mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-23 02:35:17 +01:00
Merge pull request #1534 from reasonerjt/notary-integration
Provide a api for retrieving the signature of a repo.
This commit is contained in:
commit
aaa27386c9
@ -119,6 +119,8 @@ func (b *BaseAPI) GetUserIDForRequest() (int, bool, bool) {
|
|||||||
user = nil
|
user = nil
|
||||||
}
|
}
|
||||||
if user != nil {
|
if user != nil {
|
||||||
|
b.SetSession("userId", user.UserID)
|
||||||
|
b.SetSession("username", user.Username)
|
||||||
// User login successfully no further check required.
|
// User login successfully no further check required.
|
||||||
return user.UserID, false, true
|
return user.UserID, false, true
|
||||||
}
|
}
|
||||||
|
82
src/common/utils/notary/helper.go
Normal file
82
src/common/utils/notary/helper.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package notary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/notary"
|
||||||
|
"github.com/docker/notary/client"
|
||||||
|
"github.com/docker/notary/trustpinning"
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/registry"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/registry/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
notaryEndpoint = "http://notary-server:4443"
|
||||||
|
notaryCachePath = "/root/notary"
|
||||||
|
trustPin trustpinning.TrustPinConfig
|
||||||
|
mockRetriever notary.PassRetriever
|
||||||
|
)
|
||||||
|
|
||||||
|
// Target represents the json object of a target of a docker image in notary.
|
||||||
|
// The struct will be used when repository is know so it won'g contain the name of a repository.
|
||||||
|
type Target struct {
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
Hashes data.Hashes `json:"hashes"`
|
||||||
|
//TODO: update fields as needed.
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
mockRetriever = func(keyName, alias string, createNew bool, attempts int) (passphrase string, giveup bool, err error) {
|
||||||
|
passphrase = "hardcode"
|
||||||
|
giveup = false
|
||||||
|
err = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
trustPin = trustpinning.TrustPinConfig{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTargets is a help function called by API to fetch signature information of a given repository.
|
||||||
|
// Per docker's convention the repository should contain the information of endpoint, i.e. it should look
|
||||||
|
// like "10.117.4.117/library/ubuntu", instead of "library/ubuntu" (fqRepo for fully-qualified repo)
|
||||||
|
func GetTargets(username string, fqRepo string) ([]Target, error) {
|
||||||
|
res := []Target{}
|
||||||
|
authorizer := auth.NewNotaryUsernameTokenAuthorizer(username, "repository", fqRepo, "pull")
|
||||||
|
store, err := auth.NewAuthorizerStore(strings.Split(notaryEndpoint, "//")[1], true, authorizer)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
tr := registry.NewTransport(registry.GetHTTPTransport(true), store)
|
||||||
|
gun := data.GUN(fqRepo)
|
||||||
|
notaryRepo, err := client.NewFileCachedNotaryRepository(notaryCachePath, gun, notaryEndpoint, tr, mockRetriever, trustPin)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
targets, err := notaryRepo.ListTargets(data.CanonicalTargetsRole)
|
||||||
|
if _, ok := err.(client.ErrRepositoryNotExist); ok {
|
||||||
|
log.Errorf("Repository not exist, repo: %s, error: %v, returning empty signature", fqRepo, err)
|
||||||
|
return res, nil
|
||||||
|
} else if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
for _, t := range targets {
|
||||||
|
res = append(res, Target{t.Name, t.Hashes})
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
31
src/common/utils/notary/helper_test.go
Normal file
31
src/common/utils/notary/helper_test.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package notary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
notarytest "github.com/vmware/harbor/src/common/utils/notary/test"
|
||||||
|
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var endpoint = "10.117.4.142"
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
notaryServer := notarytest.NewNotaryServer(endpoint)
|
||||||
|
defer notaryServer.Close()
|
||||||
|
notaryEndpoint = notaryServer.URL
|
||||||
|
notaryCachePath = "/tmp/notary"
|
||||||
|
m.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTargets(t *testing.T) {
|
||||||
|
targets, err := GetTargets("admin", path.Join(endpoint, "notary-demo/busybox"))
|
||||||
|
assert.Nil(t, err, fmt.Sprintf("Unexpected error: %v", err))
|
||||||
|
assert.Equal(t, 1, len(targets), "")
|
||||||
|
assert.Equal(t, "1.0", targets[0].Tag, "")
|
||||||
|
|
||||||
|
targets, err = GetTargets("admin", path.Join(endpoint, "notary-demo/notexist"))
|
||||||
|
assert.Nil(t, err, fmt.Sprintf("Unexpected error: %v", err))
|
||||||
|
assert.Equal(t, 0, len(targets), "Targets list should be empty for non exist repo.")
|
||||||
|
}
|
44
src/common/utils/notary/test/server.go
Normal file
44
src/common/utils/notary/test/server.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"path"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func currPath() string {
|
||||||
|
_, f, _, ok := runtime.Caller(0)
|
||||||
|
if !ok {
|
||||||
|
panic("Failed to get current directory")
|
||||||
|
}
|
||||||
|
return path.Dir(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNotaryServer creates a notary server for testing.
|
||||||
|
func NewNotaryServer(endpoint string) *httptest.Server {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
validRoot := fmt.Sprintf("/v2/%s/notary-demo/busybox/_trust/tuf/", endpoint)
|
||||||
|
invalidRoot := fmt.Sprintf("/v2/%s/notary-demo/fail/_trust/tuf/", endpoint)
|
||||||
|
p := currPath()
|
||||||
|
fmt.Printf("valid web root: %s, local path: %s\n", validRoot, path.Join(p, "valid"))
|
||||||
|
mux.Handle(validRoot, http.StripPrefix(validRoot, http.FileServer(http.Dir(path.Join(p, "valid")))))
|
||||||
|
mux.Handle(invalidRoot, http.StripPrefix(invalidRoot, http.FileServer(http.Dir(path.Join(p, "invalid")))))
|
||||||
|
return httptest.NewServer(mux)
|
||||||
|
}
|
1
src/common/utils/notary/test/valid/root.json
Normal file
1
src/common/utils/notary/test/valid/root.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"signed":{"_type":"Root","consistent_snapshot":false,"expires":"2027-02-26T20:58:40.741161013+08:00","keys":{"54c7a86e6f03a093c432c6f31d8cfcbc8637bb4ee9223de8a68971ddb9b53b35":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYgQ4QwWAHPDTlQvTSPyEDw0aAI9n9PY0hLtkgv2nbGo/mE5Da9gFX4o1wG8CNtzRWEf8RnHL1tpmmhQkRx5Byw=="}},"756dc9faa625646ff80e26a25e05e3df88254e9be68b92b68dbfea7b4697292e":{"keytype":"ecdsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJpVENDQVMrZ0F3SUJBZ0lRZFlWTnZQbGtqdHlCSjlaT3YxaDQ4VEFLQmdncWhrak9QUVFEQWpBck1Ta3cKSndZRFZRUURFeUF4TUM0eE1UY3VOQzR4TkRJdmJtOTBZWEo1TFdSbGJXOHZZblZ6ZVdKdmVEQWVGdzB4TnpBeQpNamd4TWpVNE16aGFGdzB5TnpBeU1qWXhNalU0TXpoYU1Dc3hLVEFuQmdOVkJBTVRJREV3TGpFeE55NDBMakUwCk1pOXViM1JoY25rdFpHVnRieTlpZFhONVltOTRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUKNktZRzdIeC90SHJPMUh6SkRuTSs0SmdyMFJBWWR3N0w5MVhMRTJHV0lCeUJjSTRXMktSQlMxUHY4RlQwd2V4Kwo1cHNvZGZtcTdObWFCYitUQU85ZWRhTTFNRE13RGdZRFZSMFBBUUgvQkFRREFnV2dNQk1HQTFVZEpRUU1NQW9HCkNDc0dBUVVGQndNRE1Bd0dBMVVkRXdFQi93UUNNQUF3Q2dZSUtvWkl6ajBFQXdJRFNBQXdSUUlnT25rNENWelgKU2dacFZjSy9wa01VTWFmOUpCeGRidHgvTkNxRWJpaHJUbEFDSVFEbytudkh6azF1SURLUlc5c01ZNG5zaUtxSAprcUR4UEhaRGlZVXE0UExoOHc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="}},"8230fd1fbdf1d7de675cd93ec0a64685a63f0db50a65217555f321939efe59df":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDkmdEwFUhMC+NRy3TuchNprTD8HoRUE+X5RPxevxdl3qcWIFk+26GIYYMMTqFcsmDzaoGXqixdqcJA5WaTg79A=="}},"f45d9afcbb5afd810369f5c2ef84477b75502c22867e66e5f69465f18c6ae157":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESWYg9Ix/pC2lVu1WTBQ0obYVdT+P9Xh6qMkvD1YVv0t28vxoiKYobmVwAfOzdERfXTJM9jIbZwP94Q41HQB2Zg=="}}},"roles":{"root":{"keyids":["756dc9faa625646ff80e26a25e05e3df88254e9be68b92b68dbfea7b4697292e"],"threshold":1},"snapshot":{"keyids":["8230fd1fbdf1d7de675cd93ec0a64685a63f0db50a65217555f321939efe59df"],"threshold":1},"targets":{"keyids":["f45d9afcbb5afd810369f5c2ef84477b75502c22867e66e5f69465f18c6ae157"],"threshold":1},"timestamp":{"keyids":["54c7a86e6f03a093c432c6f31d8cfcbc8637bb4ee9223de8a68971ddb9b53b35"],"threshold":1}},"version":1},"signatures":[{"keyid":"756dc9faa625646ff80e26a25e05e3df88254e9be68b92b68dbfea7b4697292e","method":"ecdsa","sig":"b1V3xDWGp0YNFde9Hgx4yipiebzZedhBaVRJSfxKsjxRmFmvNty8hvTL1D7mURZkc7FJPcsN/o3xC9AlUz/isQ=="}]}
|
@ -0,0 +1 @@
|
|||||||
|
{"signed":{"_type":"Snapshot","expires":"2020-02-28T12:58:40.793595145Z","meta":{"root":{"hashes":{"sha256":"yy0hAbWa41HhHIVfmexYbqqzSc60ZB8Vz55BdPjEYxI=","sha512":"kT7I5pFqI35onn6tghIcwSR24jVgxQG/rF+Ct4eFJBpr7J8kMKL86vcD+pjbWkh/px6oPRm89+34i45woam89w=="},"length":2429},"targets":{"hashes":{"sha256":"iBYw3fp614v3WkMfhrf2gYLgsSAcS8bSuNHy3eQAKzA=","sha512":"ZC78eLxWK4o8PZoAkDOyxN8ggBDpLKXHKZUdu558B/lYVYFmXpdzKglw/87hE/jDXbfct1sh2EBmD68ESLjG1Q=="},"length":433}},"version":1},"signatures":[{"keyid":"8230fd1fbdf1d7de675cd93ec0a64685a63f0db50a65217555f321939efe59df","method":"ecdsa","sig":"/cx8YA5vwxRckZQjUQxQ+OghKEy1R2Ha8m1oHLtEfvqzKIKNyZvNo3I9AMKMDgukz85JDKRS7zFg88jkkghleA=="}]}
|
@ -0,0 +1 @@
|
|||||||
|
{"signed":{"_type":"Targets","delegations":{"keys":{},"roles":[]},"expires":"2020-02-28T20:58:40.770351325+08:00","targets":{"1.0":{"hashes":{"sha256":"E1lggRW5RZnlZBY4usWu8d36p5u5YFfr9B68jTOs+Kc="},"length":527}},"version":2},"signatures":[{"keyid":"f45d9afcbb5afd810369f5c2ef84477b75502c22867e66e5f69465f18c6ae157","method":"ecdsa","sig":"T070LEVEi5cdA1RRt0MOeYlxl+KEAyfa8uGkD0OJI/V9OQlh12aDDu7H6qhR5qk1LmQpwTHBOtdEnjZd2bkN6w=="}]}
|
1
src/common/utils/notary/test/valid/timestamp.json
Normal file
1
src/common/utils/notary/test/valid/timestamp.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"signed":{"_type":"Timestamp","expires":"2017-03-14T12:58:40.833773016Z","meta":{"snapshot":{"hashes":{"sha256":"fPRwHL3qeGxQnGpBdKd7JcBoHMjSbQcUnpF0TTiq8io=","sha512":"vbJK5eX8iSIjWu2nZAHLzHLuETvz6kSzCOoYyn1C86BgieMHFpZmLgEj7AKuG9svBCYc17nii3B8ROfiLvNHaQ=="},"length":683}},"version":1},"signatures":[{"keyid":"54c7a86e6f03a093c432c6f31d8cfcbc8637bb4ee9223de8a68971ddb9b53b35","method":"ecdsa","sig":"fK6IF/jcigZ2mz5kqqb9Yma97zUOGB4OQqDfxQcAskW7DhpsKIWB1l+E7m0IPFBbIrL8q9l0GjlumCNVptmauw=="}]}
|
@ -252,9 +252,21 @@ type usernameTokenAuthorizer struct {
|
|||||||
username string
|
username string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUsernameTokenAuthorizer returns a authorizer which will generate a token according to
|
// NewRegistryUsernameTokenAuthorizer returns an authorizer to generate token for registry according to
|
||||||
// the user's privileges
|
// the user's privileges
|
||||||
func NewUsernameTokenAuthorizer(username string, scopeType, scopeName string, scopeActions ...string) Authorizer {
|
func NewRegistryUsernameTokenAuthorizer(username, scopeType, scopeName string, scopeActions ...string) Authorizer {
|
||||||
|
return newUsernameTokenAuthorizer(false, username, scopeType, scopeName, scopeActions...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNotaryUsernameTokenAuthorizer returns an authorizer to generate token for notary according to
|
||||||
|
// the user's privileges
|
||||||
|
func NewNotaryUsernameTokenAuthorizer(username, scopeType, scopeName string, scopeActions ...string) Authorizer {
|
||||||
|
return newUsernameTokenAuthorizer(true, username, scopeType, scopeName, scopeActions...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newUsernameTokenAuthorizer returns a authorizer which will generate a token according to
|
||||||
|
// the user's privileges
|
||||||
|
func newUsernameTokenAuthorizer(notary bool, username, scopeType, scopeName string, scopeActions ...string) Authorizer {
|
||||||
authorizer := &usernameTokenAuthorizer{
|
authorizer := &usernameTokenAuthorizer{
|
||||||
username: username,
|
username: username,
|
||||||
}
|
}
|
||||||
@ -264,9 +276,11 @@ func NewUsernameTokenAuthorizer(username string, scopeType, scopeName string, sc
|
|||||||
Name: scopeName,
|
Name: scopeName,
|
||||||
Actions: scopeActions,
|
Actions: scopeActions,
|
||||||
}
|
}
|
||||||
|
if notary {
|
||||||
authorizer.tg = authorizer.generateToken
|
authorizer.tg = authorizer.genNotaryToken
|
||||||
|
} else {
|
||||||
|
authorizer.tg = authorizer.genRegistryToken
|
||||||
|
}
|
||||||
return authorizer
|
return authorizer
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,3 +288,13 @@ func (u *usernameTokenAuthorizer) generateToken(realm, service string, scopes []
|
|||||||
token, expiresIn, issuedAt, err = token_util.RegistryTokenForUI(u.username, service, scopes)
|
token, expiresIn, issuedAt, err = token_util.RegistryTokenForUI(u.username, service, scopes)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *usernameTokenAuthorizer) genRegistryToken(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) {
|
||||||
|
token, expiresIn, issuedAt, err = token_util.RegistryTokenForUI(u.username, service, scopes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *usernameTokenAuthorizer) genNotaryToken(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) {
|
||||||
|
token, expiresIn, issuedAt, err = token_util.NotaryTokenForUI(u.username, service, scopes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -19,22 +19,23 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/distribution/manifest/schema1"
|
"github.com/docker/distribution/manifest/schema1"
|
||||||
"github.com/docker/distribution/manifest/schema2"
|
"github.com/docker/distribution/manifest/schema2"
|
||||||
"github.com/vmware/harbor/src/common/api"
|
"github.com/vmware/harbor/src/common/api"
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
|
||||||
"github.com/vmware/harbor/src/common/utils/registry"
|
|
||||||
svc_utils "github.com/vmware/harbor/src/ui/service/utils"
|
|
||||||
|
|
||||||
registry_error "github.com/vmware/harbor/src/common/utils/registry/error"
|
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/utils"
|
"github.com/vmware/harbor/src/common/utils"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/notary"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/registry"
|
||||||
"github.com/vmware/harbor/src/common/utils/registry/auth"
|
"github.com/vmware/harbor/src/common/utils/registry/auth"
|
||||||
|
registry_error "github.com/vmware/harbor/src/common/utils/registry/error"
|
||||||
"github.com/vmware/harbor/src/ui/config"
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
|
svc_utils "github.com/vmware/harbor/src/ui/service/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RepositoryAPI handles request to /api/repositories /api/repositories/tags /api/repositories/manifests, the parm has to be put
|
// RepositoryAPI handles request to /api/repositories /api/repositories/tags /api/repositories/manifests, the parm has to be put
|
||||||
@ -440,6 +441,34 @@ func (ra *RepositoryAPI) GetTopRepos() {
|
|||||||
ra.ServeJSON()
|
ra.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//GetSignatures handles request GET /api/repositories/signatures
|
||||||
|
func (ra *RepositoryAPI) GetSignatures() {
|
||||||
|
//use this func to init session.
|
||||||
|
ra.GetUserIDForRequest()
|
||||||
|
repoName := ra.GetString("repo_name")
|
||||||
|
if len(repoName) == 0 {
|
||||||
|
ra.CustomAbort(http.StatusBadRequest, "repo_name is nil")
|
||||||
|
}
|
||||||
|
ext, err := config.ExtEndpoint()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error while reading external endpoint: %v", err)
|
||||||
|
ra.CustomAbort(http.StatusInternalServerError, "internal error")
|
||||||
|
}
|
||||||
|
endpoint := strings.Split(ext, "//")[1]
|
||||||
|
fqRepo := path.Join(endpoint, repoName)
|
||||||
|
username, err := ra.getUsername()
|
||||||
|
if err != nil {
|
||||||
|
log.Warningf("Error when getting username: %v", err)
|
||||||
|
}
|
||||||
|
targets, err := notary.GetTargets(username, fqRepo)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error while fetching signature from notary: %v", err)
|
||||||
|
ra.CustomAbort(http.StatusInternalServerError, "internal error")
|
||||||
|
}
|
||||||
|
ra.Data["json"] = targets
|
||||||
|
ra.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
func newRepositoryClient(endpoint string, insecure bool, username, password, repository, scopeType, scopeName string,
|
func newRepositoryClient(endpoint string, insecure bool, username, password, repository, scopeType, scopeName string,
|
||||||
scopeActions ...string) (*registry.Repository, error) {
|
scopeActions ...string) (*registry.Repository, error) {
|
||||||
|
|
||||||
|
@ -499,7 +499,7 @@ func repositoryExist(name string, client *registry.Repository) (bool, error) {
|
|||||||
// NewRegistryClient ...
|
// NewRegistryClient ...
|
||||||
func NewRegistryClient(endpoint string, insecure bool, username, scopeType, scopeName string,
|
func NewRegistryClient(endpoint string, insecure bool, username, scopeType, scopeName string,
|
||||||
scopeActions ...string) (*registry.Registry, error) {
|
scopeActions ...string) (*registry.Registry, error) {
|
||||||
authorizer := auth.NewUsernameTokenAuthorizer(username, scopeType, scopeName, scopeActions...)
|
authorizer := auth.NewRegistryUsernameTokenAuthorizer(username, scopeType, scopeName, scopeActions...)
|
||||||
|
|
||||||
store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer)
|
store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -517,7 +517,7 @@ func NewRegistryClient(endpoint string, insecure bool, username, scopeType, scop
|
|||||||
func NewRepositoryClient(endpoint string, insecure bool, username, repository, scopeType, scopeName string,
|
func NewRepositoryClient(endpoint string, insecure bool, username, repository, scopeType, scopeName string,
|
||||||
scopeActions ...string) (*registry.Repository, error) {
|
scopeActions ...string) (*registry.Repository, error) {
|
||||||
|
|
||||||
authorizer := auth.NewUsernameTokenAuthorizer(username, scopeType, scopeName, scopeActions...)
|
authorizer := auth.NewRegistryUsernameTokenAuthorizer(username, scopeType, scopeName, scopeActions...)
|
||||||
|
|
||||||
store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer)
|
store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -74,6 +74,7 @@ func initRouters() {
|
|||||||
beego.Router("/api/repositories", &api.RepositoryAPI{})
|
beego.Router("/api/repositories", &api.RepositoryAPI{})
|
||||||
beego.Router("/api/repositories/tags", &api.RepositoryAPI{}, "get:GetTags")
|
beego.Router("/api/repositories/tags", &api.RepositoryAPI{}, "get:GetTags")
|
||||||
beego.Router("/api/repositories/manifests", &api.RepositoryAPI{}, "get:GetManifests")
|
beego.Router("/api/repositories/manifests", &api.RepositoryAPI{}, "get:GetManifests")
|
||||||
|
beego.Router("/api/repositories/signatures", &api.RepositoryAPI{}, "get:GetSignatures")
|
||||||
beego.Router("/api/jobs/replication/", &api.RepJobAPI{}, "get:List")
|
beego.Router("/api/jobs/replication/", &api.RepJobAPI{}, "get:List")
|
||||||
beego.Router("/api/jobs/replication/:id([0-9]+)", &api.RepJobAPI{})
|
beego.Router("/api/jobs/replication/:id([0-9]+)", &api.RepJobAPI{})
|
||||||
beego.Router("/api/jobs/replication/:id([0-9]+)/log", &api.RepJobAPI{}, "get:GetLog")
|
beego.Router("/api/jobs/replication/:id([0-9]+)/log", &api.RepJobAPI{}, "get:GetLog")
|
||||||
|
27
src/vendor/github.com/agl/ed25519/LICENSE
generated
vendored
Normal file
27
src/vendor/github.com/agl/ed25519/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
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.
|
127
src/vendor/github.com/agl/ed25519/ed25519.go
generated
vendored
Normal file
127
src/vendor/github.com/agl/ed25519/ed25519.go
generated
vendored
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
// 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
|
||||||
|
}
|
1411
src/vendor/github.com/agl/ed25519/edwards25519/const.go
generated
vendored
Normal file
1411
src/vendor/github.com/agl/ed25519/edwards25519/const.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1773
src/vendor/github.com/agl/ed25519/edwards25519/edwards25519.go
generated
vendored
Normal file
1773
src/vendor/github.com/agl/ed25519/edwards25519/edwards25519.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
27
src/vendor/github.com/docker/go/LICENSE
generated
vendored
Normal file
27
src/vendor/github.com/docker/go/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
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.
|
1168
src/vendor/github.com/docker/go/canonical/json/decode.go
generated
vendored
Normal file
1168
src/vendor/github.com/docker/go/canonical/json/decode.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1250
src/vendor/github.com/docker/go/canonical/json/encode.go
generated
vendored
Normal file
1250
src/vendor/github.com/docker/go/canonical/json/encode.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
143
src/vendor/github.com/docker/go/canonical/json/fold.go
generated
vendored
Normal file
143
src/vendor/github.com/docker/go/canonical/json/fold.go
generated
vendored
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
// 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 json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
caseMask = ^byte(0x20) // Mask to ignore case in ASCII.
|
||||||
|
kelvin = '\u212a'
|
||||||
|
smallLongEss = '\u017f'
|
||||||
|
)
|
||||||
|
|
||||||
|
// foldFunc returns one of four different case folding equivalence
|
||||||
|
// functions, from most general (and slow) to fastest:
|
||||||
|
//
|
||||||
|
// 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8
|
||||||
|
// 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S')
|
||||||
|
// 3) asciiEqualFold, no special, but includes non-letters (including _)
|
||||||
|
// 4) simpleLetterEqualFold, no specials, no non-letters.
|
||||||
|
//
|
||||||
|
// The letters S and K are special because they map to 3 runes, not just 2:
|
||||||
|
// * S maps to s and to U+017F 'ſ' Latin small letter long s
|
||||||
|
// * k maps to K and to U+212A 'K' Kelvin sign
|
||||||
|
// See https://play.golang.org/p/tTxjOc0OGo
|
||||||
|
//
|
||||||
|
// The returned function is specialized for matching against s and
|
||||||
|
// should only be given s. It's not curried for performance reasons.
|
||||||
|
func foldFunc(s []byte) func(s, t []byte) bool {
|
||||||
|
nonLetter := false
|
||||||
|
special := false // special letter
|
||||||
|
for _, b := range s {
|
||||||
|
if b >= utf8.RuneSelf {
|
||||||
|
return bytes.EqualFold
|
||||||
|
}
|
||||||
|
upper := b & caseMask
|
||||||
|
if upper < 'A' || upper > 'Z' {
|
||||||
|
nonLetter = true
|
||||||
|
} else if upper == 'K' || upper == 'S' {
|
||||||
|
// See above for why these letters are special.
|
||||||
|
special = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if special {
|
||||||
|
return equalFoldRight
|
||||||
|
}
|
||||||
|
if nonLetter {
|
||||||
|
return asciiEqualFold
|
||||||
|
}
|
||||||
|
return simpleLetterEqualFold
|
||||||
|
}
|
||||||
|
|
||||||
|
// equalFoldRight is a specialization of bytes.EqualFold when s is
|
||||||
|
// known to be all ASCII (including punctuation), but contains an 's',
|
||||||
|
// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t.
|
||||||
|
// See comments on foldFunc.
|
||||||
|
func equalFoldRight(s, t []byte) bool {
|
||||||
|
for _, sb := range s {
|
||||||
|
if len(t) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
tb := t[0]
|
||||||
|
if tb < utf8.RuneSelf {
|
||||||
|
if sb != tb {
|
||||||
|
sbUpper := sb & caseMask
|
||||||
|
if 'A' <= sbUpper && sbUpper <= 'Z' {
|
||||||
|
if sbUpper != tb&caseMask {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t = t[1:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// sb is ASCII and t is not. t must be either kelvin
|
||||||
|
// sign or long s; sb must be s, S, k, or K.
|
||||||
|
tr, size := utf8.DecodeRune(t)
|
||||||
|
switch sb {
|
||||||
|
case 's', 'S':
|
||||||
|
if tr != smallLongEss {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case 'k', 'K':
|
||||||
|
if tr != kelvin {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
t = t[size:]
|
||||||
|
|
||||||
|
}
|
||||||
|
if len(t) > 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// asciiEqualFold is a specialization of bytes.EqualFold for use when
|
||||||
|
// s is all ASCII (but may contain non-letters) and contains no
|
||||||
|
// special-folding letters.
|
||||||
|
// See comments on foldFunc.
|
||||||
|
func asciiEqualFold(s, t []byte) bool {
|
||||||
|
if len(s) != len(t) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, sb := range s {
|
||||||
|
tb := t[i]
|
||||||
|
if sb == tb {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') {
|
||||||
|
if sb&caseMask != tb&caseMask {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// simpleLetterEqualFold is a specialization of bytes.EqualFold for
|
||||||
|
// use when s is all ASCII letters (no underscores, etc) and also
|
||||||
|
// doesn't contain 'k', 'K', 's', or 'S'.
|
||||||
|
// See comments on foldFunc.
|
||||||
|
func simpleLetterEqualFold(s, t []byte) bool {
|
||||||
|
if len(s) != len(t) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, b := range s {
|
||||||
|
if b&caseMask != t[i]&caseMask {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
141
src/vendor/github.com/docker/go/canonical/json/indent.go
generated
vendored
Normal file
141
src/vendor/github.com/docker/go/canonical/json/indent.go
generated
vendored
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
// Copyright 2010 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 json
|
||||||
|
|
||||||
|
import "bytes"
|
||||||
|
|
||||||
|
// Compact appends to dst the JSON-encoded src with
|
||||||
|
// insignificant space characters elided.
|
||||||
|
func Compact(dst *bytes.Buffer, src []byte) error {
|
||||||
|
return compact(dst, src, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func compact(dst *bytes.Buffer, src []byte, escape bool) error {
|
||||||
|
origLen := dst.Len()
|
||||||
|
var scan scanner
|
||||||
|
scan.reset()
|
||||||
|
start := 0
|
||||||
|
for i, c := range src {
|
||||||
|
if escape && (c == '<' || c == '>' || c == '&') {
|
||||||
|
if start < i {
|
||||||
|
dst.Write(src[start:i])
|
||||||
|
}
|
||||||
|
dst.WriteString(`\u00`)
|
||||||
|
dst.WriteByte(hex[c>>4])
|
||||||
|
dst.WriteByte(hex[c&0xF])
|
||||||
|
start = i + 1
|
||||||
|
}
|
||||||
|
// Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9).
|
||||||
|
if c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 {
|
||||||
|
if start < i {
|
||||||
|
dst.Write(src[start:i])
|
||||||
|
}
|
||||||
|
dst.WriteString(`\u202`)
|
||||||
|
dst.WriteByte(hex[src[i+2]&0xF])
|
||||||
|
start = i + 3
|
||||||
|
}
|
||||||
|
v := scan.step(&scan, c)
|
||||||
|
if v >= scanSkipSpace {
|
||||||
|
if v == scanError {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if start < i {
|
||||||
|
dst.Write(src[start:i])
|
||||||
|
}
|
||||||
|
start = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if scan.eof() == scanError {
|
||||||
|
dst.Truncate(origLen)
|
||||||
|
return scan.err
|
||||||
|
}
|
||||||
|
if start < len(src) {
|
||||||
|
dst.Write(src[start:])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newline(dst *bytes.Buffer, prefix, indent string, depth int) {
|
||||||
|
dst.WriteByte('\n')
|
||||||
|
dst.WriteString(prefix)
|
||||||
|
for i := 0; i < depth; i++ {
|
||||||
|
dst.WriteString(indent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indent appends to dst an indented form of the JSON-encoded src.
|
||||||
|
// Each element in a JSON object or array begins on a new,
|
||||||
|
// indented line beginning with prefix followed by one or more
|
||||||
|
// copies of indent according to the indentation nesting.
|
||||||
|
// The data appended to dst does not begin with the prefix nor
|
||||||
|
// any indentation, to make it easier to embed inside other formatted JSON data.
|
||||||
|
// Although leading space characters (space, tab, carriage return, newline)
|
||||||
|
// at the beginning of src are dropped, trailing space characters
|
||||||
|
// at the end of src are preserved and copied to dst.
|
||||||
|
// For example, if src has no trailing spaces, neither will dst;
|
||||||
|
// if src ends in a trailing newline, so will dst.
|
||||||
|
func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
|
||||||
|
origLen := dst.Len()
|
||||||
|
var scan scanner
|
||||||
|
scan.reset()
|
||||||
|
needIndent := false
|
||||||
|
depth := 0
|
||||||
|
for _, c := range src {
|
||||||
|
scan.bytes++
|
||||||
|
v := scan.step(&scan, c)
|
||||||
|
if v == scanSkipSpace {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if v == scanError {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if needIndent && v != scanEndObject && v != scanEndArray {
|
||||||
|
needIndent = false
|
||||||
|
depth++
|
||||||
|
newline(dst, prefix, indent, depth)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit semantically uninteresting bytes
|
||||||
|
// (in particular, punctuation in strings) unmodified.
|
||||||
|
if v == scanContinue {
|
||||||
|
dst.WriteByte(c)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add spacing around real punctuation.
|
||||||
|
switch c {
|
||||||
|
case '{', '[':
|
||||||
|
// delay indent so that empty object and array are formatted as {} and [].
|
||||||
|
needIndent = true
|
||||||
|
dst.WriteByte(c)
|
||||||
|
|
||||||
|
case ',':
|
||||||
|
dst.WriteByte(c)
|
||||||
|
newline(dst, prefix, indent, depth)
|
||||||
|
|
||||||
|
case ':':
|
||||||
|
dst.WriteByte(c)
|
||||||
|
dst.WriteByte(' ')
|
||||||
|
|
||||||
|
case '}', ']':
|
||||||
|
if needIndent {
|
||||||
|
// suppress indent in empty object/array
|
||||||
|
needIndent = false
|
||||||
|
} else {
|
||||||
|
depth--
|
||||||
|
newline(dst, prefix, indent, depth)
|
||||||
|
}
|
||||||
|
dst.WriteByte(c)
|
||||||
|
|
||||||
|
default:
|
||||||
|
dst.WriteByte(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if scan.eof() == scanError {
|
||||||
|
dst.Truncate(origLen)
|
||||||
|
return scan.err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
623
src/vendor/github.com/docker/go/canonical/json/scanner.go
generated
vendored
Normal file
623
src/vendor/github.com/docker/go/canonical/json/scanner.go
generated
vendored
Normal file
@ -0,0 +1,623 @@
|
|||||||
|
// Copyright 2010 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 json
|
||||||
|
|
||||||
|
// JSON value parser state machine.
|
||||||
|
// Just about at the limit of what is reasonable to write by hand.
|
||||||
|
// Some parts are a bit tedious, but overall it nicely factors out the
|
||||||
|
// otherwise common code from the multiple scanning functions
|
||||||
|
// in this package (Compact, Indent, checkValid, nextValue, etc).
|
||||||
|
//
|
||||||
|
// This file starts with two simple examples using the scanner
|
||||||
|
// before diving into the scanner itself.
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
// checkValid verifies that data is valid JSON-encoded data.
|
||||||
|
// scan is passed in for use by checkValid to avoid an allocation.
|
||||||
|
func checkValid(data []byte, scan *scanner) error {
|
||||||
|
scan.reset()
|
||||||
|
for _, c := range data {
|
||||||
|
scan.bytes++
|
||||||
|
if scan.step(scan, c) == scanError {
|
||||||
|
return scan.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if scan.eof() == scanError {
|
||||||
|
return scan.err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextValue splits data after the next whole JSON value,
|
||||||
|
// returning that value and the bytes that follow it as separate slices.
|
||||||
|
// scan is passed in for use by nextValue to avoid an allocation.
|
||||||
|
func nextValue(data []byte, scan *scanner) (value, rest []byte, err error) {
|
||||||
|
scan.reset()
|
||||||
|
for i, c := range data {
|
||||||
|
v := scan.step(scan, c)
|
||||||
|
if v >= scanEndObject {
|
||||||
|
switch v {
|
||||||
|
// probe the scanner with a space to determine whether we will
|
||||||
|
// get scanEnd on the next character. Otherwise, if the next character
|
||||||
|
// is not a space, scanEndTop allocates a needless error.
|
||||||
|
case scanEndObject, scanEndArray:
|
||||||
|
if scan.step(scan, ' ') == scanEnd {
|
||||||
|
return data[:i+1], data[i+1:], nil
|
||||||
|
}
|
||||||
|
case scanError:
|
||||||
|
return nil, nil, scan.err
|
||||||
|
case scanEnd:
|
||||||
|
return data[:i], data[i:], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if scan.eof() == scanError {
|
||||||
|
return nil, nil, scan.err
|
||||||
|
}
|
||||||
|
return data, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A SyntaxError is a description of a JSON syntax error.
|
||||||
|
type SyntaxError struct {
|
||||||
|
msg string // description of error
|
||||||
|
Offset int64 // error occurred after reading Offset bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *SyntaxError) Error() string { return e.msg }
|
||||||
|
|
||||||
|
// A scanner is a JSON scanning state machine.
|
||||||
|
// Callers call scan.reset() and then pass bytes in one at a time
|
||||||
|
// by calling scan.step(&scan, c) for each byte.
|
||||||
|
// The return value, referred to as an opcode, tells the
|
||||||
|
// caller about significant parsing events like beginning
|
||||||
|
// and ending literals, objects, and arrays, so that the
|
||||||
|
// caller can follow along if it wishes.
|
||||||
|
// The return value scanEnd indicates that a single top-level
|
||||||
|
// JSON value has been completed, *before* the byte that
|
||||||
|
// just got passed in. (The indication must be delayed in order
|
||||||
|
// to recognize the end of numbers: is 123 a whole value or
|
||||||
|
// the beginning of 12345e+6?).
|
||||||
|
type scanner struct {
|
||||||
|
// The step is a func to be called to execute the next transition.
|
||||||
|
// Also tried using an integer constant and a single func
|
||||||
|
// with a switch, but using the func directly was 10% faster
|
||||||
|
// on a 64-bit Mac Mini, and it's nicer to read.
|
||||||
|
step func(*scanner, byte) int
|
||||||
|
|
||||||
|
// Reached end of top-level value.
|
||||||
|
endTop bool
|
||||||
|
|
||||||
|
// Stack of what we're in the middle of - array values, object keys, object values.
|
||||||
|
parseState []int
|
||||||
|
|
||||||
|
// Error that happened, if any.
|
||||||
|
err error
|
||||||
|
|
||||||
|
// 1-byte redo (see undo method)
|
||||||
|
redo bool
|
||||||
|
redoCode int
|
||||||
|
redoState func(*scanner, byte) int
|
||||||
|
|
||||||
|
// total bytes consumed, updated by decoder.Decode
|
||||||
|
bytes int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// These values are returned by the state transition functions
|
||||||
|
// assigned to scanner.state and the method scanner.eof.
|
||||||
|
// They give details about the current state of the scan that
|
||||||
|
// callers might be interested to know about.
|
||||||
|
// It is okay to ignore the return value of any particular
|
||||||
|
// call to scanner.state: if one call returns scanError,
|
||||||
|
// every subsequent call will return scanError too.
|
||||||
|
const (
|
||||||
|
// Continue.
|
||||||
|
scanContinue = iota // uninteresting byte
|
||||||
|
scanBeginLiteral // end implied by next result != scanContinue
|
||||||
|
scanBeginObject // begin object
|
||||||
|
scanObjectKey // just finished object key (string)
|
||||||
|
scanObjectValue // just finished non-last object value
|
||||||
|
scanEndObject // end object (implies scanObjectValue if possible)
|
||||||
|
scanBeginArray // begin array
|
||||||
|
scanArrayValue // just finished array value
|
||||||
|
scanEndArray // end array (implies scanArrayValue if possible)
|
||||||
|
scanSkipSpace // space byte; can skip; known to be last "continue" result
|
||||||
|
|
||||||
|
// Stop.
|
||||||
|
scanEnd // top-level value ended *before* this byte; known to be first "stop" result
|
||||||
|
scanError // hit an error, scanner.err.
|
||||||
|
)
|
||||||
|
|
||||||
|
// These values are stored in the parseState stack.
|
||||||
|
// They give the current state of a composite value
|
||||||
|
// being scanned. If the parser is inside a nested value
|
||||||
|
// the parseState describes the nested state, outermost at entry 0.
|
||||||
|
const (
|
||||||
|
parseObjectKey = iota // parsing object key (before colon)
|
||||||
|
parseObjectValue // parsing object value (after colon)
|
||||||
|
parseArrayValue // parsing array value
|
||||||
|
)
|
||||||
|
|
||||||
|
// reset prepares the scanner for use.
|
||||||
|
// It must be called before calling s.step.
|
||||||
|
func (s *scanner) reset() {
|
||||||
|
s.step = stateBeginValue
|
||||||
|
s.parseState = s.parseState[0:0]
|
||||||
|
s.err = nil
|
||||||
|
s.redo = false
|
||||||
|
s.endTop = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// eof tells the scanner that the end of input has been reached.
|
||||||
|
// It returns a scan status just as s.step does.
|
||||||
|
func (s *scanner) eof() int {
|
||||||
|
if s.err != nil {
|
||||||
|
return scanError
|
||||||
|
}
|
||||||
|
if s.endTop {
|
||||||
|
return scanEnd
|
||||||
|
}
|
||||||
|
s.step(s, ' ')
|
||||||
|
if s.endTop {
|
||||||
|
return scanEnd
|
||||||
|
}
|
||||||
|
if s.err == nil {
|
||||||
|
s.err = &SyntaxError{"unexpected end of JSON input", s.bytes}
|
||||||
|
}
|
||||||
|
return scanError
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushParseState pushes a new parse state p onto the parse stack.
|
||||||
|
func (s *scanner) pushParseState(p int) {
|
||||||
|
s.parseState = append(s.parseState, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// popParseState pops a parse state (already obtained) off the stack
|
||||||
|
// and updates s.step accordingly.
|
||||||
|
func (s *scanner) popParseState() {
|
||||||
|
n := len(s.parseState) - 1
|
||||||
|
s.parseState = s.parseState[0:n]
|
||||||
|
s.redo = false
|
||||||
|
if n == 0 {
|
||||||
|
s.step = stateEndTop
|
||||||
|
s.endTop = true
|
||||||
|
} else {
|
||||||
|
s.step = stateEndValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSpace(c byte) bool {
|
||||||
|
return c == ' ' || c == '\t' || c == '\r' || c == '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateBeginValueOrEmpty is the state after reading `[`.
|
||||||
|
func stateBeginValueOrEmpty(s *scanner, c byte) int {
|
||||||
|
if c <= ' ' && isSpace(c) {
|
||||||
|
return scanSkipSpace
|
||||||
|
}
|
||||||
|
if c == ']' {
|
||||||
|
return stateEndValue(s, c)
|
||||||
|
}
|
||||||
|
return stateBeginValue(s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateBeginValue is the state at the beginning of the input.
|
||||||
|
func stateBeginValue(s *scanner, c byte) int {
|
||||||
|
if c <= ' ' && isSpace(c) {
|
||||||
|
return scanSkipSpace
|
||||||
|
}
|
||||||
|
switch c {
|
||||||
|
case '{':
|
||||||
|
s.step = stateBeginStringOrEmpty
|
||||||
|
s.pushParseState(parseObjectKey)
|
||||||
|
return scanBeginObject
|
||||||
|
case '[':
|
||||||
|
s.step = stateBeginValueOrEmpty
|
||||||
|
s.pushParseState(parseArrayValue)
|
||||||
|
return scanBeginArray
|
||||||
|
case '"':
|
||||||
|
s.step = stateInString
|
||||||
|
return scanBeginLiteral
|
||||||
|
case '-':
|
||||||
|
s.step = stateNeg
|
||||||
|
return scanBeginLiteral
|
||||||
|
case '0': // beginning of 0.123
|
||||||
|
s.step = state0
|
||||||
|
return scanBeginLiteral
|
||||||
|
case 't': // beginning of true
|
||||||
|
s.step = stateT
|
||||||
|
return scanBeginLiteral
|
||||||
|
case 'f': // beginning of false
|
||||||
|
s.step = stateF
|
||||||
|
return scanBeginLiteral
|
||||||
|
case 'n': // beginning of null
|
||||||
|
s.step = stateN
|
||||||
|
return scanBeginLiteral
|
||||||
|
}
|
||||||
|
if '1' <= c && c <= '9' { // beginning of 1234.5
|
||||||
|
s.step = state1
|
||||||
|
return scanBeginLiteral
|
||||||
|
}
|
||||||
|
return s.error(c, "looking for beginning of value")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateBeginStringOrEmpty is the state after reading `{`.
|
||||||
|
func stateBeginStringOrEmpty(s *scanner, c byte) int {
|
||||||
|
if c <= ' ' && isSpace(c) {
|
||||||
|
return scanSkipSpace
|
||||||
|
}
|
||||||
|
if c == '}' {
|
||||||
|
n := len(s.parseState)
|
||||||
|
s.parseState[n-1] = parseObjectValue
|
||||||
|
return stateEndValue(s, c)
|
||||||
|
}
|
||||||
|
return stateBeginString(s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateBeginString is the state after reading `{"key": value,`.
|
||||||
|
func stateBeginString(s *scanner, c byte) int {
|
||||||
|
if c <= ' ' && isSpace(c) {
|
||||||
|
return scanSkipSpace
|
||||||
|
}
|
||||||
|
if c == '"' {
|
||||||
|
s.step = stateInString
|
||||||
|
return scanBeginLiteral
|
||||||
|
}
|
||||||
|
return s.error(c, "looking for beginning of object key string")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateEndValue is the state after completing a value,
|
||||||
|
// such as after reading `{}` or `true` or `["x"`.
|
||||||
|
func stateEndValue(s *scanner, c byte) int {
|
||||||
|
n := len(s.parseState)
|
||||||
|
if n == 0 {
|
||||||
|
// Completed top-level before the current byte.
|
||||||
|
s.step = stateEndTop
|
||||||
|
s.endTop = true
|
||||||
|
return stateEndTop(s, c)
|
||||||
|
}
|
||||||
|
if c <= ' ' && isSpace(c) {
|
||||||
|
s.step = stateEndValue
|
||||||
|
return scanSkipSpace
|
||||||
|
}
|
||||||
|
ps := s.parseState[n-1]
|
||||||
|
switch ps {
|
||||||
|
case parseObjectKey:
|
||||||
|
if c == ':' {
|
||||||
|
s.parseState[n-1] = parseObjectValue
|
||||||
|
s.step = stateBeginValue
|
||||||
|
return scanObjectKey
|
||||||
|
}
|
||||||
|
return s.error(c, "after object key")
|
||||||
|
case parseObjectValue:
|
||||||
|
if c == ',' {
|
||||||
|
s.parseState[n-1] = parseObjectKey
|
||||||
|
s.step = stateBeginString
|
||||||
|
return scanObjectValue
|
||||||
|
}
|
||||||
|
if c == '}' {
|
||||||
|
s.popParseState()
|
||||||
|
return scanEndObject
|
||||||
|
}
|
||||||
|
return s.error(c, "after object key:value pair")
|
||||||
|
case parseArrayValue:
|
||||||
|
if c == ',' {
|
||||||
|
s.step = stateBeginValue
|
||||||
|
return scanArrayValue
|
||||||
|
}
|
||||||
|
if c == ']' {
|
||||||
|
s.popParseState()
|
||||||
|
return scanEndArray
|
||||||
|
}
|
||||||
|
return s.error(c, "after array element")
|
||||||
|
}
|
||||||
|
return s.error(c, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateEndTop is the state after finishing the top-level value,
|
||||||
|
// such as after reading `{}` or `[1,2,3]`.
|
||||||
|
// Only space characters should be seen now.
|
||||||
|
func stateEndTop(s *scanner, c byte) int {
|
||||||
|
if c != ' ' && c != '\t' && c != '\r' && c != '\n' {
|
||||||
|
// Complain about non-space byte on next call.
|
||||||
|
s.error(c, "after top-level value")
|
||||||
|
}
|
||||||
|
return scanEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateInString is the state after reading `"`.
|
||||||
|
func stateInString(s *scanner, c byte) int {
|
||||||
|
if c == '"' {
|
||||||
|
s.step = stateEndValue
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
if c == '\\' {
|
||||||
|
s.step = stateInStringEsc
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
if c < 0x20 {
|
||||||
|
return s.error(c, "in string literal")
|
||||||
|
}
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateInStringEsc is the state after reading `"\` during a quoted string.
|
||||||
|
func stateInStringEsc(s *scanner, c byte) int {
|
||||||
|
switch c {
|
||||||
|
case 'b', 'f', 'n', 'r', 't', '\\', '/', '"':
|
||||||
|
s.step = stateInString
|
||||||
|
return scanContinue
|
||||||
|
case 'u':
|
||||||
|
s.step = stateInStringEscU
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in string escape code")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateInStringEscU is the state after reading `"\u` during a quoted string.
|
||||||
|
func stateInStringEscU(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||||
|
s.step = stateInStringEscU1
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
// numbers
|
||||||
|
return s.error(c, "in \\u hexadecimal character escape")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateInStringEscU1 is the state after reading `"\u1` during a quoted string.
|
||||||
|
func stateInStringEscU1(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||||
|
s.step = stateInStringEscU12
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
// numbers
|
||||||
|
return s.error(c, "in \\u hexadecimal character escape")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateInStringEscU12 is the state after reading `"\u12` during a quoted string.
|
||||||
|
func stateInStringEscU12(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||||
|
s.step = stateInStringEscU123
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
// numbers
|
||||||
|
return s.error(c, "in \\u hexadecimal character escape")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateInStringEscU123 is the state after reading `"\u123` during a quoted string.
|
||||||
|
func stateInStringEscU123(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||||
|
s.step = stateInString
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
// numbers
|
||||||
|
return s.error(c, "in \\u hexadecimal character escape")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateNeg is the state after reading `-` during a number.
|
||||||
|
func stateNeg(s *scanner, c byte) int {
|
||||||
|
if c == '0' {
|
||||||
|
s.step = state0
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
if '1' <= c && c <= '9' {
|
||||||
|
s.step = state1
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in numeric literal")
|
||||||
|
}
|
||||||
|
|
||||||
|
// state1 is the state after reading a non-zero integer during a number,
|
||||||
|
// such as after reading `1` or `100` but not `0`.
|
||||||
|
func state1(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' {
|
||||||
|
s.step = state1
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return state0(s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// state0 is the state after reading `0` during a number.
|
||||||
|
func state0(s *scanner, c byte) int {
|
||||||
|
if c == '.' {
|
||||||
|
s.step = stateDot
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
if c == 'e' || c == 'E' {
|
||||||
|
s.step = stateE
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return stateEndValue(s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateDot is the state after reading the integer and decimal point in a number,
|
||||||
|
// such as after reading `1.`.
|
||||||
|
func stateDot(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' {
|
||||||
|
s.step = stateDot0
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "after decimal point in numeric literal")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateDot0 is the state after reading the integer, decimal point, and subsequent
|
||||||
|
// digits of a number, such as after reading `3.14`.
|
||||||
|
func stateDot0(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' {
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
if c == 'e' || c == 'E' {
|
||||||
|
s.step = stateE
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return stateEndValue(s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateE is the state after reading the mantissa and e in a number,
|
||||||
|
// such as after reading `314e` or `0.314e`.
|
||||||
|
func stateE(s *scanner, c byte) int {
|
||||||
|
if c == '+' || c == '-' {
|
||||||
|
s.step = stateESign
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return stateESign(s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateESign is the state after reading the mantissa, e, and sign in a number,
|
||||||
|
// such as after reading `314e-` or `0.314e+`.
|
||||||
|
func stateESign(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' {
|
||||||
|
s.step = stateE0
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in exponent of numeric literal")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateE0 is the state after reading the mantissa, e, optional sign,
|
||||||
|
// and at least one digit of the exponent in a number,
|
||||||
|
// such as after reading `314e-2` or `0.314e+1` or `3.14e0`.
|
||||||
|
func stateE0(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' {
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return stateEndValue(s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateT is the state after reading `t`.
|
||||||
|
func stateT(s *scanner, c byte) int {
|
||||||
|
if c == 'r' {
|
||||||
|
s.step = stateTr
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal true (expecting 'r')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateTr is the state after reading `tr`.
|
||||||
|
func stateTr(s *scanner, c byte) int {
|
||||||
|
if c == 'u' {
|
||||||
|
s.step = stateTru
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal true (expecting 'u')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateTru is the state after reading `tru`.
|
||||||
|
func stateTru(s *scanner, c byte) int {
|
||||||
|
if c == 'e' {
|
||||||
|
s.step = stateEndValue
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal true (expecting 'e')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateF is the state after reading `f`.
|
||||||
|
func stateF(s *scanner, c byte) int {
|
||||||
|
if c == 'a' {
|
||||||
|
s.step = stateFa
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal false (expecting 'a')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateFa is the state after reading `fa`.
|
||||||
|
func stateFa(s *scanner, c byte) int {
|
||||||
|
if c == 'l' {
|
||||||
|
s.step = stateFal
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal false (expecting 'l')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateFal is the state after reading `fal`.
|
||||||
|
func stateFal(s *scanner, c byte) int {
|
||||||
|
if c == 's' {
|
||||||
|
s.step = stateFals
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal false (expecting 's')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateFals is the state after reading `fals`.
|
||||||
|
func stateFals(s *scanner, c byte) int {
|
||||||
|
if c == 'e' {
|
||||||
|
s.step = stateEndValue
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal false (expecting 'e')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateN is the state after reading `n`.
|
||||||
|
func stateN(s *scanner, c byte) int {
|
||||||
|
if c == 'u' {
|
||||||
|
s.step = stateNu
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal null (expecting 'u')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateNu is the state after reading `nu`.
|
||||||
|
func stateNu(s *scanner, c byte) int {
|
||||||
|
if c == 'l' {
|
||||||
|
s.step = stateNul
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal null (expecting 'l')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateNul is the state after reading `nul`.
|
||||||
|
func stateNul(s *scanner, c byte) int {
|
||||||
|
if c == 'l' {
|
||||||
|
s.step = stateEndValue
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal null (expecting 'l')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateError is the state after reaching a syntax error,
|
||||||
|
// such as after reading `[1}` or `5.1.2`.
|
||||||
|
func stateError(s *scanner, c byte) int {
|
||||||
|
return scanError
|
||||||
|
}
|
||||||
|
|
||||||
|
// error records an error and switches to the error state.
|
||||||
|
func (s *scanner) error(c byte, context string) int {
|
||||||
|
s.step = stateError
|
||||||
|
s.err = &SyntaxError{"invalid character " + quoteChar(c) + " " + context, s.bytes}
|
||||||
|
return scanError
|
||||||
|
}
|
||||||
|
|
||||||
|
// quoteChar formats c as a quoted character literal
|
||||||
|
func quoteChar(c byte) string {
|
||||||
|
// special cases - different from quoted strings
|
||||||
|
if c == '\'' {
|
||||||
|
return `'\''`
|
||||||
|
}
|
||||||
|
if c == '"' {
|
||||||
|
return `'"'`
|
||||||
|
}
|
||||||
|
|
||||||
|
// use quoted string with different quotation marks
|
||||||
|
s := strconv.Quote(string(c))
|
||||||
|
return "'" + s[1:len(s)-1] + "'"
|
||||||
|
}
|
||||||
|
|
||||||
|
// undo causes the scanner to return scanCode from the next state transition.
|
||||||
|
// This gives callers a simple 1-byte undo mechanism.
|
||||||
|
func (s *scanner) undo(scanCode int) {
|
||||||
|
if s.redo {
|
||||||
|
panic("json: invalid use of scanner")
|
||||||
|
}
|
||||||
|
s.redoCode = scanCode
|
||||||
|
s.redoState = s.step
|
||||||
|
s.step = stateRedo
|
||||||
|
s.redo = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateRedo helps implement the scanner's 1-byte undo.
|
||||||
|
func stateRedo(s *scanner, c byte) int {
|
||||||
|
s.redo = false
|
||||||
|
s.step = s.redoState
|
||||||
|
return s.redoCode
|
||||||
|
}
|
487
src/vendor/github.com/docker/go/canonical/json/stream.go
generated
vendored
Normal file
487
src/vendor/github.com/docker/go/canonical/json/stream.go
generated
vendored
Normal file
@ -0,0 +1,487 @@
|
|||||||
|
// Copyright 2010 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 json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Decoder reads and decodes JSON objects from an input stream.
|
||||||
|
type Decoder struct {
|
||||||
|
r io.Reader
|
||||||
|
buf []byte
|
||||||
|
d decodeState
|
||||||
|
scanp int // start of unread data in buf
|
||||||
|
scan scanner
|
||||||
|
err error
|
||||||
|
|
||||||
|
tokenState int
|
||||||
|
tokenStack []int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDecoder returns a new decoder that reads from r.
|
||||||
|
//
|
||||||
|
// The decoder introduces its own buffering and may
|
||||||
|
// read data from r beyond the JSON values requested.
|
||||||
|
func NewDecoder(r io.Reader) *Decoder {
|
||||||
|
return &Decoder{r: r}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseNumber causes the Decoder to unmarshal a number into an interface{} as a
|
||||||
|
// Number instead of as a float64.
|
||||||
|
func (dec *Decoder) UseNumber() { dec.d.useNumber = true }
|
||||||
|
|
||||||
|
// Decode reads the next JSON-encoded value from its
|
||||||
|
// input and stores it in the value pointed to by v.
|
||||||
|
//
|
||||||
|
// See the documentation for Unmarshal for details about
|
||||||
|
// the conversion of JSON into a Go value.
|
||||||
|
func (dec *Decoder) Decode(v interface{}) error {
|
||||||
|
if dec.err != nil {
|
||||||
|
return dec.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dec.tokenPrepareForDecode(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dec.tokenValueAllowed() {
|
||||||
|
return &SyntaxError{msg: "not at beginning of value"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read whole value into buffer.
|
||||||
|
n, err := dec.readValue()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dec.d.init(dec.buf[dec.scanp : dec.scanp+n])
|
||||||
|
dec.scanp += n
|
||||||
|
|
||||||
|
// Don't save err from unmarshal into dec.err:
|
||||||
|
// the connection is still usable since we read a complete JSON
|
||||||
|
// object from it before the error happened.
|
||||||
|
err = dec.d.unmarshal(v)
|
||||||
|
|
||||||
|
// fixup token streaming state
|
||||||
|
dec.tokenValueEnd()
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffered returns a reader of the data remaining in the Decoder's
|
||||||
|
// buffer. The reader is valid until the next call to Decode.
|
||||||
|
func (dec *Decoder) Buffered() io.Reader {
|
||||||
|
return bytes.NewReader(dec.buf[dec.scanp:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// readValue reads a JSON value into dec.buf.
|
||||||
|
// It returns the length of the encoding.
|
||||||
|
func (dec *Decoder) readValue() (int, error) {
|
||||||
|
dec.scan.reset()
|
||||||
|
|
||||||
|
scanp := dec.scanp
|
||||||
|
var err error
|
||||||
|
Input:
|
||||||
|
for {
|
||||||
|
// Look in the buffer for a new value.
|
||||||
|
for i, c := range dec.buf[scanp:] {
|
||||||
|
dec.scan.bytes++
|
||||||
|
v := dec.scan.step(&dec.scan, c)
|
||||||
|
if v == scanEnd {
|
||||||
|
scanp += i
|
||||||
|
break Input
|
||||||
|
}
|
||||||
|
// scanEnd is delayed one byte.
|
||||||
|
// We might block trying to get that byte from src,
|
||||||
|
// so instead invent a space byte.
|
||||||
|
if (v == scanEndObject || v == scanEndArray) && dec.scan.step(&dec.scan, ' ') == scanEnd {
|
||||||
|
scanp += i + 1
|
||||||
|
break Input
|
||||||
|
}
|
||||||
|
if v == scanError {
|
||||||
|
dec.err = dec.scan.err
|
||||||
|
return 0, dec.scan.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scanp = len(dec.buf)
|
||||||
|
|
||||||
|
// Did the last read have an error?
|
||||||
|
// Delayed until now to allow buffer scan.
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
if dec.scan.step(&dec.scan, ' ') == scanEnd {
|
||||||
|
break Input
|
||||||
|
}
|
||||||
|
if nonSpace(dec.buf) {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dec.err = err
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
n := scanp - dec.scanp
|
||||||
|
err = dec.refill()
|
||||||
|
scanp = dec.scanp + n
|
||||||
|
}
|
||||||
|
return scanp - dec.scanp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dec *Decoder) refill() error {
|
||||||
|
// Make room to read more into the buffer.
|
||||||
|
// First slide down data already consumed.
|
||||||
|
if dec.scanp > 0 {
|
||||||
|
n := copy(dec.buf, dec.buf[dec.scanp:])
|
||||||
|
dec.buf = dec.buf[:n]
|
||||||
|
dec.scanp = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grow buffer if not large enough.
|
||||||
|
const minRead = 512
|
||||||
|
if cap(dec.buf)-len(dec.buf) < minRead {
|
||||||
|
newBuf := make([]byte, len(dec.buf), 2*cap(dec.buf)+minRead)
|
||||||
|
copy(newBuf, dec.buf)
|
||||||
|
dec.buf = newBuf
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read. Delay error for next iteration (after scan).
|
||||||
|
n, err := dec.r.Read(dec.buf[len(dec.buf):cap(dec.buf)])
|
||||||
|
dec.buf = dec.buf[0 : len(dec.buf)+n]
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func nonSpace(b []byte) bool {
|
||||||
|
for _, c := range b {
|
||||||
|
if !isSpace(c) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// An Encoder writes JSON objects to an output stream.
|
||||||
|
type Encoder struct {
|
||||||
|
w io.Writer
|
||||||
|
err error
|
||||||
|
canonical bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEncoder returns a new encoder that writes to w.
|
||||||
|
func NewEncoder(w io.Writer) *Encoder {
|
||||||
|
return &Encoder{w: w}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Canonical causes the encoder to switch to Canonical JSON mode.
|
||||||
|
// Read more at: http://wiki.laptop.org/go/Canonical_JSON
|
||||||
|
func (enc *Encoder) Canonical() { enc.canonical = true }
|
||||||
|
|
||||||
|
// Encode writes the JSON encoding of v to the stream,
|
||||||
|
// followed by a newline character.
|
||||||
|
//
|
||||||
|
// See the documentation for Marshal for details about the
|
||||||
|
// conversion of Go values to JSON.
|
||||||
|
func (enc *Encoder) Encode(v interface{}) error {
|
||||||
|
if enc.err != nil {
|
||||||
|
return enc.err
|
||||||
|
}
|
||||||
|
e := newEncodeState(enc.canonical)
|
||||||
|
err := e.marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !enc.canonical {
|
||||||
|
// Terminate each value with a newline.
|
||||||
|
// This makes the output look a little nicer
|
||||||
|
// when debugging, and some kind of space
|
||||||
|
// is required if the encoded value was a number,
|
||||||
|
// so that the reader knows there aren't more
|
||||||
|
// digits coming.
|
||||||
|
e.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = enc.w.Write(e.Bytes()); err != nil {
|
||||||
|
enc.err = err
|
||||||
|
}
|
||||||
|
encodeStatePool.Put(e)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawMessage is a raw encoded JSON object.
|
||||||
|
// It implements Marshaler and Unmarshaler and can
|
||||||
|
// be used to delay JSON decoding or precompute a JSON encoding.
|
||||||
|
type RawMessage []byte
|
||||||
|
|
||||||
|
// MarshalJSON returns *m as the JSON encoding of m.
|
||||||
|
func (m *RawMessage) MarshalJSON() ([]byte, error) {
|
||||||
|
return *m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON sets *m to a copy of data.
|
||||||
|
func (m *RawMessage) UnmarshalJSON(data []byte) error {
|
||||||
|
if m == nil {
|
||||||
|
return errors.New("json.RawMessage: UnmarshalJSON on nil pointer")
|
||||||
|
}
|
||||||
|
*m = append((*m)[0:0], data...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Marshaler = (*RawMessage)(nil)
|
||||||
|
var _ Unmarshaler = (*RawMessage)(nil)
|
||||||
|
|
||||||
|
// A Token holds a value of one of these types:
|
||||||
|
//
|
||||||
|
// Delim, for the four JSON delimiters [ ] { }
|
||||||
|
// bool, for JSON booleans
|
||||||
|
// float64, for JSON numbers
|
||||||
|
// Number, for JSON numbers
|
||||||
|
// string, for JSON string literals
|
||||||
|
// nil, for JSON null
|
||||||
|
//
|
||||||
|
type Token interface{}
|
||||||
|
|
||||||
|
const (
|
||||||
|
tokenTopValue = iota
|
||||||
|
tokenArrayStart
|
||||||
|
tokenArrayValue
|
||||||
|
tokenArrayComma
|
||||||
|
tokenObjectStart
|
||||||
|
tokenObjectKey
|
||||||
|
tokenObjectColon
|
||||||
|
tokenObjectValue
|
||||||
|
tokenObjectComma
|
||||||
|
)
|
||||||
|
|
||||||
|
// advance tokenstate from a separator state to a value state
|
||||||
|
func (dec *Decoder) tokenPrepareForDecode() error {
|
||||||
|
// Note: Not calling peek before switch, to avoid
|
||||||
|
// putting peek into the standard Decode path.
|
||||||
|
// peek is only called when using the Token API.
|
||||||
|
switch dec.tokenState {
|
||||||
|
case tokenArrayComma:
|
||||||
|
c, err := dec.peek()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if c != ',' {
|
||||||
|
return &SyntaxError{"expected comma after array element", 0}
|
||||||
|
}
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenState = tokenArrayValue
|
||||||
|
case tokenObjectColon:
|
||||||
|
c, err := dec.peek()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if c != ':' {
|
||||||
|
return &SyntaxError{"expected colon after object key", 0}
|
||||||
|
}
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenState = tokenObjectValue
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dec *Decoder) tokenValueAllowed() bool {
|
||||||
|
switch dec.tokenState {
|
||||||
|
case tokenTopValue, tokenArrayStart, tokenArrayValue, tokenObjectValue:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dec *Decoder) tokenValueEnd() {
|
||||||
|
switch dec.tokenState {
|
||||||
|
case tokenArrayStart, tokenArrayValue:
|
||||||
|
dec.tokenState = tokenArrayComma
|
||||||
|
case tokenObjectValue:
|
||||||
|
dec.tokenState = tokenObjectComma
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Delim is a JSON array or object delimiter, one of [ ] { or }.
|
||||||
|
type Delim rune
|
||||||
|
|
||||||
|
func (d Delim) String() string {
|
||||||
|
return string(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token returns the next JSON token in the input stream.
|
||||||
|
// At the end of the input stream, Token returns nil, io.EOF.
|
||||||
|
//
|
||||||
|
// Token guarantees that the delimiters [ ] { } it returns are
|
||||||
|
// properly nested and matched: if Token encounters an unexpected
|
||||||
|
// delimiter in the input, it will return an error.
|
||||||
|
//
|
||||||
|
// The input stream consists of basic JSON values—bool, string,
|
||||||
|
// number, and null—along with delimiters [ ] { } of type Delim
|
||||||
|
// to mark the start and end of arrays and objects.
|
||||||
|
// Commas and colons are elided.
|
||||||
|
func (dec *Decoder) Token() (Token, error) {
|
||||||
|
for {
|
||||||
|
c, err := dec.peek()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch c {
|
||||||
|
case '[':
|
||||||
|
if !dec.tokenValueAllowed() {
|
||||||
|
return dec.tokenError(c)
|
||||||
|
}
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenStack = append(dec.tokenStack, dec.tokenState)
|
||||||
|
dec.tokenState = tokenArrayStart
|
||||||
|
return Delim('['), nil
|
||||||
|
|
||||||
|
case ']':
|
||||||
|
if dec.tokenState != tokenArrayStart && dec.tokenState != tokenArrayComma {
|
||||||
|
return dec.tokenError(c)
|
||||||
|
}
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenState = dec.tokenStack[len(dec.tokenStack)-1]
|
||||||
|
dec.tokenStack = dec.tokenStack[:len(dec.tokenStack)-1]
|
||||||
|
dec.tokenValueEnd()
|
||||||
|
return Delim(']'), nil
|
||||||
|
|
||||||
|
case '{':
|
||||||
|
if !dec.tokenValueAllowed() {
|
||||||
|
return dec.tokenError(c)
|
||||||
|
}
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenStack = append(dec.tokenStack, dec.tokenState)
|
||||||
|
dec.tokenState = tokenObjectStart
|
||||||
|
return Delim('{'), nil
|
||||||
|
|
||||||
|
case '}':
|
||||||
|
if dec.tokenState != tokenObjectStart && dec.tokenState != tokenObjectComma {
|
||||||
|
return dec.tokenError(c)
|
||||||
|
}
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenState = dec.tokenStack[len(dec.tokenStack)-1]
|
||||||
|
dec.tokenStack = dec.tokenStack[:len(dec.tokenStack)-1]
|
||||||
|
dec.tokenValueEnd()
|
||||||
|
return Delim('}'), nil
|
||||||
|
|
||||||
|
case ':':
|
||||||
|
if dec.tokenState != tokenObjectColon {
|
||||||
|
return dec.tokenError(c)
|
||||||
|
}
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenState = tokenObjectValue
|
||||||
|
continue
|
||||||
|
|
||||||
|
case ',':
|
||||||
|
if dec.tokenState == tokenArrayComma {
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenState = tokenArrayValue
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if dec.tokenState == tokenObjectComma {
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenState = tokenObjectKey
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return dec.tokenError(c)
|
||||||
|
|
||||||
|
case '"':
|
||||||
|
if dec.tokenState == tokenObjectStart || dec.tokenState == tokenObjectKey {
|
||||||
|
var x string
|
||||||
|
old := dec.tokenState
|
||||||
|
dec.tokenState = tokenTopValue
|
||||||
|
err := dec.Decode(&x)
|
||||||
|
dec.tokenState = old
|
||||||
|
if err != nil {
|
||||||
|
clearOffset(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dec.tokenState = tokenObjectColon
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
default:
|
||||||
|
if !dec.tokenValueAllowed() {
|
||||||
|
return dec.tokenError(c)
|
||||||
|
}
|
||||||
|
var x interface{}
|
||||||
|
if err := dec.Decode(&x); err != nil {
|
||||||
|
clearOffset(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearOffset(err error) {
|
||||||
|
if s, ok := err.(*SyntaxError); ok {
|
||||||
|
s.Offset = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dec *Decoder) tokenError(c byte) (Token, error) {
|
||||||
|
var context string
|
||||||
|
switch dec.tokenState {
|
||||||
|
case tokenTopValue:
|
||||||
|
context = " looking for beginning of value"
|
||||||
|
case tokenArrayStart, tokenArrayValue, tokenObjectValue:
|
||||||
|
context = " looking for beginning of value"
|
||||||
|
case tokenArrayComma:
|
||||||
|
context = " after array element"
|
||||||
|
case tokenObjectKey:
|
||||||
|
context = " looking for beginning of object key string"
|
||||||
|
case tokenObjectColon:
|
||||||
|
context = " after object key"
|
||||||
|
case tokenObjectComma:
|
||||||
|
context = " after object key:value pair"
|
||||||
|
}
|
||||||
|
return nil, &SyntaxError{"invalid character " + quoteChar(c) + " " + context, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// More reports whether there is another element in the
|
||||||
|
// current array or object being parsed.
|
||||||
|
func (dec *Decoder) More() bool {
|
||||||
|
c, err := dec.peek()
|
||||||
|
return err == nil && c != ']' && c != '}'
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dec *Decoder) peek() (byte, error) {
|
||||||
|
var err error
|
||||||
|
for {
|
||||||
|
for i := dec.scanp; i < len(dec.buf); i++ {
|
||||||
|
c := dec.buf[i]
|
||||||
|
if isSpace(c) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dec.scanp = i
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
// buffer has been scanned, now report any error
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
err = dec.refill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO
|
||||||
|
|
||||||
|
// EncodeToken writes the given JSON token to the stream.
|
||||||
|
// It returns an error if the delimiters [ ] { } are not properly used.
|
||||||
|
//
|
||||||
|
// EncodeToken does not call Flush, because usually it is part of
|
||||||
|
// a larger operation such as Encode, and those will call Flush when finished.
|
||||||
|
// Callers that create an Encoder and then invoke EncodeToken directly,
|
||||||
|
// without using Encode, need to call Flush when finished to ensure that
|
||||||
|
// the JSON is written to the underlying writer.
|
||||||
|
func (e *Encoder) EncodeToken(t Token) error {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
44
src/vendor/github.com/docker/go/canonical/json/tags.go
generated
vendored
Normal file
44
src/vendor/github.com/docker/go/canonical/json/tags.go
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// Copyright 2011 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 json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// tagOptions is the string following a comma in a struct field's "json"
|
||||||
|
// tag, or the empty string. It does not include the leading comma.
|
||||||
|
type tagOptions string
|
||||||
|
|
||||||
|
// parseTag splits a struct field's json tag into its name and
|
||||||
|
// comma-separated options.
|
||||||
|
func parseTag(tag string) (string, tagOptions) {
|
||||||
|
if idx := strings.Index(tag, ","); idx != -1 {
|
||||||
|
return tag[:idx], tagOptions(tag[idx+1:])
|
||||||
|
}
|
||||||
|
return tag, tagOptions("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains reports whether a comma-separated list of options
|
||||||
|
// contains a particular substr flag. substr must be surrounded by a
|
||||||
|
// string boundary or commas.
|
||||||
|
func (o tagOptions) Contains(optionName string) bool {
|
||||||
|
if len(o) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
s := string(o)
|
||||||
|
for s != "" {
|
||||||
|
var next string
|
||||||
|
i := strings.Index(s, ",")
|
||||||
|
if i >= 0 {
|
||||||
|
s, next = s[:i], s[i+1:]
|
||||||
|
}
|
||||||
|
if s == optionName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
s = next
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
99
src/vendor/github.com/docker/notary/CHANGELOG.md
generated
vendored
Normal file
99
src/vendor/github.com/docker/notary/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [v0.5.0](https://github.com/docker/notary/releases/tag/v0.5.0) 11/14/2016
|
||||||
|
+ Non-certificate public keys in PEM format can now be added to delegation roles [#965](https://github.com/docker/notary/pull/965)
|
||||||
|
+ PostgreSQL support as a storage backend for Server and Signer [#920](https://github.com/docker/notary/pull/920)
|
||||||
|
+ Notary server's health check now fails if it cannot connect to the signer, since no new repositories can be created and existing repositories cannot be updated if the server cannot reach the signer [#952](https://github.com/docker/notary/pull/952)
|
||||||
|
+ Server runs its connectivity healthcheck to the server once every 10 seconds instead of once every minute. [#902](https://github.com/docker/notary/pull/902)
|
||||||
|
+ The keys on disk are now stored in the `~/.notary/private` directory, rather than in a key hierarchy that separates them by GUN and by role. Notary will automatically migrate old-style directory layouts to the new style. **This is not forwards-compatible against notary<0.4.2 and docker<=1.12** [#872](https://github.com/docker/notary/pull/872)
|
||||||
|
+ A new changefeed API has been added to Notary Server. It is only supported when using one of the relational database backends: MySQL, PostgreSQL, or SQLite.[#1019](https://github.com/docker/notary/pull/1019)
|
||||||
|
|
||||||
|
## [v0.4.3](https://github.com/docker/notary/releases/tag/v0.4.3) 1/3/2017
|
||||||
|
+ Fix build tags for static notary client binaries in linux [#1039](https://github.com/docker/notary/pull/1039)
|
||||||
|
+ Fix key import for exported delegation keys [#1067](https://github.com/docker/notary/pull/1067)
|
||||||
|
|
||||||
|
## [v0.4.2](https://github.com/docker/notary/releases/tag/v0.4.2) 9/30/2016
|
||||||
|
+ Bump the cross compiler to golang 1.7.1, since [1.6.3 builds binaries that could have non-deterministic bugs in OS X Sierra](https://groups.google.com/forum/#!msg/golang-dev/Jho5sBHZgAg/cq6d97S1AwAJ) [#984](https://github.com/docker/notary/pull/984)
|
||||||
|
|
||||||
|
## [v0.4.1](https://github.com/docker/notary/releases/tag/v0.4.1) 9/27/2016
|
||||||
|
+ Preliminary Windows support for notary client [#970](https://github.com/docker/notary/pull/970)
|
||||||
|
+ Output message to CLI when repo changes have been successfully published [#974](https://github.com/docker/notary/pull/974)
|
||||||
|
+ Improved error messages for client authentication errors and for the witness command [#972](https://github.com/docker/notary/pull/972)
|
||||||
|
+ Support for finding keys that are anywhere in the notary directory's "private" directory, not just under "private/root_keys" or "private/tuf_keys" [#981](https://github.com/docker/notary/pull/981)
|
||||||
|
+ Previously, on any error updating, the client would fall back on the cache. Now we only do so if there is a network error or if the server is unavailable or missing the TUF data. Invalid TUF data will cause the update to fail - for example if there was an invalid root rotation. [#884](https://github.com/docker/notary/pull/884) [#982](https://github.com/docker/notary/pull/982)
|
||||||
|
|
||||||
|
## [v0.4.0](https://github.com/docker/notary/releases/tag/v0.4.0) 9/21/2016
|
||||||
|
+ Server-managed key rotations [#889](https://github.com/docker/notary/pull/889)
|
||||||
|
+ Remove `timestamp_keys` table, which stored redundant information [#889](https://github.com/docker/notary/pull/889)
|
||||||
|
+ Introduce `notary delete` command to delete local and/or remote repo data [#895](https://github.com/docker/notary/pull/895)
|
||||||
|
+ Introduce `notary witness` command to stage signatures for specified roles [#875](https://github.com/docker/notary/pull/875)
|
||||||
|
+ Add `-p` flag to offline commands to attempt auto-publish [#886](https://github.com/docker/notary/pull/886) [#912](https://github.com/docker/notary/pull/912) [#923](https://github.com/docker/notary/pull/923)
|
||||||
|
+ Introduce `notary reset` command to manage staged changes [#959](https://github.com/docker/notary/pull/959) [#856](https://github.com/docker/notary/pull/856)
|
||||||
|
+ Add `--rootkey` flag to `notary init` to provide a private root key for a repo [#801](https://github.com/docker/notary/pull/801)
|
||||||
|
+ Introduce `notary delegation purge` command to remove a specified key from all delegations [#855](https://github.com/docker/notary/pull/855)
|
||||||
|
+ Removed HTTP endpoint from notary-signer [#870](https://github.com/docker/notary/pull/870)
|
||||||
|
+ Refactored and unified key storage [#825](https://github.com/docker/notary/pull/825)
|
||||||
|
+ Batched key import and export now operate on PEM files (potentially with multiple blocks) instead of ZIP [#825](https://github.com/docker/notary/pull/825) [#882](https://github.com/docker/notary/pull/882)
|
||||||
|
+ Add full database integration test-suite [#824](https://github.com/docker/notary/pull/824) [#854](https://github.com/docker/notary/pull/854) [#863](https://github.com/docker/notary/pull/863)
|
||||||
|
+ Improve notary-server, trust pinning, and yubikey logging [#798](https://github.com/docker/notary/pull/798) [#858](https://github.com/docker/notary/pull/858) [#891](https://github.com/docker/notary/pull/891)
|
||||||
|
+ Warn if certificates for root or delegations are near expiry [#802](https://github.com/docker/notary/pull/802)
|
||||||
|
+ Warn if role metadata is near expiry [#786](https://github.com/docker/notary/pull/786)
|
||||||
|
+ Reformat CLI table output to use the `text/tabwriter` package [#809](https://github.com/docker/notary/pull/809)
|
||||||
|
+ Fix passphrase retrieval attempt counting and terminal detection [#906](https://github.com/docker/notary/pull/906)
|
||||||
|
+ Fix listing nested delegations [#864](https://github.com/docker/notary/pull/864)
|
||||||
|
+ Bump go version to 1.6.3, fix go1.7 compatibility [#851](https://github.com/docker/notary/pull/851) [#793](https://github.com/docker/notary/pull/793)
|
||||||
|
+ Convert docker-compose files to v2 format [#755](https://github.com/docker/notary/pull/755)
|
||||||
|
+ Validate root rotations against trust pinning [#800](https://github.com/docker/notary/pull/800)
|
||||||
|
+ Update fixture certificates for two-year expiry window [#951](https://github.com/docker/notary/pull/951)
|
||||||
|
|
||||||
|
## [v0.3.0](https://github.com/docker/notary/releases/tag/v0.3.0) 5/11/2016
|
||||||
|
+ Root rotations
|
||||||
|
+ RethinkDB support as a storage backend for Server and Signer
|
||||||
|
+ A new TUF repo builder that merges server and client validation
|
||||||
|
+ Trust Pinning: configure known good key IDs and CAs to replace TOFU.
|
||||||
|
+ Add --input, --output, and --quiet flags to notary verify command
|
||||||
|
+ Remove local certificate store. It was redundant as all certs were also stored in the cached root.json
|
||||||
|
+ Cleanup of dead code in client side key storage logic
|
||||||
|
+ Update project to Go 1.6.1
|
||||||
|
+ Reorganize vendoring to meet Go 1.6+ standard. Still using Godeps to manage vendored packages
|
||||||
|
+ Add targets by hash, no longer necessary to have the original target data available
|
||||||
|
+ Active Key ID verification during signature verification
|
||||||
|
+ Switch all testing from assert to require, reduces noise in test runs
|
||||||
|
+ Use alpine based images for smaller downloads and faster setup times
|
||||||
|
+ Clean up out of data signatures when re-signing content
|
||||||
|
+ Set cache control headers on HTTP responses from Notary Server
|
||||||
|
+ Add sha512 support for targets
|
||||||
|
+ Add environment variable for delegation key passphrase
|
||||||
|
+ Reduce permissions requested by client from token server
|
||||||
|
+ Update formatting for delegation list output
|
||||||
|
+ Move SQLite dependency to tests only so it doesn't get built into official images
|
||||||
|
+ Fixed asking for password to list private repositories
|
||||||
|
+ Enable using notary client with username/password in a scripted fashion
|
||||||
|
+ Fix static compilation of client
|
||||||
|
+ Enforce TUF version to be >= 1, previously 0 was acceptable although unused
|
||||||
|
+ json.RawMessage should always be used as *json.RawMessage due to concepts of addressability in Go and effects on encoding
|
||||||
|
|
||||||
|
## [v0.2](https://github.com/docker/notary/releases/tag/v0.2.0) 2/24/2016
|
||||||
|
+ Add support for delegation roles in `notary` server and client
|
||||||
|
+ Add `notary CLI` commands for managing delegation roles: `notary delegation`
|
||||||
|
+ `add`, `list` and `remove` subcommands
|
||||||
|
+ Enhance `notary CLI` commands for adding targets to delegation roles
|
||||||
|
+ `notary add --roles` and `notary remove --roles` to manipulate targets for delegations
|
||||||
|
+ Support for rotating the snapshot key to one managed by the `notary` server
|
||||||
|
+ Add consistent download functionality to download metadata and content by checksum
|
||||||
|
+ Update `docker-compose` configuration to use official mariadb image
|
||||||
|
+ deprecate `notarymysql`
|
||||||
|
+ default to using a volume for `data` directory
|
||||||
|
+ use separate databases for `notary-server` and `notary-signer` with separate users
|
||||||
|
+ Add `notary CLI` command for changing private key passphrases: `notary key passwd`
|
||||||
|
+ Enhance `notary CLI` commands for importing and exporting keys
|
||||||
|
+ Change default `notary CLI` log level to fatal, introduce new verbose (error-level) and debug-level settings
|
||||||
|
+ Store roles as PEM headers in private keys, incompatible with previous notary v0.1 key format
|
||||||
|
+ No longer store keys as `<KEY_ID>_role.key`, instead store as `<KEY_ID>.key`; new private keys from new notary clients will crash old notary clients
|
||||||
|
+ Support logging as JSON format on server and signer
|
||||||
|
+ Support mutual TLS between notary client and notary server
|
||||||
|
|
||||||
|
## [v0.1](https://github.com/docker/notary/releases/tag/v0.1) 11/15/2015
|
||||||
|
+ Initial non-alpha `notary` version
|
||||||
|
+ Implement TUF (the update framework) with support for root, targets, snapshot, and timestamp roles
|
||||||
|
+ Add PKCS11 interface to store and sign with keys in HSMs (i.e. Yubikey)
|
95
src/vendor/github.com/docker/notary/CONTRIBUTING.md
generated
vendored
Normal file
95
src/vendor/github.com/docker/notary/CONTRIBUTING.md
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# Contributing to notary
|
||||||
|
|
||||||
|
## Before reporting an issue...
|
||||||
|
|
||||||
|
### If your problem is with...
|
||||||
|
|
||||||
|
- automated builds
|
||||||
|
- your account on the [Docker Hub](https://hub.docker.com/)
|
||||||
|
- any other [Docker Hub](https://hub.docker.com/) issue
|
||||||
|
|
||||||
|
Then please do not report your issue here - you should instead report it to [https://support.docker.com](https://support.docker.com)
|
||||||
|
|
||||||
|
### If you...
|
||||||
|
|
||||||
|
- need help setting up notary
|
||||||
|
- can't figure out something
|
||||||
|
- are not sure what's going on or what your problem is
|
||||||
|
|
||||||
|
Then please do not open an issue here yet - you should first try one of the following support forums:
|
||||||
|
|
||||||
|
- irc: #docker-trust on freenode
|
||||||
|
|
||||||
|
## Reporting an issue properly
|
||||||
|
|
||||||
|
By following these simple rules you will get better and faster feedback on your issue.
|
||||||
|
|
||||||
|
- search the bugtracker for an already reported issue
|
||||||
|
|
||||||
|
### If you found an issue that describes your problem:
|
||||||
|
|
||||||
|
- please read other user comments first, and confirm this is the same issue: a given error condition might be indicative of different problems - you may also find a workaround in the comments
|
||||||
|
- please refrain from adding "same thing here" or "+1" comments
|
||||||
|
- you don't need to comment on an issue to get notified of updates: just hit the "subscribe" button
|
||||||
|
- comment if you have some new, technical and relevant information to add to the case
|
||||||
|
|
||||||
|
### If you have not found an existing issue that describes your problem:
|
||||||
|
|
||||||
|
1. create a new issue, with a succinct title that describes your issue:
|
||||||
|
- bad title: "It doesn't work with my docker"
|
||||||
|
- good title: "Publish fail: 400 error with E_INVALID_DIGEST"
|
||||||
|
2. copy the output of:
|
||||||
|
- `notary version` or `docker version`
|
||||||
|
3. Run `notary` or `docker` with the `-D` option for debug output, and please include a copy of the command and the output.
|
||||||
|
4. If relevant, copy your `notaryserver` and `notarysigner` logs that show the error (this is likely the output from running `docker-compose up`)
|
||||||
|
|
||||||
|
## Contributing a patch for a known bug, or a small correction
|
||||||
|
|
||||||
|
You should follow the basic GitHub workflow:
|
||||||
|
|
||||||
|
1. fork
|
||||||
|
2. commit a change
|
||||||
|
3. make sure the tests pass
|
||||||
|
4. PR
|
||||||
|
|
||||||
|
Additionally, you must [sign your commits](https://github.com/docker/docker/blob/master/CONTRIBUTING.md#sign-your-work). It's very simple:
|
||||||
|
|
||||||
|
- configure your name with git: `git config user.name "Real Name" && git config user.email mail@example.com`
|
||||||
|
- sign your commits using `-s`: `git commit -s -m "My commit"`
|
||||||
|
|
||||||
|
Some simple rules to ensure quick merge:
|
||||||
|
|
||||||
|
- clearly point to the issue(s) you want to fix in your PR comment (e.g., `closes #12345`)
|
||||||
|
- prefer multiple (smaller) PRs addressing individual issues over a big one trying to address multiple issues at once
|
||||||
|
- if you need to amend your PR following comments, please squash instead of adding more commits
|
||||||
|
- if fixing a bug or adding a feature, please add or update the relevant `CHANGELOG.md` entry with your pull request number
|
||||||
|
and a description of the change
|
||||||
|
|
||||||
|
## Contributing new features
|
||||||
|
|
||||||
|
You are heavily encouraged to first discuss what you want to do. You can do so on the irc channel, or by opening an issue that clearly describes the use case you want to fulfill, or the problem you are trying to solve.
|
||||||
|
|
||||||
|
If this is a major new feature, you should then submit a proposal that describes your technical solution and reasoning.
|
||||||
|
If you did discuss it first, this will likely be greenlighted very fast. It's advisable to address all feedback on this proposal before starting actual work
|
||||||
|
|
||||||
|
Then you should submit your implementation, clearly linking to the issue (and possible proposal).
|
||||||
|
|
||||||
|
Your PR will be reviewed by the community, then ultimately by the project maintainers, before being merged.
|
||||||
|
|
||||||
|
It's mandatory to:
|
||||||
|
|
||||||
|
- interact respectfully with other community members and maintainers - more generally, you are expected to abide by the [Docker community rules](https://github.com/docker/docker/blob/master/CONTRIBUTING.md#docker-community-guidelines)
|
||||||
|
- address maintainers' comments and modify your submission accordingly
|
||||||
|
- write tests for any new code
|
||||||
|
|
||||||
|
Complying to these simple rules will greatly accelerate the review process, and will ensure you have a pleasant experience in contributing code to the Registry.
|
||||||
|
|
||||||
|
## Review and Development notes
|
||||||
|
|
||||||
|
- All merges require LGTMs from any 2 maintainers.
|
||||||
|
- We use the git flow model (as best we can) using the `releases` branch as the stable branch, and the `master` branch as the development branch. When we get near a potential release, a release branch (`release/<semver>`) will be created from `master`. Any PRs that should go into the release should be made against that branch. Hotfixes for a minor release will be added to the branch `hotfix/<semver>`.
|
||||||
|
|
||||||
|
## Vendoring new dependency versions
|
||||||
|
|
||||||
|
We use [VNDR](https://github.com/LK4D4/vndr); please update `vendor.conf` with the new dependency or the new version, and run
|
||||||
|
`vndr <top level package name>`.
|
4
src/vendor/github.com/docker/notary/CONTRIBUTORS
generated
vendored
Normal file
4
src/vendor/github.com/docker/notary/CONTRIBUTORS
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
David Williamson <david.williamson@docker.com> (github: davidwilliamson)
|
||||||
|
Aaron Lehmann <aaron.lehmann@docker.com> (github: aaronlehmann)
|
||||||
|
Lewis Marshall <lewis@flynn.io> (github: lmars)
|
||||||
|
Jonathan Rudenberg <jonathan@flynn.io> (github: titanous)
|
25
src/vendor/github.com/docker/notary/Dockerfile
generated
vendored
Normal file
25
src/vendor/github.com/docker/notary/Dockerfile
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
FROM golang:1.7.3
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
curl \
|
||||||
|
clang \
|
||||||
|
libltdl-dev \
|
||||||
|
libsqlite3-dev \
|
||||||
|
patch \
|
||||||
|
tar \
|
||||||
|
xz-utils \
|
||||||
|
python \
|
||||||
|
python-pip \
|
||||||
|
--no-install-recommends \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN useradd -ms /bin/bash notary \
|
||||||
|
&& pip install codecov \
|
||||||
|
&& go get github.com/golang/lint/golint github.com/fzipp/gocyclo github.com/client9/misspell/cmd/misspell github.com/gordonklaus/ineffassign github.com/HewlettPackard/gas
|
||||||
|
|
||||||
|
ENV NOTARYDIR /go/src/github.com/docker/notary
|
||||||
|
|
||||||
|
COPY . ${NOTARYDIR}
|
||||||
|
RUN chmod -R a+rw /go
|
||||||
|
|
||||||
|
WORKDIR ${NOTARYDIR}
|
8
src/vendor/github.com/docker/notary/Jenkinsfile
generated
vendored
Normal file
8
src/vendor/github.com/docker/notary/Jenkinsfile
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Only run on Linux atm
|
||||||
|
wrappedNode(label: 'docker') {
|
||||||
|
deleteDir()
|
||||||
|
stage "checkout"
|
||||||
|
checkout scm
|
||||||
|
|
||||||
|
documentationChecker("docs")
|
||||||
|
}
|
201
src/vendor/github.com/docker/notary/LICENSE
generated
vendored
Normal file
201
src/vendor/github.com/docker/notary/LICENSE
generated
vendored
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2015 Docker, Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
70
src/vendor/github.com/docker/notary/MAINTAINERS
generated
vendored
Normal file
70
src/vendor/github.com/docker/notary/MAINTAINERS
generated
vendored
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# Notary maintainers file
|
||||||
|
#
|
||||||
|
# This file describes who runs the docker/notary project and how.
|
||||||
|
# This is a living document - if you see something out of date or missing, speak up!
|
||||||
|
#
|
||||||
|
# It is structured to be consumable by both humans and programs.
|
||||||
|
# To extract its contents programmatically, use any TOML-compliant parser.
|
||||||
|
#
|
||||||
|
# This file is compiled into the MAINTAINERS file in docker/opensource.
|
||||||
|
#
|
||||||
|
[Org]
|
||||||
|
[Org."Core maintainers"]
|
||||||
|
people = [
|
||||||
|
"cyli",
|
||||||
|
"diogomonica",
|
||||||
|
"dmcgowan",
|
||||||
|
"endophage",
|
||||||
|
"ecordell",
|
||||||
|
"hukeping",
|
||||||
|
"nathanmccauley",
|
||||||
|
"riyazdf",
|
||||||
|
]
|
||||||
|
|
||||||
|
[people]
|
||||||
|
|
||||||
|
# A reference list of all people associated with the project.
|
||||||
|
# All other sections should refer to people by their canonical key
|
||||||
|
# in the people section.
|
||||||
|
|
||||||
|
# ADD YOURSELF HERE IN ALPHABETICAL ORDER
|
||||||
|
|
||||||
|
[people.cyli]
|
||||||
|
Name = "Ying Li"
|
||||||
|
Email = "ying.li@docker.com"
|
||||||
|
GitHub = "cyli"
|
||||||
|
|
||||||
|
[people.diogomonica]
|
||||||
|
Name = "Diogo Monica"
|
||||||
|
Email = "diogo@docker.com"
|
||||||
|
GitHub = "diogomonica"
|
||||||
|
|
||||||
|
[people.dmcgowan]
|
||||||
|
Name = "Derek McGowan"
|
||||||
|
Email = "derek@docker.com"
|
||||||
|
GitHub = "dmcgowan"
|
||||||
|
|
||||||
|
[people.endophage]
|
||||||
|
Name = "David Lawrence"
|
||||||
|
Email = "david.lawrence@docker.com"
|
||||||
|
GitHub = "endophage"
|
||||||
|
|
||||||
|
[people.ecordell]
|
||||||
|
Name = "Evan Cordell"
|
||||||
|
Email = "evan.cordell@coreos.com"
|
||||||
|
GitHub = "ecordell"
|
||||||
|
|
||||||
|
[people.hukeping]
|
||||||
|
Name = "Hu Keping"
|
||||||
|
Email = "hukeping@huawei.com"
|
||||||
|
GitHub = "hukeping"
|
||||||
|
|
||||||
|
[people.nathanmccauley]
|
||||||
|
Name = "Nathan McCauley"
|
||||||
|
Email = "nathan.mccauley@docker.com"
|
||||||
|
GitHub = "nathanmccauley"
|
||||||
|
|
||||||
|
[people.riyazdf]
|
||||||
|
Name = "Riyaz Faizullabhoy"
|
||||||
|
Email = "riyaz@docker.com"
|
||||||
|
GitHub = "riyazdf"
|
213
src/vendor/github.com/docker/notary/Makefile
generated
vendored
Normal file
213
src/vendor/github.com/docker/notary/Makefile
generated
vendored
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
# Set an output prefix, which is the local directory if not specified
|
||||||
|
PREFIX?=$(shell pwd)
|
||||||
|
|
||||||
|
# Populate version variables
|
||||||
|
# Add to compile time flags
|
||||||
|
NOTARY_PKG := github.com/docker/notary
|
||||||
|
NOTARY_VERSION := $(shell cat NOTARY_VERSION)
|
||||||
|
GITCOMMIT := $(shell git rev-parse --short HEAD)
|
||||||
|
GITUNTRACKEDCHANGES := $(shell git status --porcelain --untracked-files=no)
|
||||||
|
ifneq ($(GITUNTRACKEDCHANGES),)
|
||||||
|
GITCOMMIT := $(GITCOMMIT)-dirty
|
||||||
|
endif
|
||||||
|
CTIMEVAR=-X $(NOTARY_PKG)/version.GitCommit=$(GITCOMMIT) -X $(NOTARY_PKG)/version.NotaryVersion=$(NOTARY_VERSION)
|
||||||
|
GO_LDFLAGS=-ldflags "-w $(CTIMEVAR)"
|
||||||
|
GO_LDFLAGS_STATIC=-ldflags "-w $(CTIMEVAR) -extldflags -static"
|
||||||
|
GOOSES = darwin linux windows
|
||||||
|
NOTARY_BUILDTAGS ?= pkcs11
|
||||||
|
NOTARYDIR := /go/src/github.com/docker/notary
|
||||||
|
|
||||||
|
GO_VERSION := $(shell go version | grep "1\.[7-9]\(\.[0-9]+\)*\|devel")
|
||||||
|
# check to make sure we have the right version. development versions of Go are
|
||||||
|
# not officially supported, but allowed for building
|
||||||
|
|
||||||
|
ifeq ($(strip $(GO_VERSION))$(SKIPENVCHECK),)
|
||||||
|
$(error Bad Go version - please install Go >= 1.7)
|
||||||
|
endif
|
||||||
|
|
||||||
|
# check to be sure pkcs11 lib is always imported with a build tag
|
||||||
|
GO_LIST_PKCS11 := $(shell go list -tags "${NOTARY_BUILDTAGS}" -e -f '{{join .Deps "\n"}}' ./... | grep -v /vendor/ | xargs go list -e -f '{{if not .Standard}}{{.ImportPath}}{{end}}' | grep -q pkcs11)
|
||||||
|
ifeq ($(GO_LIST_PKCS11),)
|
||||||
|
$(info pkcs11 import was not found anywhere without a build tag, yay)
|
||||||
|
else
|
||||||
|
$(error You are importing pkcs11 somewhere and not using a build tag)
|
||||||
|
endif
|
||||||
|
|
||||||
|
_empty :=
|
||||||
|
_space := $(empty) $(empty)
|
||||||
|
|
||||||
|
# go cover test variables
|
||||||
|
COVERPROFILE?=coverage.txt
|
||||||
|
COVERMODE=atomic
|
||||||
|
PKGS ?= $(shell go list -tags "${NOTARY_BUILDTAGS}" ./... | grep -v /vendor/ | tr '\n' ' ')
|
||||||
|
|
||||||
|
.PHONY: clean all lint build test binaries cross cover docker-images notary-dockerfile
|
||||||
|
.DELETE_ON_ERROR: cover
|
||||||
|
.DEFAULT: default
|
||||||
|
|
||||||
|
all: AUTHORS clean lint build test binaries
|
||||||
|
|
||||||
|
AUTHORS: .git/HEAD
|
||||||
|
git log --format='%aN <%aE>' | sort -fu > $@
|
||||||
|
|
||||||
|
# This only needs to be generated by hand when cutting full releases.
|
||||||
|
version/version.go:
|
||||||
|
./version/version.sh > $@
|
||||||
|
|
||||||
|
${PREFIX}/bin/notary-server: NOTARY_VERSION $(shell find . -type f -name '*.go')
|
||||||
|
@echo "+ $@"
|
||||||
|
@go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS} ./cmd/notary-server
|
||||||
|
|
||||||
|
${PREFIX}/bin/notary: NOTARY_VERSION $(shell find . -type f -name '*.go')
|
||||||
|
@echo "+ $@"
|
||||||
|
@go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS} ./cmd/notary
|
||||||
|
|
||||||
|
${PREFIX}/bin/notary-signer: NOTARY_VERSION $(shell find . -type f -name '*.go')
|
||||||
|
@echo "+ $@"
|
||||||
|
@go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS} ./cmd/notary-signer
|
||||||
|
|
||||||
|
${PREFIX}/bin/escrow: NOTARY_VERSION $(shell find . -type f -name '*.go')
|
||||||
|
@echo "+ $@"
|
||||||
|
@go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS} ./cmd/escrow
|
||||||
|
|
||||||
|
ifeq ($(shell uname -s),Darwin)
|
||||||
|
${PREFIX}/bin/static/notary-server:
|
||||||
|
@echo "notary-server: static builds not supported on OS X"
|
||||||
|
|
||||||
|
${PREFIX}/bin/static/notary-signer:
|
||||||
|
@echo "notary-signer: static builds not supported on OS X"
|
||||||
|
|
||||||
|
${PREFIX}/bin/static/notary:
|
||||||
|
@echo "notary: static builds not supported on OS X"
|
||||||
|
else
|
||||||
|
${PREFIX}/bin/static/notary-server: NOTARY_VERSION $(shell find . -type f -name '*.go')
|
||||||
|
@echo "+ $@"
|
||||||
|
@(export CGO_ENABLED=0; go build -tags "${NOTARY_BUILDTAGS} netgo" -o $@ ${GO_LDFLAGS_STATIC} ./cmd/notary-server)
|
||||||
|
|
||||||
|
${PREFIX}/bin/static/notary-signer: NOTARY_VERSION $(shell find . -type f -name '*.go')
|
||||||
|
@echo "+ $@"
|
||||||
|
@(export CGO_ENABLED=0; go build -tags "${NOTARY_BUILDTAGS} netgo" -o $@ ${GO_LDFLAGS_STATIC} ./cmd/notary-signer)
|
||||||
|
|
||||||
|
${PREFIX}/bin/static/notary:
|
||||||
|
@echo "+ $@"
|
||||||
|
@go build -tags "${NOTARY_BUILDTAGS} netgo" -o $@ ${GO_LDFLAGS_STATIC} ./cmd/notary
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
|
# run all lint functionality - excludes Godep directory, vendoring, binaries, python tests, and git files
|
||||||
|
lint:
|
||||||
|
@echo "+ $@: golint, go vet, go fmt, gocycle, misspell, ineffassign"
|
||||||
|
# golint
|
||||||
|
@test -z "$(shell find . -type f -name "*.go" -not -path "./vendor/*" -not -name "*.pb.*" -exec golint {} \; | tee /dev/stderr)"
|
||||||
|
# gofmt
|
||||||
|
@test -z "$$(gofmt -s -l .| grep -v .pb. | grep -v vendor/ | tee /dev/stderr)"
|
||||||
|
# govet
|
||||||
|
ifeq ($(shell uname -s), Darwin)
|
||||||
|
@test -z "$(shell find . -iname *test*.go | grep -v _test.go | grep -v vendor | xargs echo "This file should end with '_test':" | tee /dev/stderr)"
|
||||||
|
else
|
||||||
|
@test -z "$(shell find . -iname *test*.go | grep -v _test.go | grep -v vendor | xargs -r echo "This file should end with '_test':" | tee /dev/stderr)"
|
||||||
|
endif
|
||||||
|
@test -z "$$(go tool vet -printf=false . 2>&1 | grep -v vendor/ | tee /dev/stderr)"
|
||||||
|
# gocyclo - we require cyclomatic complexity to be < 16
|
||||||
|
@test -z "$(shell find . -type f -name "*.go" -not -path "./vendor/*" -not -name "*.pb.*" -exec gocyclo -over 15 {} \; | tee /dev/stderr)"
|
||||||
|
# misspell - requires that the following be run first:
|
||||||
|
# go get -u github.com/client9/misspell/cmd/misspell
|
||||||
|
@test -z "$$(find . -type f | grep -v vendor/ | grep -v bin/ | grep -v misc/ | grep -v .git/ | grep -v \.pdf | xargs misspell | tee /dev/stderr)"
|
||||||
|
# ineffassign - requires that the following be run first:
|
||||||
|
# go get -u github.com/gordonklaus/ineffassign
|
||||||
|
@test -z "$(shell find . -type f -name "*.go" -not -path "./vendor/*" -not -name "*.pb.*" -exec ineffassign {} \; | tee /dev/stderr)"
|
||||||
|
# gas - requires that the following be run first:
|
||||||
|
# go get -u github.com/HewlettPackard/gas
|
||||||
|
# @gas -skip=vendor -skip=*/*_test.go -skip=*/*/*_test.go -fmt=csv -out=gas_output.csv ./... && test -z "$$(cat gas_output.csv | tee /dev/stderr)"
|
||||||
|
|
||||||
|
build:
|
||||||
|
@echo "+ $@"
|
||||||
|
@go build -tags "${NOTARY_BUILDTAGS}" -v ${GO_LDFLAGS} $(PKGS)
|
||||||
|
|
||||||
|
# When running `go test ./...`, it runs all the suites in parallel, which causes
|
||||||
|
# problems when running with a yubikey
|
||||||
|
test: TESTOPTS =
|
||||||
|
test:
|
||||||
|
@echo Note: when testing with a yubikey plugged in, make sure to include 'TESTOPTS="-p 1"'
|
||||||
|
@echo "+ $@ $(TESTOPTS)"
|
||||||
|
@echo
|
||||||
|
go test -tags "${NOTARY_BUILDTAGS}" $(TESTOPTS) $(PKGS)
|
||||||
|
|
||||||
|
integration: TESTDB = mysql
|
||||||
|
integration: clean
|
||||||
|
buildscripts/integrationtest.sh $(TESTDB)
|
||||||
|
|
||||||
|
testdb: TESTDB = mysql
|
||||||
|
testdb:
|
||||||
|
buildscripts/dbtests.sh $(TESTDB)
|
||||||
|
|
||||||
|
protos:
|
||||||
|
@protoc --go_out=plugins=grpc:. proto/*.proto
|
||||||
|
|
||||||
|
# This allows coverage for a package to come from tests in different package.
|
||||||
|
# Requires that the following:
|
||||||
|
# go get github.com/wadey/gocovmerge; go install github.com/wadey/gocovmerge
|
||||||
|
#
|
||||||
|
# be run first
|
||||||
|
gen-cover:
|
||||||
|
gen-cover:
|
||||||
|
@python -u buildscripts/covertest.py --tags "$(NOTARY_BUILDTAGS)" --pkgs="$(PKGS)" --testopts="${TESTOPTS}" --debug
|
||||||
|
|
||||||
|
# Generates the cover binaries and runs them all in serial, so this can be used
|
||||||
|
# run all tests with a yubikey without any problems
|
||||||
|
cover: gen-cover covmerge
|
||||||
|
@go tool cover -html="$(COVERPROFILE)"
|
||||||
|
|
||||||
|
# Generates the cover binaries and runs them all in serial, so this can be used
|
||||||
|
# run all tests with a yubikey without any problems
|
||||||
|
ci: override TESTOPTS = -race
|
||||||
|
# Codecov knows how to merge multiple coverage files, so covmerge is not needed
|
||||||
|
ci: gen-cover
|
||||||
|
|
||||||
|
yubikey-tests: override PKGS = github.com/docker/notary/cmd/notary github.com/docker/notary/trustmanager/yubikey
|
||||||
|
yubikey-tests: ci
|
||||||
|
|
||||||
|
covmerge:
|
||||||
|
@gocovmerge $(shell find . -name coverage*.txt | tr "\n" " ") > $(COVERPROFILE)
|
||||||
|
@go tool cover -func="$(COVERPROFILE)"
|
||||||
|
|
||||||
|
clean-protos:
|
||||||
|
@rm proto/*.pb.go
|
||||||
|
|
||||||
|
client: ${PREFIX}/bin/notary
|
||||||
|
@echo "+ $@"
|
||||||
|
|
||||||
|
binaries: ${PREFIX}/bin/notary-server ${PREFIX}/bin/notary ${PREFIX}/bin/notary-signer
|
||||||
|
@echo "+ $@"
|
||||||
|
|
||||||
|
escrow: ${PREFIX}/bin/escrow
|
||||||
|
@echo "+ $@"
|
||||||
|
|
||||||
|
static: ${PREFIX}/bin/static/notary-server ${PREFIX}/bin/static/notary-signer ${PREFIX}/bin/static/notary
|
||||||
|
@echo "+ $@"
|
||||||
|
|
||||||
|
notary-dockerfile:
|
||||||
|
@docker build --rm --force-rm -t notary .
|
||||||
|
|
||||||
|
server-dockerfile:
|
||||||
|
@docker build --rm --force-rm -f server.Dockerfile -t notary-server .
|
||||||
|
|
||||||
|
signer-dockerfile:
|
||||||
|
@docker build --rm --force-rm -f signer.Dockerfile -t notary-signer .
|
||||||
|
|
||||||
|
docker-images: notary-dockerfile server-dockerfile signer-dockerfile
|
||||||
|
|
||||||
|
shell: notary-dockerfile
|
||||||
|
docker run --rm -it -v $(CURDIR)/cross:$(NOTARYDIR)/cross -v $(CURDIR)/bin:$(NOTARYDIR)/bin notary bash
|
||||||
|
|
||||||
|
cross:
|
||||||
|
@rm -rf $(CURDIR)/cross
|
||||||
|
@docker build --rm --force-rm -t notary -f cross.Dockerfile .
|
||||||
|
docker run --rm -v $(CURDIR)/cross:$(NOTARYDIR)/cross -e CTIMEVAR="${CTIMEVAR}" -e NOTARY_BUILDTAGS=$(NOTARY_BUILDTAGS) notary buildscripts/cross.sh $(GOOSES)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@echo "+ $@"
|
||||||
|
@rm -rf .cover cross
|
||||||
|
find . -name coverage.txt -delete
|
||||||
|
@rm -rf "${PREFIX}/bin/notary-server" "${PREFIX}/bin/notary" "${PREFIX}/bin/notary-signer"
|
||||||
|
@rm -rf "${PREFIX}/bin/static"
|
1
src/vendor/github.com/docker/notary/NOTARY_VERSION
generated
vendored
Normal file
1
src/vendor/github.com/docker/notary/NOTARY_VERSION
generated
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
0.5.0
|
105
src/vendor/github.com/docker/notary/README.md
generated
vendored
Normal file
105
src/vendor/github.com/docker/notary/README.md
generated
vendored
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
# Notary
|
||||||
|
[![Circle CI](https://circleci.com/gh/docker/notary/tree/master.svg?style=shield)](https://circleci.com/gh/docker/notary/tree/master) [![CodeCov](https://codecov.io/github/docker/notary/coverage.svg?branch=master)](https://codecov.io/github/docker/notary) [![GoReportCard](https://goreportcard.com/badge/docker/notary)](https://goreportcard.com/report/github.com/docker/notary)
|
||||||
|
|
||||||
|
The Notary project comprises a [server](cmd/notary-server) and a [client](cmd/notary) for running and interacting
|
||||||
|
with trusted collections. Please see the [service architecture](docs/service_architecture.md) documentation
|
||||||
|
for more information.
|
||||||
|
|
||||||
|
|
||||||
|
Notary aims to make the internet more secure by making it easy for people to
|
||||||
|
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
|
||||||
|
enables malicious content to be substituted for the legitimate content.
|
||||||
|
|
||||||
|
With Notary, publishers can sign their content offline using keys kept highly
|
||||||
|
secure. Once the publisher is ready to make the content available, they can
|
||||||
|
push their signed trusted collection to a Notary Server.
|
||||||
|
|
||||||
|
Consumers, having acquired the publisher's public key through a secure channel,
|
||||||
|
can then communicate with any notary server or (insecure) mirror, relying
|
||||||
|
only on the publisher's key to determine the validity and integrity of the
|
||||||
|
received content.
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
Notary is based on [The Update Framework](https://www.theupdateframework.com/), a secure general design for the problem of software distribution and updates. By using TUF, notary achieves a number of key advantages:
|
||||||
|
|
||||||
|
* **Survivable Key Compromise**: Content publishers must manage keys in order to sign their content. Signing keys may be compromised or lost so systems must be designed in order to be flexible and recoverable in the case of key compromise. TUF's notion of key roles is utilized to separate responsibilities across a hierarchy of keys such that loss of any particular key (except the root role) by itself is not fatal to the security of the system.
|
||||||
|
* **Freshness Guarantees**: Replay attacks are a common problem in designing secure systems, where previously valid payloads are replayed to trick another system. The same problem exists in the software update systems, where old signed can be presented as the most recent. notary makes use of timestamping on publishing so that consumers can know that they are receiving the most up to date content. This is particularly important when dealing with software update where old vulnerable versions could be used to attack users.
|
||||||
|
* **Configurable Trust Thresholds**: Oftentimes there are a large number of publishers that are allowed to publish a particular piece of content. For example, open source projects where there are a number of core maintainers. Trust thresholds can be used so that content consumers require a configurable number of signatures on a piece of content in order to trust it. Using thresholds increases security so that loss of individual signing keys doesn't allow publishing of malicious content.
|
||||||
|
* **Signing Delegation**: To allow for flexible publishing of trusted collections, a content publisher can delegate part of their collection to another signer. This delegation is represented as signed metadata so that a consumer of the content can verify both the content and the delegation.
|
||||||
|
* **Use of Existing Distribution**: Notary's trust guarantees are not tied at all to particular distribution channels from which content is delivered. Therefore, trust can be added to any existing content delivery mechanism.
|
||||||
|
* **Untrusted Mirrors and Transport**: All of the notary metadata can be mirrored and distributed via arbitrary channels.
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
Please see our [service architecture docs](docs/service_architecture.md#threat-model) for more information about our threat model, which details the varying survivability and severities for key compromise as well as mitigations.
|
||||||
|
|
||||||
|
Our last security audit was on July 31, 2015 by NCC ([results](docs/resources/ncc_docker_notary_audit_2015_07_31.pdf)).
|
||||||
|
|
||||||
|
Any security vulnerabilities can be reported to security@docker.com.
|
||||||
|
|
||||||
|
# Getting started with the Notary CLI
|
||||||
|
|
||||||
|
Please get the Notary Client CLI binary from [the official releases page](https://github.com/docker/notary/releases) or you can [build one yourself](#building-notary).
|
||||||
|
The version of Notary server and signer should be greater than or equal to Notary CLI's version to ensure feature compatibility (ex: CLI version 0.2, server/signer version >= 0.2), and all official releases are associated with GitHub tags.
|
||||||
|
|
||||||
|
To use the Notary CLI with Docker hub images, please have a look at our
|
||||||
|
[getting started docs](docs/getting_started.md).
|
||||||
|
|
||||||
|
For more advanced usage, please see the
|
||||||
|
[advanced usage docs](docs/advanced_usage.md).
|
||||||
|
|
||||||
|
To use the CLI against a local Notary server rather than against Docker Hub:
|
||||||
|
|
||||||
|
1. Please ensure that you have [docker and docker-compose](http://docs.docker.com/compose/install/) installed.
|
||||||
|
1. `git clone https://github.com/docker/notary.git` and from the cloned repository path,
|
||||||
|
start up a local Notary server and signer and copy the config file and testing certs to your
|
||||||
|
local notary config directory:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ docker-compose build
|
||||||
|
$ docker-compose up -d
|
||||||
|
$ mkdir -p ~/.notary && cp cmd/notary/config.json cmd/notary/root-ca.crt ~/.notary
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Add `127.0.0.1 notary-server` to your `/etc/hosts`, or if using docker-machine,
|
||||||
|
add `$(docker-machine ip) notary-server`).
|
||||||
|
|
||||||
|
You can run through the examples in the
|
||||||
|
[getting started docs](docs/getting_started.md) and
|
||||||
|
[advanced usage docs](docs/advanced_usage.md), but
|
||||||
|
without the `-s` (server URL) argument to the `notary` command since the server
|
||||||
|
URL is specified already in the configuration, file you copied.
|
||||||
|
|
||||||
|
You can also leave off the `-d ~/.docker/trust` argument if you do not care
|
||||||
|
to use `notary` with Docker images.
|
||||||
|
|
||||||
|
|
||||||
|
## Building Notary
|
||||||
|
|
||||||
|
Note that our [latest stable release](https://github.com/docker/notary/releases) is at the head of the
|
||||||
|
[releases branch](https://github.com/docker/notary/tree/releases). The master branch is the development
|
||||||
|
branch and contains features for the next release.
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- Go >= 1.7.1
|
||||||
|
- [godep](https://github.com/tools/godep) installed
|
||||||
|
- libtool development headers installed
|
||||||
|
- Ubuntu: `apt-get install libltdl-dev`
|
||||||
|
- CentOS/RedHat: `yum install libtool-ltdl-devel`
|
||||||
|
- Mac OS ([Homebrew](http://brew.sh/)): `brew install libtool`
|
||||||
|
|
||||||
|
Run `make client`, which creates the Notary Client CLI binary at `bin/notary`.
|
||||||
|
Note that `make client` assumes a standard Go directory structure, in which
|
||||||
|
Notary is checked out to the `src` directory in your `GOPATH`. For example:
|
||||||
|
```
|
||||||
|
$GOPATH/
|
||||||
|
src/
|
||||||
|
github.com/
|
||||||
|
docker/
|
||||||
|
notary/
|
||||||
|
```
|
||||||
|
|
||||||
|
To build the server and signer, please run `docker-compose build`.
|
7
src/vendor/github.com/docker/notary/ROADMAP.md
generated
vendored
Normal file
7
src/vendor/github.com/docker/notary/ROADMAP.md
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Roadmap
|
||||||
|
|
||||||
|
The Trust project consists of a number of moving parts of which Notary Server is one. Notary Server is the front line metadata service
|
||||||
|
that clients interact with. It manages TUF metadata and interacts with a pluggable signing service to issue new TUF timestamp
|
||||||
|
files.
|
||||||
|
|
||||||
|
The Notary-signer is provided as our reference implementation of a signing service. It supports HSMs along with Ed25519 software signing.
|
23
src/vendor/github.com/docker/notary/circle.yml
generated
vendored
Normal file
23
src/vendor/github.com/docker/notary/circle.yml
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
machine:
|
||||||
|
pre:
|
||||||
|
# Upgrade docker
|
||||||
|
- curl -sSL https://s3.amazonaws.com/circle-downloads/install-circleci-docker.sh | bash -s -- 1.10.0
|
||||||
|
# upgrade compose
|
||||||
|
- sudo pip install --upgrade docker-compose
|
||||||
|
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
override:
|
||||||
|
- docker build -t notary_client .
|
||||||
|
|
||||||
|
test:
|
||||||
|
override:
|
||||||
|
# circleci only supports manual parellism
|
||||||
|
- buildscripts/circle_parallelism.sh:
|
||||||
|
parallel: true
|
||||||
|
timeout: 600
|
||||||
|
post:
|
||||||
|
- docker-compose -f docker-compose.yml down -v
|
||||||
|
- docker-compose -f docker-compose.rethink.yml down -v
|
100
src/vendor/github.com/docker/notary/client/changelist/change.go
generated
vendored
Normal file
100
src/vendor/github.com/docker/notary/client/changelist/change.go
generated
vendored
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package changelist
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Scopes for TUFChanges are simply the TUF roles.
|
||||||
|
// Unfortunately because of targets delegations, we can only
|
||||||
|
// cover the base roles.
|
||||||
|
const (
|
||||||
|
ScopeRoot = "root"
|
||||||
|
ScopeTargets = "targets"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Types for TUFChanges are namespaced by the Role they
|
||||||
|
// are relevant for. The Root and Targets roles are the
|
||||||
|
// only ones for which user action can cause a change, as
|
||||||
|
// all changes in Snapshot and Timestamp are programmatically
|
||||||
|
// generated base on Root and Targets changes.
|
||||||
|
const (
|
||||||
|
TypeBaseRole = "role"
|
||||||
|
TypeTargetsTarget = "target"
|
||||||
|
TypeTargetsDelegation = "delegation"
|
||||||
|
TypeWitness = "witness"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TUFChange represents a change to a TUF repo
|
||||||
|
type TUFChange struct {
|
||||||
|
// Abbreviated because Go doesn't permit a field and method of the same name
|
||||||
|
Actn string `json:"action"`
|
||||||
|
Role data.RoleName `json:"role"`
|
||||||
|
ChangeType string `json:"type"`
|
||||||
|
ChangePath string `json:"path"`
|
||||||
|
Data []byte `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TUFRootData represents a modification of the keys associated
|
||||||
|
// with a role that appears in the root.json
|
||||||
|
type TUFRootData struct {
|
||||||
|
Keys data.KeyList `json:"keys"`
|
||||||
|
RoleName data.RoleName `json:"role"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTUFChange initializes a TUFChange object
|
||||||
|
func NewTUFChange(action string, role data.RoleName, changeType, changePath string, content []byte) *TUFChange {
|
||||||
|
return &TUFChange{
|
||||||
|
Actn: action,
|
||||||
|
Role: role,
|
||||||
|
ChangeType: changeType,
|
||||||
|
ChangePath: changePath,
|
||||||
|
Data: content,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action return c.Actn
|
||||||
|
func (c TUFChange) Action() string {
|
||||||
|
return c.Actn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scope returns c.Role
|
||||||
|
func (c TUFChange) Scope() data.RoleName {
|
||||||
|
return c.Role
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns c.ChangeType
|
||||||
|
func (c TUFChange) Type() string {
|
||||||
|
return c.ChangeType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path return c.ChangePath
|
||||||
|
func (c TUFChange) Path() string {
|
||||||
|
return c.ChangePath
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content returns c.Data
|
||||||
|
func (c TUFChange) Content() []byte {
|
||||||
|
return c.Data
|
||||||
|
}
|
||||||
|
|
||||||
|
// TUFDelegation represents a modification to a target delegation
|
||||||
|
// this includes creating a delegations. This format is used to avoid
|
||||||
|
// unexpected race conditions between humans modifying the same delegation
|
||||||
|
type TUFDelegation struct {
|
||||||
|
NewName data.RoleName `json:"new_name,omitempty"`
|
||||||
|
NewThreshold int `json:"threshold, omitempty"`
|
||||||
|
AddKeys data.KeyList `json:"add_keys, omitempty"`
|
||||||
|
RemoveKeys []string `json:"remove_keys,omitempty"`
|
||||||
|
AddPaths []string `json:"add_paths,omitempty"`
|
||||||
|
RemovePaths []string `json:"remove_paths,omitempty"`
|
||||||
|
ClearAllPaths bool `json:"clear_paths,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToNewRole creates a fresh role object from the TUFDelegation data
|
||||||
|
func (td TUFDelegation) ToNewRole(scope data.RoleName) (*data.Role, error) {
|
||||||
|
name := scope
|
||||||
|
if td.NewName != "" {
|
||||||
|
name = td.NewName
|
||||||
|
}
|
||||||
|
return data.NewRole(name, td.NewThreshold, td.AddKeys.IDs(), td.AddPaths)
|
||||||
|
}
|
82
src/vendor/github.com/docker/notary/client/changelist/changelist.go
generated
vendored
Normal file
82
src/vendor/github.com/docker/notary/client/changelist/changelist.go
generated
vendored
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package changelist
|
||||||
|
|
||||||
|
// memChangeList implements a simple in memory change list.
|
||||||
|
type memChangelist struct {
|
||||||
|
changes []Change
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMemChangelist instantiates a new in-memory changelist
|
||||||
|
func NewMemChangelist() Changelist {
|
||||||
|
return &memChangelist{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns a list of Changes
|
||||||
|
func (cl memChangelist) List() []Change {
|
||||||
|
return cl.changes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds a change to the in-memory change list
|
||||||
|
func (cl *memChangelist) Add(c Change) error {
|
||||||
|
cl.changes = append(cl.changes, c)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Location returns the string "memory"
|
||||||
|
func (cl memChangelist) Location() string {
|
||||||
|
return "memory"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove deletes the changes found at the given indices
|
||||||
|
func (cl *memChangelist) Remove(idxs []int) error {
|
||||||
|
remove := make(map[int]struct{})
|
||||||
|
for _, i := range idxs {
|
||||||
|
remove[i] = struct{}{}
|
||||||
|
}
|
||||||
|
var keep []Change
|
||||||
|
|
||||||
|
for i, c := range cl.changes {
|
||||||
|
if _, ok := remove[i]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keep = append(keep, c)
|
||||||
|
}
|
||||||
|
cl.changes = keep
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear empties the changelist file.
|
||||||
|
func (cl *memChangelist) Clear(archive string) error {
|
||||||
|
// appending to a nil list initializes it.
|
||||||
|
cl.changes = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close is a no-op in this in-memory change-list
|
||||||
|
func (cl *memChangelist) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *memChangelist) NewIterator() (ChangeIterator, error) {
|
||||||
|
return &MemChangeListIterator{index: 0, collection: cl.changes}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemChangeListIterator is a concrete instance of ChangeIterator
|
||||||
|
type MemChangeListIterator struct {
|
||||||
|
index int
|
||||||
|
collection []Change // Same type as memChangeList.changes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns the next Change
|
||||||
|
func (m *MemChangeListIterator) Next() (item Change, err error) {
|
||||||
|
if m.index >= len(m.collection) {
|
||||||
|
return nil, IteratorBoundsError(m.index)
|
||||||
|
}
|
||||||
|
item = m.collection[m.index]
|
||||||
|
m.index++
|
||||||
|
return item, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasNext indicates whether the iterator is exhausted
|
||||||
|
func (m *MemChangeListIterator) HasNext() bool {
|
||||||
|
return m.index < len(m.collection)
|
||||||
|
}
|
202
src/vendor/github.com/docker/notary/client/changelist/file_changelist.go
generated
vendored
Normal file
202
src/vendor/github.com/docker/notary/client/changelist/file_changelist.go
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
package changelist
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/distribution/uuid"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileChangelist stores all the changes as files
|
||||||
|
type FileChangelist struct {
|
||||||
|
dir string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileChangelist is a convenience method for returning FileChangeLists
|
||||||
|
func NewFileChangelist(dir string) (*FileChangelist, error) {
|
||||||
|
logrus.Debug("Making dir path: ", dir)
|
||||||
|
err := os.MkdirAll(dir, 0700)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &FileChangelist{dir: dir}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFileNames reads directory, filtering out child directories
|
||||||
|
func getFileNames(dirName string) ([]os.FileInfo, error) {
|
||||||
|
var dirListing, fileInfos []os.FileInfo
|
||||||
|
dir, err := os.Open(dirName)
|
||||||
|
if err != nil {
|
||||||
|
return fileInfos, err
|
||||||
|
}
|
||||||
|
defer dir.Close()
|
||||||
|
dirListing, err = dir.Readdir(0)
|
||||||
|
if err != nil {
|
||||||
|
return fileInfos, err
|
||||||
|
}
|
||||||
|
for _, f := range dirListing {
|
||||||
|
if f.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fileInfos = append(fileInfos, f)
|
||||||
|
}
|
||||||
|
sort.Sort(fileChanges(fileInfos))
|
||||||
|
return fileInfos, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read a JSON formatted file from disk; convert to TUFChange struct
|
||||||
|
func unmarshalFile(dirname string, f os.FileInfo) (*TUFChange, error) {
|
||||||
|
c := &TUFChange{}
|
||||||
|
raw, err := ioutil.ReadFile(filepath.Join(dirname, f.Name()))
|
||||||
|
if err != nil {
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(raw, c)
|
||||||
|
if err != nil {
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns a list of sorted changes
|
||||||
|
func (cl FileChangelist) List() []Change {
|
||||||
|
var changes []Change
|
||||||
|
fileInfos, err := getFileNames(cl.dir)
|
||||||
|
if err != nil {
|
||||||
|
return changes
|
||||||
|
}
|
||||||
|
for _, f := range fileInfos {
|
||||||
|
c, err := unmarshalFile(cl.dir, f)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Warn(err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
changes = append(changes, c)
|
||||||
|
}
|
||||||
|
return changes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds a change to the file change list
|
||||||
|
func (cl FileChangelist) Add(c Change) error {
|
||||||
|
cJSON, err := json.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
filename := fmt.Sprintf("%020d_%s.change", time.Now().UnixNano(), uuid.Generate())
|
||||||
|
return ioutil.WriteFile(filepath.Join(cl.dir, filename), cJSON, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove deletes the changes found at the given indices
|
||||||
|
func (cl FileChangelist) Remove(idxs []int) error {
|
||||||
|
fileInfos, err := getFileNames(cl.dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
remove := make(map[int]struct{})
|
||||||
|
for _, i := range idxs {
|
||||||
|
remove[i] = struct{}{}
|
||||||
|
}
|
||||||
|
for i, c := range fileInfos {
|
||||||
|
if _, ok := remove[i]; ok {
|
||||||
|
file := filepath.Join(cl.dir, c.Name())
|
||||||
|
if err := os.Remove(file); err != nil {
|
||||||
|
logrus.Errorf("could not remove change %d: %s", i, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear clears the change list
|
||||||
|
// N.B. archiving not currently implemented
|
||||||
|
func (cl FileChangelist) Clear(archive string) error {
|
||||||
|
dir, err := os.Open(cl.dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer dir.Close()
|
||||||
|
files, err := dir.Readdir(0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, f := range files {
|
||||||
|
os.Remove(filepath.Join(cl.dir, f.Name()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close is a no-op
|
||||||
|
func (cl FileChangelist) Close() error {
|
||||||
|
// Nothing to do here
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Location returns the file path to the changelist
|
||||||
|
func (cl FileChangelist) Location() string {
|
||||||
|
return cl.dir
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIterator creates an iterator from FileChangelist
|
||||||
|
func (cl FileChangelist) NewIterator() (ChangeIterator, error) {
|
||||||
|
fileInfos, err := getFileNames(cl.dir)
|
||||||
|
if err != nil {
|
||||||
|
return &FileChangeListIterator{}, err
|
||||||
|
}
|
||||||
|
return &FileChangeListIterator{dirname: cl.dir, collection: fileInfos}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IteratorBoundsError is an Error type used by Next()
|
||||||
|
type IteratorBoundsError int
|
||||||
|
|
||||||
|
// Error implements the Error interface
|
||||||
|
func (e IteratorBoundsError) Error() string {
|
||||||
|
return fmt.Sprintf("Iterator index (%d) out of bounds", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileChangeListIterator is a concrete instance of ChangeIterator
|
||||||
|
type FileChangeListIterator struct {
|
||||||
|
index int
|
||||||
|
dirname string
|
||||||
|
collection []os.FileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns the next Change in the FileChangeList
|
||||||
|
func (m *FileChangeListIterator) Next() (item Change, err error) {
|
||||||
|
if m.index >= len(m.collection) {
|
||||||
|
return nil, IteratorBoundsError(m.index)
|
||||||
|
}
|
||||||
|
f := m.collection[m.index]
|
||||||
|
m.index++
|
||||||
|
item, err = unmarshalFile(m.dirname, f)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasNext indicates whether iterator is exhausted
|
||||||
|
func (m *FileChangeListIterator) HasNext() bool {
|
||||||
|
return m.index < len(m.collection)
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileChanges []os.FileInfo
|
||||||
|
|
||||||
|
// Len returns the length of a file change list
|
||||||
|
func (cs fileChanges) Len() int {
|
||||||
|
return len(cs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less compares the names of two different file changes
|
||||||
|
func (cs fileChanges) Less(i, j int) bool {
|
||||||
|
return cs[i].Name() < cs[j].Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap swaps the position of two file changes
|
||||||
|
func (cs fileChanges) Swap(i, j int) {
|
||||||
|
tmp := cs[i]
|
||||||
|
cs[i] = cs[j]
|
||||||
|
cs[j] = tmp
|
||||||
|
}
|
78
src/vendor/github.com/docker/notary/client/changelist/interface.go
generated
vendored
Normal file
78
src/vendor/github.com/docker/notary/client/changelist/interface.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package changelist
|
||||||
|
|
||||||
|
import "github.com/docker/notary/tuf/data"
|
||||||
|
|
||||||
|
// Changelist is the interface for all TUF change lists
|
||||||
|
type Changelist interface {
|
||||||
|
// List returns the ordered list of changes
|
||||||
|
// currently stored
|
||||||
|
List() []Change
|
||||||
|
|
||||||
|
// Add change appends the provided change to
|
||||||
|
// the list of changes
|
||||||
|
Add(Change) error
|
||||||
|
|
||||||
|
// Clear empties the current change list.
|
||||||
|
// Archive may be provided as a directory path
|
||||||
|
// to save a copy of the changelist in that location
|
||||||
|
Clear(archive string) error
|
||||||
|
|
||||||
|
// Remove deletes the changes corresponding with the indices given
|
||||||
|
Remove(idxs []int) error
|
||||||
|
|
||||||
|
// Close syncronizes any pending writes to the underlying
|
||||||
|
// storage and closes the file/connection
|
||||||
|
Close() error
|
||||||
|
|
||||||
|
// NewIterator returns an iterator for walking through the list
|
||||||
|
// of changes currently stored
|
||||||
|
NewIterator() (ChangeIterator, error)
|
||||||
|
|
||||||
|
// Location returns the place the changelist is stores
|
||||||
|
Location() string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ActionCreate represents a Create action
|
||||||
|
ActionCreate = "create"
|
||||||
|
// ActionUpdate represents an Update action
|
||||||
|
ActionUpdate = "update"
|
||||||
|
// ActionDelete represents a Delete action
|
||||||
|
ActionDelete = "delete"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Change is the interface for a TUF Change
|
||||||
|
type Change interface {
|
||||||
|
// "create","update", or "delete"
|
||||||
|
Action() string
|
||||||
|
|
||||||
|
// Where the change should be made.
|
||||||
|
// For TUF this will be the role
|
||||||
|
Scope() data.RoleName
|
||||||
|
|
||||||
|
// The content type being affected.
|
||||||
|
// For TUF this will be "target", or "delegation".
|
||||||
|
// If the type is "delegation", the Scope will be
|
||||||
|
// used to determine if a root role is being updated
|
||||||
|
// or a target delegation.
|
||||||
|
Type() string
|
||||||
|
|
||||||
|
// Path indicates the entry within a role to be affected by the
|
||||||
|
// change. For targets, this is simply the target's path,
|
||||||
|
// for delegations it's the delegated role name.
|
||||||
|
Path() string
|
||||||
|
|
||||||
|
// Serialized content that the interpreter of a changelist
|
||||||
|
// can use to apply the change.
|
||||||
|
// For TUF this will be the serialized JSON that needs
|
||||||
|
// to be inserted or merged. In the case of a "delete"
|
||||||
|
// action, it will be nil.
|
||||||
|
Content() []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeIterator is the interface for iterating across collections of
|
||||||
|
// TUF Change items
|
||||||
|
type ChangeIterator interface {
|
||||||
|
Next() (Change, error)
|
||||||
|
HasNext() bool
|
||||||
|
}
|
1162
src/vendor/github.com/docker/notary/client/client.go
generated
vendored
Normal file
1162
src/vendor/github.com/docker/notary/client/client.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
262
src/vendor/github.com/docker/notary/client/delegations.go
generated
vendored
Normal file
262
src/vendor/github.com/docker/notary/client/delegations.go
generated
vendored
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/notary"
|
||||||
|
"github.com/docker/notary/client/changelist"
|
||||||
|
store "github.com/docker/notary/storage"
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"github.com/docker/notary/tuf/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddDelegation creates changelist entries to add provided delegation public keys and paths.
|
||||||
|
// This method composes AddDelegationRoleAndKeys and AddDelegationPaths (each creates one changelist if called).
|
||||||
|
func (r *NotaryRepository) AddDelegation(name data.RoleName, delegationKeys []data.PublicKey, paths []string) error {
|
||||||
|
if len(delegationKeys) > 0 {
|
||||||
|
err := r.AddDelegationRoleAndKeys(name, delegationKeys)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(paths) > 0 {
|
||||||
|
err := r.AddDelegationPaths(name, paths)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddDelegationRoleAndKeys creates a changelist entry to add provided delegation public keys.
|
||||||
|
// This method is the simplest way to create a new delegation, because the delegation must have at least
|
||||||
|
// one key upon creation to be valid since we will reject the changelist while validating the threshold.
|
||||||
|
func (r *NotaryRepository) AddDelegationRoleAndKeys(name data.RoleName, delegationKeys []data.PublicKey) error {
|
||||||
|
|
||||||
|
if !data.IsDelegation(name) {
|
||||||
|
return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf(`Adding delegation "%s" with threshold %d, and %d keys\n`,
|
||||||
|
name, notary.MinThreshold, len(delegationKeys))
|
||||||
|
|
||||||
|
// Defaulting to threshold of 1, since we don't allow for larger thresholds at the moment.
|
||||||
|
tdJSON, err := json.Marshal(&changelist.TUFDelegation{
|
||||||
|
NewThreshold: notary.MinThreshold,
|
||||||
|
AddKeys: data.KeyList(delegationKeys),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
template := newCreateDelegationChange(name, tdJSON)
|
||||||
|
return addChange(r.changelist, template, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddDelegationPaths creates a changelist entry to add provided paths to an existing delegation.
|
||||||
|
// This method cannot create a new delegation itself because the role must meet the key threshold upon creation.
|
||||||
|
func (r *NotaryRepository) AddDelegationPaths(name data.RoleName, paths []string) error {
|
||||||
|
|
||||||
|
if !data.IsDelegation(name) {
|
||||||
|
return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf(`Adding %s paths to delegation %s\n`, paths, name)
|
||||||
|
|
||||||
|
tdJSON, err := json.Marshal(&changelist.TUFDelegation{
|
||||||
|
AddPaths: paths,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
template := newCreateDelegationChange(name, tdJSON)
|
||||||
|
return addChange(r.changelist, template, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveDelegationKeysAndPaths creates changelist entries to remove provided delegation key IDs and paths.
|
||||||
|
// This method composes RemoveDelegationPaths and RemoveDelegationKeys (each creates one changelist if called).
|
||||||
|
func (r *NotaryRepository) RemoveDelegationKeysAndPaths(name data.RoleName, keyIDs, paths []string) error {
|
||||||
|
if len(paths) > 0 {
|
||||||
|
err := r.RemoveDelegationPaths(name, paths)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(keyIDs) > 0 {
|
||||||
|
err := r.RemoveDelegationKeys(name, keyIDs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveDelegationRole creates a changelist to remove all paths and keys from a role, and delete the role in its entirety.
|
||||||
|
func (r *NotaryRepository) RemoveDelegationRole(name data.RoleName) error {
|
||||||
|
|
||||||
|
if !data.IsDelegation(name) {
|
||||||
|
return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf(`Removing delegation "%s"\n`, name)
|
||||||
|
|
||||||
|
template := newDeleteDelegationChange(name, nil)
|
||||||
|
return addChange(r.changelist, template, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveDelegationPaths creates a changelist entry to remove provided paths from an existing delegation.
|
||||||
|
func (r *NotaryRepository) RemoveDelegationPaths(name data.RoleName, paths []string) error {
|
||||||
|
|
||||||
|
if !data.IsDelegation(name) {
|
||||||
|
return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf(`Removing %s paths from delegation "%s"\n`, paths, name)
|
||||||
|
|
||||||
|
tdJSON, err := json.Marshal(&changelist.TUFDelegation{
|
||||||
|
RemovePaths: paths,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
template := newUpdateDelegationChange(name, tdJSON)
|
||||||
|
return addChange(r.changelist, template, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveDelegationKeys creates a changelist entry to remove provided keys from an existing delegation.
|
||||||
|
// When this changelist is applied, if the specified keys are the only keys left in the role,
|
||||||
|
// the role itself will be deleted in its entirety.
|
||||||
|
// It can also delete a key from all delegations under a parent using a name
|
||||||
|
// with a wildcard at the end.
|
||||||
|
func (r *NotaryRepository) RemoveDelegationKeys(name data.RoleName, keyIDs []string) error {
|
||||||
|
|
||||||
|
if !data.IsDelegation(name) && !data.IsWildDelegation(name) {
|
||||||
|
return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf(`Removing %s keys from delegation "%s"\n`, keyIDs, name)
|
||||||
|
|
||||||
|
tdJSON, err := json.Marshal(&changelist.TUFDelegation{
|
||||||
|
RemoveKeys: keyIDs,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
template := newUpdateDelegationChange(name, tdJSON)
|
||||||
|
return addChange(r.changelist, template, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearDelegationPaths creates a changelist entry to remove all paths from an existing delegation.
|
||||||
|
func (r *NotaryRepository) ClearDelegationPaths(name data.RoleName) error {
|
||||||
|
|
||||||
|
if !data.IsDelegation(name) {
|
||||||
|
return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf(`Removing all paths from delegation "%s"\n`, name)
|
||||||
|
|
||||||
|
tdJSON, err := json.Marshal(&changelist.TUFDelegation{
|
||||||
|
ClearAllPaths: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
template := newUpdateDelegationChange(name, tdJSON)
|
||||||
|
return addChange(r.changelist, template, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUpdateDelegationChange(name data.RoleName, content []byte) *changelist.TUFChange {
|
||||||
|
return changelist.NewTUFChange(
|
||||||
|
changelist.ActionUpdate,
|
||||||
|
name,
|
||||||
|
changelist.TypeTargetsDelegation,
|
||||||
|
"", // no path for delegations
|
||||||
|
content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCreateDelegationChange(name data.RoleName, content []byte) *changelist.TUFChange {
|
||||||
|
return changelist.NewTUFChange(
|
||||||
|
changelist.ActionCreate,
|
||||||
|
name,
|
||||||
|
changelist.TypeTargetsDelegation,
|
||||||
|
"", // no path for delegations
|
||||||
|
content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDeleteDelegationChange(name data.RoleName, content []byte) *changelist.TUFChange {
|
||||||
|
return changelist.NewTUFChange(
|
||||||
|
changelist.ActionDelete,
|
||||||
|
name,
|
||||||
|
changelist.TypeTargetsDelegation,
|
||||||
|
"", // no path for delegations
|
||||||
|
content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDelegationRoles returns the keys and roles of the repository's delegations
|
||||||
|
// Also converts key IDs to canonical key IDs to keep consistent with signing prompts
|
||||||
|
func (r *NotaryRepository) GetDelegationRoles() ([]data.Role, error) {
|
||||||
|
// Update state of the repo to latest
|
||||||
|
if err := r.Update(false); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// All top level delegations (ex: targets/level1) are stored exclusively in targets.json
|
||||||
|
_, ok := r.tufRepo.Targets[data.CanonicalTargetsRole]
|
||||||
|
if !ok {
|
||||||
|
return nil, store.ErrMetaNotFound{Resource: data.CanonicalTargetsRole.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) {
|
||||||
|
canonicalDelegations := make([]data.Role, len(delegationInfo.Roles))
|
||||||
|
// Do a copy by value to ensure local delegation metadata is untouched
|
||||||
|
for idx, origRole := range delegationInfo.Roles {
|
||||||
|
canonicalDelegations[idx] = *origRole
|
||||||
|
}
|
||||||
|
delegationKeys := delegationInfo.Keys
|
||||||
|
for i, delegation := range canonicalDelegations {
|
||||||
|
canonicalKeyIDs := []string{}
|
||||||
|
for _, keyID := range delegation.KeyIDs {
|
||||||
|
pubKey, ok := delegationKeys[keyID]
|
||||||
|
if !ok {
|
||||||
|
return []data.Role{}, fmt.Errorf("Could not translate canonical key IDs for %s", delegation.Name)
|
||||||
|
}
|
||||||
|
canonicalKeyID, err := utils.CanonicalKeyID(pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return []data.Role{}, fmt.Errorf("Could not translate canonical key IDs for %s: %v", delegation.Name, err)
|
||||||
|
}
|
||||||
|
canonicalKeyIDs = append(canonicalKeyIDs, canonicalKeyID)
|
||||||
|
}
|
||||||
|
canonicalDelegations[i].KeyIDs = canonicalKeyIDs
|
||||||
|
}
|
||||||
|
return canonicalDelegations, nil
|
||||||
|
}
|
47
src/vendor/github.com/docker/notary/client/errors.go
generated
vendored
Normal file
47
src/vendor/github.com/docker/notary/client/errors.go
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrRepoNotInitialized is returned when trying to publish an uninitialized
|
||||||
|
// notary repository
|
||||||
|
type ErrRepoNotInitialized struct{}
|
||||||
|
|
||||||
|
func (err ErrRepoNotInitialized) Error() string {
|
||||||
|
return "repository has not been initialized"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrInvalidRemoteRole is returned when the server is requested to manage
|
||||||
|
// a key type that is not permitted
|
||||||
|
type ErrInvalidRemoteRole struct {
|
||||||
|
Role data.RoleName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrInvalidRemoteRole) Error() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"notary does not permit the server managing the %s key", err.Role.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrInvalidLocalRole is returned when the client wants to manage
|
||||||
|
// a key type that is not permitted
|
||||||
|
type ErrInvalidLocalRole struct {
|
||||||
|
Role data.RoleName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrInvalidLocalRole) Error() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"notary does not permit the client managing the %s key", err.Role)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrRepositoryNotExist is returned when an action is taken on a remote
|
||||||
|
// repository that doesn't exist
|
||||||
|
type ErrRepositoryNotExist struct {
|
||||||
|
remote string
|
||||||
|
gun data.GUN
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrRepositoryNotExist) Error() string {
|
||||||
|
return fmt.Sprintf("%s does not have trust data for %s", err.remote, err.gun.String())
|
||||||
|
}
|
270
src/vendor/github.com/docker/notary/client/helpers.go
generated
vendored
Normal file
270
src/vendor/github.com/docker/notary/client/helpers.go
generated
vendored
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/notary/client/changelist"
|
||||||
|
store "github.com/docker/notary/storage"
|
||||||
|
"github.com/docker/notary/tuf"
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"github.com/docker/notary/tuf/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Use this to initialize remote HTTPStores from the config settings
|
||||||
|
func getRemoteStore(baseURL string, gun data.GUN, rt http.RoundTripper) (store.RemoteStore, error) {
|
||||||
|
s, err := store.NewHTTPStore(
|
||||||
|
baseURL+"/v2/"+gun.String()+"/_trust/tuf/",
|
||||||
|
"",
|
||||||
|
"json",
|
||||||
|
"key",
|
||||||
|
rt,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return store.OfflineStore{}, err
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyChangelist(repo *tuf.Repo, invalid *tuf.Repo, cl changelist.Changelist) error {
|
||||||
|
it, err := cl.NewIterator()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
index := 0
|
||||||
|
for it.HasNext() {
|
||||||
|
c, err := it.Next()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
isDel := data.IsDelegation(c.Scope()) || data.IsWildDelegation(c.Scope())
|
||||||
|
switch {
|
||||||
|
case c.Scope() == changelist.ScopeTargets || isDel:
|
||||||
|
err = applyTargetsChange(repo, invalid, c)
|
||||||
|
case c.Scope() == changelist.ScopeRoot:
|
||||||
|
err = applyRootChange(repo, c)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("scope not supported: %s", c.Scope().String())
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("error attempting to apply change #%d: %s, on scope: %s path: %s type: %s", index, c.Action(), c.Scope(), c.Path(), c.Type())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
logrus.Debugf("applied %d change(s)", index)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyTargetsChange(repo *tuf.Repo, invalid *tuf.Repo, c changelist.Change) error {
|
||||||
|
switch c.Type() {
|
||||||
|
case changelist.TypeTargetsTarget:
|
||||||
|
return changeTargetMeta(repo, c)
|
||||||
|
case changelist.TypeTargetsDelegation:
|
||||||
|
return changeTargetsDelegation(repo, c)
|
||||||
|
case changelist.TypeWitness:
|
||||||
|
return witnessTargets(repo, invalid, c.Scope())
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("only target meta and delegations changes supported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func changeTargetsDelegation(repo *tuf.Repo, c changelist.Change) error {
|
||||||
|
switch c.Action() {
|
||||||
|
case changelist.ActionCreate:
|
||||||
|
td := changelist.TUFDelegation{}
|
||||||
|
err := json.Unmarshal(c.Content(), &td)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to create brand new role or update one
|
||||||
|
// First add the keys, then the paths. We can only add keys and paths in this scenario
|
||||||
|
err = repo.UpdateDelegationKeys(c.Scope(), td.AddKeys, []string{}, td.NewThreshold)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return repo.UpdateDelegationPaths(c.Scope(), td.AddPaths, []string{}, false)
|
||||||
|
case changelist.ActionUpdate:
|
||||||
|
td := changelist.TUFDelegation{}
|
||||||
|
err := json.Unmarshal(c.Content(), &td)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if data.IsWildDelegation(c.Scope()) {
|
||||||
|
return repo.PurgeDelegationKeys(c.Scope(), td.RemoveKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
delgRole, err := repo.GetDelegationRole(c.Scope())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to translate the keys from canonical ID to TUF ID for compatibility
|
||||||
|
canonicalToTUFID := make(map[string]string)
|
||||||
|
for tufID, pubKey := range delgRole.Keys {
|
||||||
|
canonicalID, err := utils.CanonicalKeyID(pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
canonicalToTUFID[canonicalID] = tufID
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTUFKeyIDs := []string{}
|
||||||
|
for _, canonID := range td.RemoveKeys {
|
||||||
|
removeTUFKeyIDs = append(removeTUFKeyIDs, canonicalToTUFID[canonID])
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.UpdateDelegationKeys(c.Scope(), td.AddKeys, removeTUFKeyIDs, td.NewThreshold)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return repo.UpdateDelegationPaths(c.Scope(), td.AddPaths, td.RemovePaths, td.ClearAllPaths)
|
||||||
|
case changelist.ActionDelete:
|
||||||
|
return repo.DeleteDelegation(c.Scope())
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported action against delegations: %s", c.Action())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func changeTargetMeta(repo *tuf.Repo, c changelist.Change) error {
|
||||||
|
var err error
|
||||||
|
switch c.Action() {
|
||||||
|
case changelist.ActionCreate:
|
||||||
|
logrus.Debug("changelist add: ", c.Path())
|
||||||
|
meta := &data.FileMeta{}
|
||||||
|
err = json.Unmarshal(c.Content(), meta)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
files := data.Files{c.Path(): *meta}
|
||||||
|
|
||||||
|
// Attempt to add the target to this role
|
||||||
|
if _, err = repo.AddTargets(c.Scope(), files); err != nil {
|
||||||
|
logrus.Errorf("couldn't add target to %s: %s", c.Scope(), err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
case changelist.ActionDelete:
|
||||||
|
logrus.Debug("changelist remove: ", c.Path())
|
||||||
|
|
||||||
|
// Attempt to remove the target from this role
|
||||||
|
if err = repo.RemoveTargets(c.Scope(), c.Path()); err != nil {
|
||||||
|
logrus.Errorf("couldn't remove target from %s: %s", c.Scope(), err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("action not yet supported: %s", c.Action())
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyRootChange(repo *tuf.Repo, c changelist.Change) error {
|
||||||
|
var err error
|
||||||
|
switch c.Type() {
|
||||||
|
case changelist.TypeBaseRole:
|
||||||
|
err = applyRootRoleChange(repo, c)
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("type of root change not yet supported: %s", c.Type())
|
||||||
|
}
|
||||||
|
return err // might be nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyRootRoleChange(repo *tuf.Repo, c changelist.Change) error {
|
||||||
|
switch c.Action() {
|
||||||
|
case changelist.ActionCreate:
|
||||||
|
// replaces all keys for a role
|
||||||
|
d := &changelist.TUFRootData{}
|
||||||
|
err := json.Unmarshal(c.Content(), d)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = repo.ReplaceBaseKeys(d.RoleName, d.Keys...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("action not yet supported for root: %s", c.Action())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func nearExpiry(r data.SignedCommon) bool {
|
||||||
|
plus6mo := time.Now().AddDate(0, 6, 0)
|
||||||
|
return r.Expires.Before(plus6mo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func warnRolesNearExpiry(r *tuf.Repo) {
|
||||||
|
//get every role and its respective signed common and call nearExpiry on it
|
||||||
|
//Root check
|
||||||
|
if nearExpiry(r.Root.Signed.SignedCommon) {
|
||||||
|
logrus.Warn("root is nearing expiry, you should re-sign the role metadata")
|
||||||
|
}
|
||||||
|
//Targets and delegations check
|
||||||
|
for role, signedTOrD := range r.Targets {
|
||||||
|
//signedTOrD is of type *data.SignedTargets
|
||||||
|
if nearExpiry(signedTOrD.Signed.SignedCommon) {
|
||||||
|
logrus.Warn(role, " metadata is nearing expiry, you should re-sign the role metadata")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Snapshot check
|
||||||
|
if nearExpiry(r.Snapshot.Signed.SignedCommon) {
|
||||||
|
logrus.Warn("snapshot is nearing expiry, you should re-sign the role metadata")
|
||||||
|
}
|
||||||
|
//do not need to worry about Timestamp, notary signer will re-sign with the timestamp key
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetches a public key from a remote store, given a gun and role
|
||||||
|
func getRemoteKey(role data.RoleName, remote store.RemoteStore) (data.PublicKey, error) {
|
||||||
|
rawPubKey, err := remote.GetKey(role)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKey, err := data.UnmarshalPublicKey(rawPubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pubKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotates a private key in a remote store and returns the public key component
|
||||||
|
func rotateRemoteKey(role data.RoleName, remote store.RemoteStore) (data.PublicKey, error) {
|
||||||
|
rawPubKey, err := remote.RotateKey(role)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKey, err := data.UnmarshalPublicKey(rawPubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pubKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// signs and serializes the metadata for a canonical role in a TUF repo to JSON
|
||||||
|
func serializeCanonicalRole(tufRepo *tuf.Repo, role data.RoleName, extraSigningKeys data.KeyList) (out []byte, err error) {
|
||||||
|
var s *data.Signed
|
||||||
|
switch {
|
||||||
|
case role == data.CanonicalRootRole:
|
||||||
|
s, err = tufRepo.SignRoot(data.DefaultExpires(role), extraSigningKeys)
|
||||||
|
case role == data.CanonicalSnapshotRole:
|
||||||
|
s, err = tufRepo.SignSnapshot(data.DefaultExpires(role))
|
||||||
|
case tufRepo.Targets[role] != nil:
|
||||||
|
s, err = tufRepo.SignTargets(
|
||||||
|
role, data.DefaultExpires(data.CanonicalTargetsRole))
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("%s not supported role to sign on the client", role)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(s)
|
||||||
|
}
|
18
src/vendor/github.com/docker/notary/client/repo.go
generated
vendored
Normal file
18
src/vendor/github.com/docker/notary/client/repo.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// +build !pkcs11
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/notary"
|
||||||
|
"github.com/docker/notary/trustmanager"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getKeyStores(baseDir string, retriever notary.PassRetriever) ([]trustmanager.KeyStore, error) {
|
||||||
|
fileKeyStore, err := trustmanager.NewKeyFileStore(baseDir, retriever)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create private key store in directory: %s", baseDir)
|
||||||
|
}
|
||||||
|
return []trustmanager.KeyStore{fileKeyStore}, nil
|
||||||
|
}
|
25
src/vendor/github.com/docker/notary/client/repo_pkcs11.go
generated
vendored
Normal file
25
src/vendor/github.com/docker/notary/client/repo_pkcs11.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// +build pkcs11
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/notary"
|
||||||
|
"github.com/docker/notary/trustmanager"
|
||||||
|
"github.com/docker/notary/trustmanager/yubikey"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getKeyStores(baseDir string, retriever notary.PassRetriever) ([]trustmanager.KeyStore, error) {
|
||||||
|
fileKeyStore, err := trustmanager.NewKeyFileStore(baseDir, retriever)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create private key store in directory: %s", baseDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyStores := []trustmanager.KeyStore{fileKeyStore}
|
||||||
|
yubiKeyStore, _ := yubikey.NewYubiStore(fileKeyStore, retriever)
|
||||||
|
if yubiKeyStore != nil {
|
||||||
|
keyStores = []trustmanager.KeyStore{yubiKeyStore, fileKeyStore}
|
||||||
|
}
|
||||||
|
return keyStores, nil
|
||||||
|
}
|
325
src/vendor/github.com/docker/notary/client/tufclient.go
generated
vendored
Normal file
325
src/vendor/github.com/docker/notary/client/tufclient.go
generated
vendored
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/notary"
|
||||||
|
store "github.com/docker/notary/storage"
|
||||||
|
"github.com/docker/notary/trustpinning"
|
||||||
|
"github.com/docker/notary/tuf"
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"github.com/docker/notary/tuf/signed"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TUFClient is a usability wrapper around a raw TUF repo
|
||||||
|
type TUFClient struct {
|
||||||
|
remote store.RemoteStore
|
||||||
|
cache store.MetadataStore
|
||||||
|
oldBuilder 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
|
||||||
|
func (c *TUFClient) Update() (*tuf.Repo, *tuf.Repo, error) {
|
||||||
|
// 1. Get timestamp
|
||||||
|
// a. If timestamp error (verification, expired, etc...) download new root and return to 1.
|
||||||
|
// 2. Check if local snapshot is up to date
|
||||||
|
// a. If out of date, get updated snapshot
|
||||||
|
// i. If snapshot error, download new root and return to 1.
|
||||||
|
// 3. Check if root correct against snapshot
|
||||||
|
// a. If incorrect, download new root and return to 1.
|
||||||
|
// 4. Iteratively download and search targets and delegations to find target meta
|
||||||
|
logrus.Debug("updating TUF client")
|
||||||
|
err := c.update()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debug("Error occurred. Root will be downloaded and another update attempted")
|
||||||
|
logrus.Debug("Resetting the TUF builder...")
|
||||||
|
|
||||||
|
c.newBuilder = c.newBuilder.BootstrapNewBuilder()
|
||||||
|
|
||||||
|
if err := c.updateRoot(); err != nil {
|
||||||
|
logrus.Debug("Client Update (Root): ", err)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
// If we error again, we now have the latest root and just want to fail
|
||||||
|
// out as there's no expectation the problem can be resolved automatically
|
||||||
|
logrus.Debug("retrying TUF client update")
|
||||||
|
if err := c.update(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.newBuilder.Finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TUFClient) update() error {
|
||||||
|
if err := c.downloadTimestamp(); err != nil {
|
||||||
|
logrus.Debugf("Client Update (Timestamp): %s", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.downloadSnapshot(); err != nil {
|
||||||
|
logrus.Debugf("Client Update (Snapshot): %s", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// will always need top level targets at a minimum
|
||||||
|
if err := c.downloadTargets(); err != nil {
|
||||||
|
logrus.Debugf("Client Update (Targets): %s", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateRoot checks if there is a newer version of the root available, and if so
|
||||||
|
// downloads all intermediate root files to allow proper key rotation.
|
||||||
|
func (c *TUFClient) updateRoot() error {
|
||||||
|
// Get current root version
|
||||||
|
currentRootConsistentInfo := c.oldBuilder.GetConsistentInfo(data.CanonicalRootRole)
|
||||||
|
currentVersion := c.oldBuilder.GetLoadedVersion(currentRootConsistentInfo.RoleName)
|
||||||
|
|
||||||
|
// Get new root version
|
||||||
|
raw, err := c.downloadRoot()
|
||||||
|
|
||||||
|
switch err.(type) {
|
||||||
|
case *trustpinning.ErrRootRotationFail:
|
||||||
|
// Rotation errors are okay since we haven't yet downloaded
|
||||||
|
// all intermediate root files
|
||||||
|
break
|
||||||
|
case nil:
|
||||||
|
// No error updating root - we were at most 1 version behind
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
// Return any non-rotation error.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load current version into newBuilder
|
||||||
|
currentRaw, err := c.cache.GetSized(data.CanonicalRootRole.String(), -1)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("error loading %d.%s: %s", currentVersion, data.CanonicalRootRole, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.newBuilder.LoadRootForUpdate(currentRaw, currentVersion, false); err != nil {
|
||||||
|
logrus.Debugf("%d.%s is invalid: %s", currentVersion, data.CanonicalRootRole, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract newest version number
|
||||||
|
signedRoot := &data.Signed{}
|
||||||
|
if err := json.Unmarshal(raw, signedRoot); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newestRoot, err := data.RootFromSigned(signedRoot)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newestVersion := newestRoot.Signed.SignedCommon.Version
|
||||||
|
|
||||||
|
// Update from current + 1 (current already loaded) to newest - 1 (newest loaded below)
|
||||||
|
if err := c.updateRootVersions(currentVersion+1, newestVersion-1); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Already downloaded newest, verify it against newest - 1
|
||||||
|
if err := c.newBuilder.LoadRootForUpdate(raw, newestVersion, true); err != nil {
|
||||||
|
logrus.Debugf("downloaded %d.%s is invalid: %s", newestVersion, data.CanonicalRootRole, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logrus.Debugf("successfully verified downloaded %d.%s", newestVersion, data.CanonicalRootRole)
|
||||||
|
|
||||||
|
// Write newest to cache
|
||||||
|
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("finished updating root files")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateRootVersions updates the root from it's current version to a target, rotating keys
|
||||||
|
// as they are found
|
||||||
|
func (c *TUFClient) updateRootVersions(fromVersion, toVersion int) error {
|
||||||
|
for v := fromVersion; v <= toVersion; v++ {
|
||||||
|
logrus.Debugf("updating root from version %d to version %d, currently fetching %d", fromVersion, toVersion, v)
|
||||||
|
|
||||||
|
versionedRole := fmt.Sprintf("%d.%s", v, data.CanonicalRootRole)
|
||||||
|
|
||||||
|
raw, err := c.remote.GetSized(versionedRole, -1)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("error downloading %s: %s", versionedRole, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.newBuilder.LoadRootForUpdate(raw, v, false); err != nil {
|
||||||
|
logrus.Debugf("downloaded %s is invalid: %s", versionedRole, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logrus.Debugf("successfully verified downloaded %s", versionedRole)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// downloadTimestamp is responsible for downloading the timestamp.json
|
||||||
|
// Timestamps are special in that we ALWAYS attempt to download and only
|
||||||
|
// use cache if the download fails (and the cache is still valid).
|
||||||
|
func (c *TUFClient) downloadTimestamp() error {
|
||||||
|
logrus.Debug("Loading timestamp...")
|
||||||
|
role := data.CanonicalTimestampRole
|
||||||
|
consistentInfo := c.newBuilder.GetConsistentInfo(role)
|
||||||
|
|
||||||
|
// always get the remote timestamp, since it supersedes the local one
|
||||||
|
cachedTS, cachedErr := c.cache.GetSized(role.String(), notary.MaxTimestampSize)
|
||||||
|
_, remoteErr := c.tryLoadRemote(consistentInfo, cachedTS)
|
||||||
|
|
||||||
|
// check that there was no remote error, or if there was a network problem
|
||||||
|
// If there was a validation error, we should error out so we can download a new root or fail the update
|
||||||
|
switch remoteErr.(type) {
|
||||||
|
case nil:
|
||||||
|
return nil
|
||||||
|
case store.ErrMetaNotFound, store.ErrServerUnavailable, store.ErrOffline, store.NetworkError:
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return remoteErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// since it was a network error: get the cached timestamp, if it exists
|
||||||
|
if cachedErr != nil {
|
||||||
|
logrus.Debug("no cached or remote timestamp available")
|
||||||
|
return remoteErr
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Warn("Error while downloading remote metadata, using cached timestamp - this might not be the latest version available remotely")
|
||||||
|
err := c.newBuilder.Load(role, cachedTS, 1, false)
|
||||||
|
if err == nil {
|
||||||
|
logrus.Debug("successfully verified cached timestamp")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// downloadSnapshot is responsible for downloading the snapshot.json
|
||||||
|
func (c *TUFClient) downloadSnapshot() error {
|
||||||
|
logrus.Debug("Loading snapshot...")
|
||||||
|
role := data.CanonicalSnapshotRole
|
||||||
|
consistentInfo := c.newBuilder.GetConsistentInfo(role)
|
||||||
|
|
||||||
|
_, err := c.tryLoadCacheThenRemote(consistentInfo)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// downloadTargets downloads all targets and delegated targets for the repository.
|
||||||
|
// It uses a pre-order tree traversal as it's necessary to download parents first
|
||||||
|
// to obtain the keys to validate children.
|
||||||
|
func (c *TUFClient) downloadTargets() error {
|
||||||
|
toDownload := []data.DelegationRole{{
|
||||||
|
BaseRole: data.BaseRole{Name: data.CanonicalTargetsRole},
|
||||||
|
Paths: []string{""},
|
||||||
|
}}
|
||||||
|
|
||||||
|
for len(toDownload) > 0 {
|
||||||
|
role := toDownload[0]
|
||||||
|
toDownload = toDownload[1:]
|
||||||
|
|
||||||
|
consistentInfo := c.newBuilder.GetConsistentInfo(role.Name)
|
||||||
|
if !consistentInfo.ChecksumKnown() {
|
||||||
|
logrus.Debugf("skipping %s because there is no checksum for it", role.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
children, err := c.getTargetsFile(role, consistentInfo)
|
||||||
|
switch err.(type) {
|
||||||
|
case signed.ErrExpired, signed.ErrRoleThreshold:
|
||||||
|
if role.Name == data.CanonicalTargetsRole {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logrus.Warnf("Error getting %s: %s", role.Name, err)
|
||||||
|
break
|
||||||
|
case nil:
|
||||||
|
toDownload = append(children, toDownload...)
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TUFClient) getTargetsFile(role data.DelegationRole, ci tuf.ConsistentInfo) ([]data.DelegationRole, error) {
|
||||||
|
logrus.Debugf("Loading %s...", role.Name)
|
||||||
|
tgs := &data.SignedTargets{}
|
||||||
|
|
||||||
|
raw, err := c.tryLoadCacheThenRemote(ci)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// we know it unmarshals because if `tryLoadCacheThenRemote` didn't fail, then
|
||||||
|
// the raw has already been loaded into the builder
|
||||||
|
json.Unmarshal(raw, tgs)
|
||||||
|
return tgs.GetValidDelegations(role), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// downloadRoot is responsible for downloading the root.json
|
||||||
|
func (c *TUFClient) downloadRoot() ([]byte, error) {
|
||||||
|
role := data.CanonicalRootRole
|
||||||
|
consistentInfo := c.newBuilder.GetConsistentInfo(role)
|
||||||
|
|
||||||
|
// We can't read an exact size for the root metadata without risking getting stuck in the TUF update cycle
|
||||||
|
// since it's possible that downloading timestamp/snapshot metadata may fail due to a signature mismatch
|
||||||
|
if !consistentInfo.ChecksumKnown() {
|
||||||
|
logrus.Debugf("Loading root with no expected checksum")
|
||||||
|
|
||||||
|
// get the cached root, if it exists, just for version checking
|
||||||
|
cachedRoot, _ := c.cache.GetSized(role.String(), -1)
|
||||||
|
// prefer to download a new root
|
||||||
|
return c.tryLoadRemote(consistentInfo, cachedRoot)
|
||||||
|
}
|
||||||
|
return c.tryLoadCacheThenRemote(consistentInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TUFClient) tryLoadCacheThenRemote(consistentInfo tuf.ConsistentInfo) ([]byte, error) {
|
||||||
|
cachedTS, err := c.cache.GetSized(consistentInfo.RoleName.String(), consistentInfo.Length())
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("no %s in cache, must download", consistentInfo.RoleName)
|
||||||
|
return c.tryLoadRemote(consistentInfo, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = c.newBuilder.Load(consistentInfo.RoleName, cachedTS, 1, false); err == nil {
|
||||||
|
logrus.Debugf("successfully verified cached %s", consistentInfo.RoleName)
|
||||||
|
return cachedTS, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("cached %s is invalid (must download): %s", consistentInfo.RoleName, err)
|
||||||
|
return c.tryLoadRemote(consistentInfo, cachedTS)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TUFClient) tryLoadRemote(consistentInfo tuf.ConsistentInfo, old []byte) ([]byte, error) {
|
||||||
|
consistentName := consistentInfo.ConsistentName()
|
||||||
|
raw, err := c.remote.GetSized(consistentName, consistentInfo.Length())
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("error downloading %s: %s", consistentName, err)
|
||||||
|
return old, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to load the old data into the old builder - only use it to validate
|
||||||
|
// versions if it loads successfully. If it errors, then the loaded version
|
||||||
|
// will be 1
|
||||||
|
c.oldBuilder.Load(consistentInfo.RoleName, old, 1, true)
|
||||||
|
minVersion := c.oldBuilder.GetLoadedVersion(consistentInfo.RoleName)
|
||||||
|
if err := c.newBuilder.Load(consistentInfo.RoleName, raw, minVersion, false); err != nil {
|
||||||
|
logrus.Debugf("downloaded %s is invalid: %s", consistentName, err)
|
||||||
|
return raw, err
|
||||||
|
}
|
||||||
|
logrus.Debugf("successfully verified downloaded %s", consistentName)
|
||||||
|
if err := c.cache.Set(consistentInfo.RoleName.String(), raw); err != nil {
|
||||||
|
logrus.Debugf("Unable to write %s to cache: %s", consistentInfo.RoleName, err)
|
||||||
|
}
|
||||||
|
return raw, nil
|
||||||
|
}
|
62
src/vendor/github.com/docker/notary/client/witness.go
generated
vendored
Normal file
62
src/vendor/github.com/docker/notary/client/witness.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/notary/client/changelist"
|
||||||
|
"github.com/docker/notary/tuf"
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Witness creates change objects to witness (i.e. re-sign) the given
|
||||||
|
// roles on the next publish. One change is created per role
|
||||||
|
func (r *NotaryRepository) Witness(roles ...data.RoleName) ([]data.RoleName, error) {
|
||||||
|
var err error
|
||||||
|
successful := make([]data.RoleName, 0, len(roles))
|
||||||
|
for _, role := range roles {
|
||||||
|
// scope is role
|
||||||
|
c := changelist.NewTUFChange(
|
||||||
|
changelist.ActionUpdate,
|
||||||
|
role,
|
||||||
|
changelist.TypeWitness,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
err = r.changelist.Add(c)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
successful = append(successful, role)
|
||||||
|
}
|
||||||
|
return successful, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func witnessTargets(repo *tuf.Repo, invalid *tuf.Repo, role data.RoleName) error {
|
||||||
|
if r, ok := repo.Targets[role]; ok {
|
||||||
|
// role is already valid, mark for re-signing/updating
|
||||||
|
r.Dirty = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if roleObj, err := repo.GetDelegationRole(role); err == nil && invalid != nil {
|
||||||
|
// A role with a threshold > len(keys) is technically invalid, but we let it build in the builder because
|
||||||
|
// we want to be able to download the role (which may still have targets on it), add more keys, and then
|
||||||
|
// witness the role, thus bringing it back to valid. However, if no keys have been added before witnessing,
|
||||||
|
// then it is still an invalid role, and can't be witnessed because nothing can bring it back to valid.
|
||||||
|
if roleObj.Threshold > len(roleObj.Keys) {
|
||||||
|
return data.ErrInvalidRole{
|
||||||
|
Role: role,
|
||||||
|
Reason: "role does not specify enough valid signing keys to meet its required threshold",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r, ok := invalid.Targets[role]; ok {
|
||||||
|
// role is recognized but invalid, move to valid data and mark for re-signing
|
||||||
|
repo.Targets[role] = r
|
||||||
|
r.Dirty = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// role isn't recognized, even as invalid
|
||||||
|
return data.ErrInvalidRole{
|
||||||
|
Role: role,
|
||||||
|
Reason: "this role is not known",
|
||||||
|
}
|
||||||
|
}
|
25
src/vendor/github.com/docker/notary/codecov.yml
generated
vendored
Normal file
25
src/vendor/github.com/docker/notary/codecov.yml
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
codecov:
|
||||||
|
notify:
|
||||||
|
# 2 builds on circleci, 1 jenkins build
|
||||||
|
after_n_builds: 3
|
||||||
|
coverage:
|
||||||
|
range: "50...100"
|
||||||
|
status:
|
||||||
|
# project will give us the diff in the total code coverage between a commit
|
||||||
|
# and its parent
|
||||||
|
project:
|
||||||
|
default:
|
||||||
|
target: auto
|
||||||
|
threshold: "0.05%"
|
||||||
|
# patch would give us the code coverage of the diff only
|
||||||
|
patch: false
|
||||||
|
# changes tells us if there are unexpected code coverage changes in other files
|
||||||
|
# which were not changed by the diff
|
||||||
|
changes: false
|
||||||
|
ignore: # ignore testutils for coverage
|
||||||
|
- "tuf/testutils/*"
|
||||||
|
- "vendor/*"
|
||||||
|
- "proto/*.pb.go"
|
||||||
|
- "trustmanager/remoteks/*.pb.go"
|
||||||
|
comment: off
|
||||||
|
|
95
src/vendor/github.com/docker/notary/const.go
generated
vendored
Normal file
95
src/vendor/github.com/docker/notary/const.go
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package notary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// application wide constants
|
||||||
|
const (
|
||||||
|
// MaxDownloadSize is the maximum size we'll download for metadata if no limit is given
|
||||||
|
MaxDownloadSize int64 = 100 << 20
|
||||||
|
// MaxTimestampSize is the maximum size of timestamp metadata - 1MiB.
|
||||||
|
MaxTimestampSize int64 = 1 << 20
|
||||||
|
// MinRSABitSize is the minimum bit size for RSA keys allowed in notary
|
||||||
|
MinRSABitSize = 2048
|
||||||
|
// MinThreshold requires a minimum of one threshold for roles; currently we do not support a higher threshold
|
||||||
|
MinThreshold = 1
|
||||||
|
// SHA256HexSize is how big a SHA256 hex is in number of characters
|
||||||
|
SHA256HexSize = 64
|
||||||
|
// SHA512HexSize is how big a SHA512 hex is in number of characters
|
||||||
|
SHA512HexSize = 128
|
||||||
|
// SHA256 is the name of SHA256 hash algorithm
|
||||||
|
SHA256 = "sha256"
|
||||||
|
// SHA512 is the name of SHA512 hash algorithm
|
||||||
|
SHA512 = "sha512"
|
||||||
|
// TrustedCertsDir is the directory, under the notary repo base directory, where trusted certs are stored
|
||||||
|
TrustedCertsDir = "trusted_certificates"
|
||||||
|
// PrivDir is the directory, under the notary repo base directory, where private keys are stored
|
||||||
|
PrivDir = "private"
|
||||||
|
// RootKeysSubdir is the subdirectory under PrivDir where root private keys are stored
|
||||||
|
// DEPRECATED: The only reason we need this constant is compatibility with older versions
|
||||||
|
RootKeysSubdir = "root_keys"
|
||||||
|
// NonRootKeysSubdir is the subdirectory under PrivDir where non-root private keys are stored
|
||||||
|
// DEPRECATED: The only reason we need this constant is compatibility with older versions
|
||||||
|
NonRootKeysSubdir = "tuf_keys"
|
||||||
|
// KeyExtension is the file extension to use for private key files
|
||||||
|
KeyExtension = "key"
|
||||||
|
|
||||||
|
// Day is a duration of one day
|
||||||
|
Day = 24 * time.Hour
|
||||||
|
Year = 365 * Day
|
||||||
|
|
||||||
|
// NotaryRootExpiry is the duration representing the expiry time of the Root role
|
||||||
|
NotaryRootExpiry = 10 * Year
|
||||||
|
NotaryTargetsExpiry = 3 * Year
|
||||||
|
NotarySnapshotExpiry = 3 * Year
|
||||||
|
NotaryTimestampExpiry = 14 * Day
|
||||||
|
|
||||||
|
ConsistentMetadataCacheMaxAge = 30 * Day
|
||||||
|
CurrentMetadataCacheMaxAge = 5 * time.Minute
|
||||||
|
// CacheMaxAgeLimit is the generally recommended maximum age for Cache-Control headers
|
||||||
|
// (one year, in seconds, since one year is forever in terms of internet
|
||||||
|
// content)
|
||||||
|
CacheMaxAgeLimit = 1 * Year
|
||||||
|
|
||||||
|
MySQLBackend = "mysql"
|
||||||
|
MemoryBackend = "memory"
|
||||||
|
PostgresBackend = "postgres"
|
||||||
|
SQLiteBackend = "sqlite3"
|
||||||
|
RethinkDBBackend = "rethinkdb"
|
||||||
|
FileBackend = "file"
|
||||||
|
|
||||||
|
DefaultImportRole = "delegation"
|
||||||
|
|
||||||
|
// HealthCheckKeyManagement and HealthCheckSigner are the grpc service name
|
||||||
|
// for "KeyManagement" and "Signer" respectively which used for health check.
|
||||||
|
// The "Overall" indicates the querying for overall status of the server.
|
||||||
|
HealthCheckKeyManagement = "grpc.health.v1.Health.KeyManagement"
|
||||||
|
HealthCheckSigner = "grpc.health.v1.Health.Signer"
|
||||||
|
HealthCheckOverall = "grpc.health.v1.Health.Overall"
|
||||||
|
|
||||||
|
// PrivExecPerms indicates the file permissions for directory
|
||||||
|
// and PrivNoExecPerms for file.
|
||||||
|
PrivExecPerms = 0700
|
||||||
|
PrivNoExecPerms = 0600
|
||||||
|
|
||||||
|
// DefaultPageSize is the default number of records to return from the changefeed
|
||||||
|
DefaultPageSize = 100
|
||||||
|
)
|
||||||
|
|
||||||
|
// enum to use for setting and retrieving values from contexts
|
||||||
|
const (
|
||||||
|
CtxKeyMetaStore CtxKey = iota
|
||||||
|
CtxKeyKeyAlgo
|
||||||
|
CtxKeyCryptoSvc
|
||||||
|
CtxKeyRepo
|
||||||
|
)
|
||||||
|
|
||||||
|
// NotarySupportedBackends contains the backends we would like to support at present
|
||||||
|
var NotarySupportedBackends = []string{
|
||||||
|
MemoryBackend,
|
||||||
|
MySQLBackend,
|
||||||
|
SQLiteBackend,
|
||||||
|
RethinkDBBackend,
|
||||||
|
PostgresBackend,
|
||||||
|
}
|
16
src/vendor/github.com/docker/notary/const_nowindows.go
generated
vendored
Normal file
16
src/vendor/github.com/docker/notary/const_nowindows.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package notary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NotarySupportedSignals contains the signals we would like to capture:
|
||||||
|
// - SIGUSR1, indicates a increment of the log level.
|
||||||
|
// - SIGUSR2, indicates a decrement of the log level.
|
||||||
|
var NotarySupportedSignals = []os.Signal{
|
||||||
|
syscall.SIGUSR1,
|
||||||
|
syscall.SIGUSR2,
|
||||||
|
}
|
8
src/vendor/github.com/docker/notary/const_windows.go
generated
vendored
Normal file
8
src/vendor/github.com/docker/notary/const_windows.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package notary
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// NotarySupportedSignals does not contain any signals, because SIGUSR1/2 are not supported on windows
|
||||||
|
var NotarySupportedSignals = []os.Signal{}
|
38
src/vendor/github.com/docker/notary/cross.Dockerfile
generated
vendored
Normal file
38
src/vendor/github.com/docker/notary/cross.Dockerfile
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
FROM golang:1.7.3
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
curl \
|
||||||
|
clang \
|
||||||
|
libltdl-dev \
|
||||||
|
libsqlite3-dev \
|
||||||
|
patch \
|
||||||
|
tar \
|
||||||
|
xz-utils \
|
||||||
|
python \
|
||||||
|
python-pip \
|
||||||
|
--no-install-recommends \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN useradd -ms /bin/bash notary \
|
||||||
|
&& pip install codecov \
|
||||||
|
&& go get github.com/golang/lint/golint github.com/fzipp/gocyclo github.com/client9/misspell/cmd/misspell github.com/gordonklaus/ineffassign github.com/HewlettPackard/gas
|
||||||
|
|
||||||
|
# Configure the container for OSX cross compilation
|
||||||
|
ENV OSX_SDK MacOSX10.11.sdk
|
||||||
|
ENV OSX_CROSS_COMMIT 8aa9b71a394905e6c5f4b59e2b97b87a004658a4
|
||||||
|
RUN set -x \
|
||||||
|
&& export OSXCROSS_PATH="/osxcross" \
|
||||||
|
&& git clone https://github.com/tpoechtrager/osxcross.git $OSXCROSS_PATH \
|
||||||
|
&& ( cd $OSXCROSS_PATH && git checkout -q $OSX_CROSS_COMMIT) \
|
||||||
|
&& curl -sSL https://s3.dockerproject.org/darwin/v2/${OSX_SDK}.tar.xz -o "${OSXCROSS_PATH}/tarballs/${OSX_SDK}.tar.xz" \
|
||||||
|
&& UNATTENDED=yes OSX_VERSION_MIN=10.6 ${OSXCROSS_PATH}/build.sh > /dev/null
|
||||||
|
ENV PATH /osxcross/target/bin:$PATH
|
||||||
|
|
||||||
|
ENV NOTARYDIR /go/src/github.com/docker/notary
|
||||||
|
|
||||||
|
COPY . ${NOTARYDIR}
|
||||||
|
RUN chmod -R a+rw /go
|
||||||
|
|
||||||
|
WORKDIR ${NOTARYDIR}
|
||||||
|
|
||||||
|
# Note this cannot use alpine because of the MacOSX Cross SDK: the cctools there uses sys/cdefs.h and that cannot be used in alpine: http://wiki.musl-libc.org/wiki/FAQ#Q:_I.27m_trying_to_compile_something_against_musl_and_I_get_error_messages_about_sys.2Fcdefs.h
|
41
src/vendor/github.com/docker/notary/cryptoservice/certificate.go
generated
vendored
Normal file
41
src/vendor/github.com/docker/notary/cryptoservice/certificate.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package cryptoservice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"github.com/docker/notary/tuf/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenerateCertificate generates an X509 Certificate from a template, given a GUN and validity interval
|
||||||
|
func GenerateCertificate(rootKey data.PrivateKey, gun data.GUN, startTime, endTime time.Time) (*x509.Certificate, error) {
|
||||||
|
signer := rootKey.CryptoSigner()
|
||||||
|
if signer == nil {
|
||||||
|
return nil, fmt.Errorf("key type not supported for Certificate generation: %s", rootKey.Algorithm())
|
||||||
|
}
|
||||||
|
|
||||||
|
return generateCertificate(signer, gun, startTime, endTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateCertificate(signer crypto.Signer, gun data.GUN, startTime, endTime time.Time) (*x509.Certificate, error) {
|
||||||
|
template, err := utils.NewCertificate(gun.String(), startTime, endTime)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create the certificate template for: %s (%v)", gun, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
derBytes, err := x509.CreateCertificate(rand.Reader, template, template, signer.Public(), signer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create the certificate for: %s (%v)", gun, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := x509.ParseCertificate(derBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse the certificate for key: %s (%v)", gun, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cert, nil
|
||||||
|
}
|
181
src/vendor/github.com/docker/notary/cryptoservice/crypto_service.go
generated
vendored
Normal file
181
src/vendor/github.com/docker/notary/cryptoservice/crypto_service.go
generated
vendored
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
package cryptoservice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/notary"
|
||||||
|
"github.com/docker/notary/trustmanager"
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"github.com/docker/notary/tuf/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrNoValidPrivateKey is returned if a key being imported doesn't
|
||||||
|
// look like a private key
|
||||||
|
ErrNoValidPrivateKey = errors.New("no valid private key found")
|
||||||
|
|
||||||
|
// ErrRootKeyNotEncrypted is returned if a root key being imported is
|
||||||
|
// unencrypted
|
||||||
|
ErrRootKeyNotEncrypted = errors.New("only encrypted root keys may be imported")
|
||||||
|
)
|
||||||
|
|
||||||
|
// CryptoService implements Sign and Create, holding a specific GUN and keystore to
|
||||||
|
// operate on
|
||||||
|
type CryptoService struct {
|
||||||
|
keyStores []trustmanager.KeyStore
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCryptoService returns an instance of CryptoService
|
||||||
|
func NewCryptoService(keyStores ...trustmanager.KeyStore) *CryptoService {
|
||||||
|
return &CryptoService{keyStores: keyStores}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create is used to generate keys for targets, snapshots and timestamps
|
||||||
|
func (cs *CryptoService) Create(role data.RoleName, gun data.GUN, algorithm string) (data.PublicKey, error) {
|
||||||
|
var privKey data.PrivateKey
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch algorithm {
|
||||||
|
case data.RSAKey:
|
||||||
|
privKey, err = utils.GenerateRSAKey(rand.Reader, notary.MinRSABitSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to generate RSA key: %v", err)
|
||||||
|
}
|
||||||
|
case data.ECDSAKey:
|
||||||
|
privKey, err = utils.GenerateECDSAKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to generate EC key: %v", err)
|
||||||
|
}
|
||||||
|
case data.ED25519Key:
|
||||||
|
privKey, err = utils.GenerateED25519Key(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to generate ED25519 key: %v", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("private key type not supported for key generation: %s", algorithm)
|
||||||
|
}
|
||||||
|
logrus.Debugf("generated new %s key for role: %s and keyID: %s", algorithm, role.String(), privKey.ID())
|
||||||
|
|
||||||
|
// Store the private key into our keystore
|
||||||
|
for _, ks := range cs.keyStores {
|
||||||
|
err = ks.AddKey(trustmanager.KeyInfo{Role: role, Gun: gun}, privKey)
|
||||||
|
if err == nil {
|
||||||
|
return data.PublicKeyFromPrivate(privKey), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to add key to filestore: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("keystores would not accept new private keys for unknown reasons")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPrivateKey returns a private key and role if present by ID.
|
||||||
|
func (cs *CryptoService) GetPrivateKey(keyID string) (k data.PrivateKey, role data.RoleName, err error) {
|
||||||
|
for _, ks := range cs.keyStores {
|
||||||
|
if k, role, err = ks.GetKey(keyID); err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch err.(type) {
|
||||||
|
case trustmanager.ErrPasswordInvalid, trustmanager.ErrAttemptsExceeded:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return // returns whatever the final values were
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKey returns a key by ID
|
||||||
|
func (cs *CryptoService) GetKey(keyID string) data.PublicKey {
|
||||||
|
privKey, _, err := cs.GetPrivateKey(keyID)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return data.PublicKeyFromPrivate(privKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKeyInfo returns role and GUN info of a key by ID
|
||||||
|
func (cs *CryptoService) GetKeyInfo(keyID string) (trustmanager.KeyInfo, error) {
|
||||||
|
for _, store := range cs.keyStores {
|
||||||
|
if info, err := store.GetKeyInfo(keyID); err == nil {
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return trustmanager.KeyInfo{}, fmt.Errorf("Could not find info for keyID %s", keyID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveKey deletes a key by ID
|
||||||
|
func (cs *CryptoService) RemoveKey(keyID string) (err error) {
|
||||||
|
for _, ks := range cs.keyStores {
|
||||||
|
ks.RemoveKey(keyID)
|
||||||
|
}
|
||||||
|
return // returns whatever the final values were
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddKey adds a private key to a specified role.
|
||||||
|
// The GUN is inferred from the cryptoservice itself for non-root roles
|
||||||
|
func (cs *CryptoService) AddKey(role data.RoleName, gun data.GUN, key data.PrivateKey) (err error) {
|
||||||
|
// First check if this key already exists in any of our keystores
|
||||||
|
for _, ks := range cs.keyStores {
|
||||||
|
if keyInfo, err := ks.GetKeyInfo(key.ID()); err == nil {
|
||||||
|
if keyInfo.Role != role {
|
||||||
|
return fmt.Errorf("key with same ID already exists for role: %s", keyInfo.Role.String())
|
||||||
|
}
|
||||||
|
logrus.Debugf("key with same ID %s and role %s already exists", key.ID(), keyInfo.Role.String())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the key didn't exist in any of our keystores, add and return on the first successful keystore
|
||||||
|
for _, ks := range cs.keyStores {
|
||||||
|
// Try to add to this keystore, return if successful
|
||||||
|
if err = ks.AddKey(trustmanager.KeyInfo{Role: role, Gun: gun}, key); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return // returns whatever the final values were
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListKeys returns a list of key IDs valid for the given role
|
||||||
|
func (cs *CryptoService) ListKeys(role data.RoleName) []string {
|
||||||
|
var res []string
|
||||||
|
for _, ks := range cs.keyStores {
|
||||||
|
for k, r := range ks.ListKeys() {
|
||||||
|
if r.Role == role {
|
||||||
|
res = append(res, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAllKeys returns a map of key IDs to role
|
||||||
|
func (cs *CryptoService) ListAllKeys() map[string]data.RoleName {
|
||||||
|
res := make(map[string]data.RoleName)
|
||||||
|
for _, ks := range cs.keyStores {
|
||||||
|
for k, r := range ks.ListKeys() {
|
||||||
|
res[k] = r.Role // keys are content addressed so don't care about overwrites
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckRootKeyIsEncrypted makes sure the root key is encrypted. We have
|
||||||
|
// internal assumptions that depend on this.
|
||||||
|
func CheckRootKeyIsEncrypted(pemBytes []byte) error {
|
||||||
|
block, _ := pem.Decode(pemBytes)
|
||||||
|
if block == nil {
|
||||||
|
return ErrNoValidPrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
if !x509.IsEncryptedPEMBlock(block) {
|
||||||
|
return ErrRootKeyNotEncrypted
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
60
src/vendor/github.com/docker/notary/development.mysql.yml
generated
vendored
Normal file
60
src/vendor/github.com/docker/notary/development.mysql.yml
generated
vendored
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
version: "2"
|
||||||
|
services:
|
||||||
|
server:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: server.Dockerfile
|
||||||
|
networks:
|
||||||
|
mdb:
|
||||||
|
sig:
|
||||||
|
srv:
|
||||||
|
aliases:
|
||||||
|
- notary-server
|
||||||
|
entrypoint: /usr/bin/env sh
|
||||||
|
command: -c "./migrations/migrate.sh && notary-server -config=fixtures/server-config.json"
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
- signer
|
||||||
|
signer:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: signer.Dockerfile
|
||||||
|
networks:
|
||||||
|
mdb:
|
||||||
|
sig:
|
||||||
|
aliases:
|
||||||
|
- notarysigner
|
||||||
|
entrypoint: /usr/bin/env sh
|
||||||
|
command: -c "./migrations/migrate.sh && notary-signer -config=fixtures/signer-config.json"
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
mysql:
|
||||||
|
networks:
|
||||||
|
- mdb
|
||||||
|
volumes:
|
||||||
|
- ./notarysql/mysql-initdb.d:/docker-entrypoint-initdb.d
|
||||||
|
image: mariadb:10.1.10
|
||||||
|
environment:
|
||||||
|
- TERM=dumb
|
||||||
|
- MYSQL_ALLOW_EMPTY_PASSWORD="true"
|
||||||
|
command: mysqld --innodb_file_per_table
|
||||||
|
client:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
env_file: buildscripts/env.list
|
||||||
|
command: buildscripts/testclient.py
|
||||||
|
volumes:
|
||||||
|
- ./test_output:/test_output
|
||||||
|
networks:
|
||||||
|
- mdb
|
||||||
|
- srv
|
||||||
|
depends_on:
|
||||||
|
- server
|
||||||
|
networks:
|
||||||
|
mdb:
|
||||||
|
external: false
|
||||||
|
sig:
|
||||||
|
external: false
|
||||||
|
srv:
|
||||||
|
external: false
|
62
src/vendor/github.com/docker/notary/development.postgresql.yml
generated
vendored
Normal file
62
src/vendor/github.com/docker/notary/development.postgresql.yml
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
version: "2"
|
||||||
|
services:
|
||||||
|
server:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: server.Dockerfile
|
||||||
|
networks:
|
||||||
|
mdb:
|
||||||
|
sig:
|
||||||
|
srv:
|
||||||
|
aliases:
|
||||||
|
- notary-server
|
||||||
|
entrypoint: /usr/bin/env sh
|
||||||
|
command: -c "./migrations/migrate.sh && notary-server -config=fixtures/server-config.postgres.json"
|
||||||
|
environment:
|
||||||
|
MIGRATIONS_PATH: migrations/server/postgresql
|
||||||
|
DB_URL: postgres://server@postgresql:5432/notaryserver?sslmode=disable
|
||||||
|
depends_on:
|
||||||
|
- postgresql
|
||||||
|
- signer
|
||||||
|
signer:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: signer.Dockerfile
|
||||||
|
networks:
|
||||||
|
mdb:
|
||||||
|
sig:
|
||||||
|
aliases:
|
||||||
|
- notarysigner
|
||||||
|
entrypoint: /usr/bin/env sh
|
||||||
|
command: -c "./migrations/migrate.sh && notary-signer -config=fixtures/signer-config.postgres.json"
|
||||||
|
environment:
|
||||||
|
MIGRATIONS_PATH: migrations/signer/postgresql
|
||||||
|
DB_URL: postgres://signer@postgresql:5432/notarysigner?sslmode=disable
|
||||||
|
depends_on:
|
||||||
|
- postgresql
|
||||||
|
postgresql:
|
||||||
|
image: postgres:9.5.4
|
||||||
|
networks:
|
||||||
|
- mdb
|
||||||
|
volumes:
|
||||||
|
- ./notarysql/postgresql-initdb.d:/docker-entrypoint-initdb.d
|
||||||
|
client:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
env_file: buildscripts/env.list
|
||||||
|
command: buildscripts/testclient.py
|
||||||
|
volumes:
|
||||||
|
- ./test_output:/test_output
|
||||||
|
networks:
|
||||||
|
- mdb
|
||||||
|
- srv
|
||||||
|
depends_on:
|
||||||
|
- server
|
||||||
|
networks:
|
||||||
|
mdb:
|
||||||
|
external: false
|
||||||
|
sig:
|
||||||
|
external: false
|
||||||
|
srv:
|
||||||
|
external: false
|
110
src/vendor/github.com/docker/notary/development.rethink.yml
generated
vendored
Normal file
110
src/vendor/github.com/docker/notary/development.rethink.yml
generated
vendored
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
version: "2"
|
||||||
|
services:
|
||||||
|
server:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: server.Dockerfile
|
||||||
|
volumes:
|
||||||
|
- ./fixtures/rethinkdb:/tls
|
||||||
|
networks:
|
||||||
|
- rdb
|
||||||
|
links:
|
||||||
|
- rdb-proxy:rdb-proxy.rdb
|
||||||
|
- signer
|
||||||
|
ports:
|
||||||
|
- "8080"
|
||||||
|
- "4443:4443"
|
||||||
|
entrypoint: /usr/bin/env sh
|
||||||
|
command: -c "sh migrations/rethink_migrate.sh && notary-server -config=fixtures/server-config.rethink.json"
|
||||||
|
depends_on:
|
||||||
|
- rdb-proxy
|
||||||
|
signer:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: signer.Dockerfile
|
||||||
|
volumes:
|
||||||
|
- ./fixtures/rethinkdb:/tls
|
||||||
|
networks:
|
||||||
|
rdb:
|
||||||
|
aliases:
|
||||||
|
- notarysigner
|
||||||
|
links:
|
||||||
|
- rdb-proxy:rdb-proxy.rdb
|
||||||
|
entrypoint: /usr/bin/env sh
|
||||||
|
command: -c "sh migrations/rethink_migrate.sh && notary-signer -config=fixtures/signer-config.rethink.json"
|
||||||
|
depends_on:
|
||||||
|
- rdb-proxy
|
||||||
|
rdb-01:
|
||||||
|
image: jlhawn/rethinkdb:2.3.4
|
||||||
|
volumes:
|
||||||
|
- ./fixtures/rethinkdb:/tls
|
||||||
|
- rdb-01-data:/var/data
|
||||||
|
networks:
|
||||||
|
rdb:
|
||||||
|
aliases:
|
||||||
|
- rdb
|
||||||
|
- rdb.rdb
|
||||||
|
- rdb-01.rdb
|
||||||
|
command: "--bind all --no-http-admin --server-name rdb_01 --canonical-address rdb-01.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls-ca /tls/ca.pem --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
|
||||||
|
rdb-02:
|
||||||
|
image: jlhawn/rethinkdb:2.3.4
|
||||||
|
volumes:
|
||||||
|
- ./fixtures/rethinkdb:/tls
|
||||||
|
- rdb-02-data:/var/data
|
||||||
|
networks:
|
||||||
|
rdb:
|
||||||
|
aliases:
|
||||||
|
- rdb
|
||||||
|
- rdb.rdb
|
||||||
|
- rdb-02.rdb
|
||||||
|
command: "--bind all --no-http-admin --server-name rdb_02 --canonical-address rdb-02.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls-ca /tls/ca.pem --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
|
||||||
|
rdb-03:
|
||||||
|
image: jlhawn/rethinkdb:2.3.4
|
||||||
|
volumes:
|
||||||
|
- ./fixtures/rethinkdb:/tls
|
||||||
|
- rdb-03-data:/var/data
|
||||||
|
networks:
|
||||||
|
rdb:
|
||||||
|
aliases:
|
||||||
|
- rdb
|
||||||
|
- rdb.rdb
|
||||||
|
- rdb-03.rdb
|
||||||
|
command: "--bind all --no-http-admin --server-name rdb_03 --canonical-address rdb-03.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls-ca /tls/ca.pem --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
|
||||||
|
rdb-proxy:
|
||||||
|
image: jlhawn/rethinkdb:2.3.4
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
volumes:
|
||||||
|
- ./fixtures/rethinkdb:/tls
|
||||||
|
networks:
|
||||||
|
rdb:
|
||||||
|
aliases:
|
||||||
|
- rdb-proxy
|
||||||
|
- rdb-proxy.rdp
|
||||||
|
command: "proxy --bind all --join rdb.rdb --driver-tls-ca /tls/ca.pem --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
|
||||||
|
depends_on:
|
||||||
|
- rdb-01
|
||||||
|
- rdb-02
|
||||||
|
- rdb-03
|
||||||
|
client:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
volumes:
|
||||||
|
- ./test_output:/test_output
|
||||||
|
networks:
|
||||||
|
- rdb
|
||||||
|
env_file: buildscripts/env.list
|
||||||
|
links:
|
||||||
|
- server:notary-server
|
||||||
|
command: buildscripts/testclient.py
|
||||||
|
volumes:
|
||||||
|
rdb-01-data:
|
||||||
|
external: false
|
||||||
|
rdb-02-data:
|
||||||
|
external: false
|
||||||
|
rdb-03-data:
|
||||||
|
external: false
|
||||||
|
networks:
|
||||||
|
rdb:
|
||||||
|
external: false
|
53
src/vendor/github.com/docker/notary/docker-compose.postgresql.yml
generated
vendored
Normal file
53
src/vendor/github.com/docker/notary/docker-compose.postgresql.yml
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
version: "2"
|
||||||
|
services:
|
||||||
|
server:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: server.Dockerfile
|
||||||
|
networks:
|
||||||
|
- mdb
|
||||||
|
- sig
|
||||||
|
ports:
|
||||||
|
- "8080"
|
||||||
|
- "4443:4443"
|
||||||
|
entrypoint: /usr/bin/env sh
|
||||||
|
command: -c "./migrations/migrate.sh && notary-server -config=fixtures/server-config.postgres.json"
|
||||||
|
environment:
|
||||||
|
MIGRATIONS_PATH: migrations/server/postgresql
|
||||||
|
DB_URL: postgres://server@postgresql:5432/notaryserver?sslmode=disable
|
||||||
|
depends_on:
|
||||||
|
- postgresql
|
||||||
|
- signer
|
||||||
|
signer:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: signer.Dockerfile
|
||||||
|
networks:
|
||||||
|
mdb:
|
||||||
|
sig:
|
||||||
|
aliases:
|
||||||
|
- notarysigner
|
||||||
|
entrypoint: /usr/bin/env sh
|
||||||
|
command: -c "./migrations/migrate.sh && notary-signer -config=fixtures/signer-config.postgres.json"
|
||||||
|
environment:
|
||||||
|
MIGRATIONS_PATH: migrations/signer/postgresql
|
||||||
|
DB_URL: postgres://signer@postgresql:5432/notarysigner?sslmode=disable
|
||||||
|
depends_on:
|
||||||
|
- postgresql
|
||||||
|
postgresql:
|
||||||
|
image: postgres:9.5.4
|
||||||
|
networks:
|
||||||
|
- mdb
|
||||||
|
volumes:
|
||||||
|
- ./notarysql/postgresql-initdb.d:/docker-entrypoint-initdb.d
|
||||||
|
- notary_data:/var/lib/postgresql
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
volumes:
|
||||||
|
notary_data:
|
||||||
|
external: false
|
||||||
|
networks:
|
||||||
|
mdb:
|
||||||
|
external: false
|
||||||
|
sig:
|
||||||
|
external: false
|
96
src/vendor/github.com/docker/notary/docker-compose.rethink.yml
generated
vendored
Normal file
96
src/vendor/github.com/docker/notary/docker-compose.rethink.yml
generated
vendored
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
version: "2"
|
||||||
|
services:
|
||||||
|
server:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: server.Dockerfile
|
||||||
|
volumes:
|
||||||
|
- ./fixtures/rethinkdb:/tls
|
||||||
|
networks:
|
||||||
|
- rdb
|
||||||
|
links:
|
||||||
|
- rdb-proxy:rdb-proxy.rdb
|
||||||
|
- signer
|
||||||
|
ports:
|
||||||
|
- "4443:4443"
|
||||||
|
entrypoint: /usr/bin/env sh
|
||||||
|
command: -c "sh migrations/rethink_migrate.sh && notary-server -config=fixtures/server-config.rethink.json"
|
||||||
|
depends_on:
|
||||||
|
- rdb-proxy
|
||||||
|
signer:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: signer.Dockerfile
|
||||||
|
volumes:
|
||||||
|
- ./fixtures/rethinkdb:/tls
|
||||||
|
networks:
|
||||||
|
rdb:
|
||||||
|
aliases:
|
||||||
|
- notarysigner
|
||||||
|
links:
|
||||||
|
- rdb-proxy:rdb-proxy.rdb
|
||||||
|
entrypoint: /usr/bin/env sh
|
||||||
|
command: -c "sh migrations/rethink_migrate.sh && notary-signer -config=fixtures/signer-config.rethink.json"
|
||||||
|
depends_on:
|
||||||
|
- rdb-proxy
|
||||||
|
rdb-01:
|
||||||
|
image: jlhawn/rethinkdb:2.3.4
|
||||||
|
volumes:
|
||||||
|
- ./fixtures/rethinkdb:/tls
|
||||||
|
- rdb-01-data:/var/data
|
||||||
|
networks:
|
||||||
|
rdb:
|
||||||
|
aliases:
|
||||||
|
- rdb-01.rdb
|
||||||
|
command: "--bind all --no-http-admin --server-name rdb_01 --canonical-address rdb-01.rdb --directory /var/data/rethinkdb --driver-tls-ca /tls/ca.pem --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
|
||||||
|
rdb-02:
|
||||||
|
image: jlhawn/rethinkdb:2.3.4
|
||||||
|
volumes:
|
||||||
|
- ./fixtures/rethinkdb:/tls
|
||||||
|
- rdb-02-data:/var/data
|
||||||
|
networks:
|
||||||
|
rdb:
|
||||||
|
aliases:
|
||||||
|
- rdb-02.rdb
|
||||||
|
command: "--bind all --no-http-admin --server-name rdb_02 --canonical-address rdb-02.rdb --directory /var/data/rethinkdb --join rdb-01 --driver-tls-ca /tls/ca.pem --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
|
||||||
|
depends_on:
|
||||||
|
- rdb-01
|
||||||
|
rdb-03:
|
||||||
|
image: jlhawn/rethinkdb:2.3.4
|
||||||
|
volumes:
|
||||||
|
- ./fixtures/rethinkdb:/tls
|
||||||
|
- rdb-03-data:/var/data
|
||||||
|
networks:
|
||||||
|
rdb:
|
||||||
|
aliases:
|
||||||
|
- rdb-03.rdb
|
||||||
|
command: "--bind all --no-http-admin --server-name rdb_03 --canonical-address rdb-03.rdb --directory /var/data/rethinkdb --join rdb-02 --driver-tls-ca /tls/ca.pem --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
|
||||||
|
depends_on:
|
||||||
|
- rdb-01
|
||||||
|
- rdb-02
|
||||||
|
rdb-proxy:
|
||||||
|
image: jlhawn/rethinkdb:2.3.4
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
volumes:
|
||||||
|
- ./fixtures/rethinkdb:/tls
|
||||||
|
networks:
|
||||||
|
rdb:
|
||||||
|
aliases:
|
||||||
|
- rdb-proxy
|
||||||
|
- rdb-proxy.rdp
|
||||||
|
command: "proxy --bind all --join rdb-03 --driver-tls-ca /tls/ca.pem --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
|
||||||
|
depends_on:
|
||||||
|
- rdb-01
|
||||||
|
- rdb-02
|
||||||
|
- rdb-03
|
||||||
|
volumes:
|
||||||
|
rdb-01-data:
|
||||||
|
external: false
|
||||||
|
rdb-02-data:
|
||||||
|
external: false
|
||||||
|
rdb-03-data:
|
||||||
|
external: false
|
||||||
|
networks:
|
||||||
|
rdb:
|
||||||
|
external: false
|
49
src/vendor/github.com/docker/notary/docker-compose.yml
generated
vendored
Normal file
49
src/vendor/github.com/docker/notary/docker-compose.yml
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
version: "2"
|
||||||
|
services:
|
||||||
|
server:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: server.Dockerfile
|
||||||
|
networks:
|
||||||
|
- mdb
|
||||||
|
- sig
|
||||||
|
ports:
|
||||||
|
- "8080"
|
||||||
|
- "4443:4443"
|
||||||
|
entrypoint: /usr/bin/env sh
|
||||||
|
command: -c "./migrations/migrate.sh && notary-server -config=fixtures/server-config.json"
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
- signer
|
||||||
|
signer:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: signer.Dockerfile
|
||||||
|
networks:
|
||||||
|
mdb:
|
||||||
|
sig:
|
||||||
|
aliases:
|
||||||
|
- notarysigner
|
||||||
|
entrypoint: /usr/bin/env sh
|
||||||
|
command: -c "./migrations/migrate.sh && notary-signer -config=fixtures/signer-config.json"
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
mysql:
|
||||||
|
networks:
|
||||||
|
- mdb
|
||||||
|
volumes:
|
||||||
|
- ./notarysql/mysql-initdb.d:/docker-entrypoint-initdb.d
|
||||||
|
- notary_data:/var/lib/mysql
|
||||||
|
image: mariadb:10.1.10
|
||||||
|
environment:
|
||||||
|
- TERM=dumb
|
||||||
|
- MYSQL_ALLOW_EMPTY_PASSWORD="true"
|
||||||
|
command: mysqld --innodb_file_per_table
|
||||||
|
volumes:
|
||||||
|
notary_data:
|
||||||
|
external: false
|
||||||
|
networks:
|
||||||
|
mdb:
|
||||||
|
external: false
|
||||||
|
sig:
|
||||||
|
external: false
|
17
src/vendor/github.com/docker/notary/escrow.Dockerfile
generated
vendored
Normal file
17
src/vendor/github.com/docker/notary/escrow.Dockerfile
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
FROM golang:1.7.3-alpine
|
||||||
|
MAINTAINER David Lawrence "david.lawrence@docker.com"
|
||||||
|
|
||||||
|
ENV NOTARYPKG github.com/docker/notary
|
||||||
|
|
||||||
|
# Copy the local repo to the expected go path
|
||||||
|
COPY . /go/src/${NOTARYPKG}
|
||||||
|
|
||||||
|
WORKDIR /go/src/${NOTARYPKG}
|
||||||
|
|
||||||
|
EXPOSE 4450
|
||||||
|
|
||||||
|
# Install escrow
|
||||||
|
RUN go install ${NOTARYPKG}/cmd/escrow
|
||||||
|
|
||||||
|
ENTRYPOINT [ "escrow" ]
|
||||||
|
CMD [ "-config=cmd/escrow/config.toml" ]
|
12
src/vendor/github.com/docker/notary/notary.go
generated
vendored
Normal file
12
src/vendor/github.com/docker/notary/notary.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package notary
|
||||||
|
|
||||||
|
// PassRetriever is a callback function that should retrieve a passphrase
|
||||||
|
// for a given named key. If it should be treated as new passphrase (e.g. with
|
||||||
|
// confirmation), createNew will be true. Attempts is passed in so that implementers
|
||||||
|
// decide how many chances to give to a human, for example.
|
||||||
|
type PassRetriever func(keyName, alias string, createNew bool, attempts int) (passphrase string, giveup bool, err error)
|
||||||
|
|
||||||
|
// CtxKey is a wrapper type for use in context.WithValue() to satisfy golint
|
||||||
|
// https://github.com/golang/go/issues/17293
|
||||||
|
// https://github.com/golang/lint/pull/245
|
||||||
|
type CtxKey int
|
26
src/vendor/github.com/docker/notary/server.Dockerfile
generated
vendored
Normal file
26
src/vendor/github.com/docker/notary/server.Dockerfile
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
FROM golang:1.7.3-alpine
|
||||||
|
MAINTAINER David Lawrence "david.lawrence@docker.com"
|
||||||
|
|
||||||
|
RUN apk add --update git gcc libc-dev && rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
# Install SQL DB migration tool
|
||||||
|
RUN go get github.com/mattes/migrate
|
||||||
|
|
||||||
|
ENV NOTARYPKG github.com/docker/notary
|
||||||
|
|
||||||
|
# Copy the local repo to the expected go path
|
||||||
|
COPY . /go/src/${NOTARYPKG}
|
||||||
|
|
||||||
|
WORKDIR /go/src/${NOTARYPKG}
|
||||||
|
|
||||||
|
ENV SERVICE_NAME=notary_server
|
||||||
|
EXPOSE 4443
|
||||||
|
|
||||||
|
# Install notary-server
|
||||||
|
RUN go install \
|
||||||
|
-tags pkcs11 \
|
||||||
|
-ldflags "-w -X ${NOTARYPKG}/version.GitCommit=`git rev-parse --short HEAD` -X ${NOTARYPKG}/version.NotaryVersion=`cat NOTARY_VERSION`" \
|
||||||
|
${NOTARYPKG}/cmd/notary-server && apk del git gcc libc-dev
|
||||||
|
|
||||||
|
ENTRYPOINT [ "notary-server" ]
|
||||||
|
CMD [ "-config=fixtures/server-config-local.json" ]
|
19
src/vendor/github.com/docker/notary/server.minimal.Dockerfile
generated
vendored
Normal file
19
src/vendor/github.com/docker/notary/server.minimal.Dockerfile
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
FROM busybox:latest
|
||||||
|
MAINTAINER David Lawrence "david.lawrence@docker.com"
|
||||||
|
|
||||||
|
# the ln is for compatibility with the docker-compose.yml, making these
|
||||||
|
# images a straight swap for the those built in the compose file.
|
||||||
|
RUN mkdir -p /usr/bin /var/lib && ln -s /bin/env /usr/bin/env
|
||||||
|
|
||||||
|
COPY ./bin/notary-server /usr/bin/notary-server
|
||||||
|
COPY ./bin/migrate /usr/bin/migrate
|
||||||
|
COPY ./bin/ld-musl-x86_64.so.1 /lib/ld-musl-x86_64.so.1
|
||||||
|
COPY ./fixtures /var/lib/notary/fixtures
|
||||||
|
COPY ./migrations /var/lib/notary/migrations
|
||||||
|
|
||||||
|
WORKDIR /var/lib/notary
|
||||||
|
ENV SERVICE_NAME=notary_server
|
||||||
|
EXPOSE 4443
|
||||||
|
|
||||||
|
ENTRYPOINT [ "/usr/bin/notary-server" ]
|
||||||
|
CMD [ "-config=/var/lib/notary/fixtures/server-config-local.json" ]
|
27
src/vendor/github.com/docker/notary/signer.Dockerfile
generated
vendored
Normal file
27
src/vendor/github.com/docker/notary/signer.Dockerfile
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
FROM golang:1.7.3-alpine
|
||||||
|
MAINTAINER David Lawrence "david.lawrence@docker.com"
|
||||||
|
|
||||||
|
RUN apk add --update git gcc libc-dev && rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
# Install SQL DB migration tool
|
||||||
|
RUN go get github.com/mattes/migrate
|
||||||
|
|
||||||
|
ENV NOTARYPKG github.com/docker/notary
|
||||||
|
|
||||||
|
# Copy the local repo to the expected go path
|
||||||
|
COPY . /go/src/${NOTARYPKG}
|
||||||
|
|
||||||
|
WORKDIR /go/src/${NOTARYPKG}
|
||||||
|
|
||||||
|
ENV SERVICE_NAME=notary_signer
|
||||||
|
ENV NOTARY_SIGNER_DEFAULT_ALIAS="timestamp_1"
|
||||||
|
ENV NOTARY_SIGNER_TIMESTAMP_1="testpassword"
|
||||||
|
|
||||||
|
# Install notary-signer
|
||||||
|
RUN go install \
|
||||||
|
-tags pkcs11 \
|
||||||
|
-ldflags "-w -X ${NOTARYPKG}/version.GitCommit=`git rev-parse --short HEAD` -X ${NOTARYPKG}/version.NotaryVersion=`cat NOTARY_VERSION`" \
|
||||||
|
${NOTARYPKG}/cmd/notary-signer && apk del git gcc libc-dev
|
||||||
|
|
||||||
|
ENTRYPOINT [ "notary-signer" ]
|
||||||
|
CMD [ "-config=fixtures/signer-config-local.json" ]
|
20
src/vendor/github.com/docker/notary/signer.minimal.Dockerfile
generated
vendored
Normal file
20
src/vendor/github.com/docker/notary/signer.minimal.Dockerfile
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
FROM busybox:latest
|
||||||
|
MAINTAINER David Lawrence "david.lawrence@docker.com"
|
||||||
|
|
||||||
|
# the ln is for compatibility with the docker-compose.yml, making these
|
||||||
|
# images a straight swap for the those built in the compose file.
|
||||||
|
RUN mkdir -p /usr/bin /var/lib && ln -s /bin/env /usr/bin/env
|
||||||
|
|
||||||
|
COPY ./bin/notary-signer /usr/bin/notary-signer
|
||||||
|
COPY ./bin/migrate /usr/bin/migrate
|
||||||
|
COPY ./bin/ld-musl-x86_64.so.1 /lib/ld-musl-x86_64.so.1
|
||||||
|
COPY ./fixtures /var/lib/notary/fixtures
|
||||||
|
COPY ./migrations /var/lib/notary/migrations
|
||||||
|
|
||||||
|
WORKDIR /var/lib/notary
|
||||||
|
ENV SERVICE_NAME=notary_signer
|
||||||
|
ENV NOTARY_SIGNER_DEFAULT_ALIAS="timestamp_1"
|
||||||
|
ENV NOTARY_SIGNER_TIMESTAMP_1="testpassword"
|
||||||
|
|
||||||
|
ENTRYPOINT [ "/usr/bin/notary-signer" ]
|
||||||
|
CMD [ "-config=/var/lib/notary/fixtures/signer-config-local.json" ]
|
22
src/vendor/github.com/docker/notary/storage/errors.go
generated
vendored
Normal file
22
src/vendor/github.com/docker/notary/storage/errors.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrPathOutsideStore indicates that the returned path would be
|
||||||
|
// outside the store
|
||||||
|
ErrPathOutsideStore = errors.New("path outside file store")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrMetaNotFound indicates we did not find a particular piece
|
||||||
|
// of metadata in the store
|
||||||
|
type ErrMetaNotFound struct {
|
||||||
|
Resource string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrMetaNotFound) Error() string {
|
||||||
|
return fmt.Sprintf("%s trust data unavailable. Has a notary repository been initialized?", err.Resource)
|
||||||
|
}
|
279
src/vendor/github.com/docker/notary/storage/filestore.go
generated
vendored
Normal file
279
src/vendor/github.com/docker/notary/storage/filestore.go
generated
vendored
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/notary"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewFileStore creates a fully configurable file store
|
||||||
|
func NewFileStore(baseDir, fileExt string) (*FilesystemStore, error) {
|
||||||
|
baseDir = filepath.Clean(baseDir)
|
||||||
|
if err := createDirectory(baseDir, notary.PrivExecPerms); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(fileExt, ".") {
|
||||||
|
fileExt = "." + fileExt
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FilesystemStore{
|
||||||
|
baseDir: baseDir,
|
||||||
|
ext: fileExt,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPrivateKeyFileStorage initializes a new filestore for private keys, appending
|
||||||
|
// the notary.PrivDir to the baseDir.
|
||||||
|
func NewPrivateKeyFileStorage(baseDir, fileExt string) (*FilesystemStore, error) {
|
||||||
|
baseDir = filepath.Join(baseDir, notary.PrivDir)
|
||||||
|
myStore, err := NewFileStore(baseDir, fileExt)
|
||||||
|
myStore.migrateTo0Dot4()
|
||||||
|
return myStore, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPrivateSimpleFileStore is a wrapper to create an owner readable/writeable
|
||||||
|
// _only_ filestore
|
||||||
|
func NewPrivateSimpleFileStore(baseDir, fileExt string) (*FilesystemStore, error) {
|
||||||
|
return NewFileStore(baseDir, fileExt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilesystemStore is a store in a locally accessible directory
|
||||||
|
type FilesystemStore struct {
|
||||||
|
baseDir string
|
||||||
|
ext string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FilesystemStore) moveKeyTo0Dot4Location(file string) {
|
||||||
|
keyID := filepath.Base(file)
|
||||||
|
fileDir := filepath.Dir(file)
|
||||||
|
d, _ := f.Get(file)
|
||||||
|
block, _ := pem.Decode(d)
|
||||||
|
if block == nil {
|
||||||
|
logrus.Warn("Key data for", file, "could not be decoded as a valid PEM block. The key will not been migrated and may not be available")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fileDir = strings.TrimPrefix(fileDir, notary.RootKeysSubdir)
|
||||||
|
fileDir = strings.TrimPrefix(fileDir, notary.NonRootKeysSubdir)
|
||||||
|
if fileDir != "" {
|
||||||
|
block.Headers["gun"] = fileDir[1:]
|
||||||
|
}
|
||||||
|
if strings.Contains(keyID, "_") {
|
||||||
|
role := strings.Split(keyID, "_")[1]
|
||||||
|
keyID = strings.TrimSuffix(keyID, "_"+role)
|
||||||
|
block.Headers["role"] = role
|
||||||
|
}
|
||||||
|
var keyPEM bytes.Buffer
|
||||||
|
// since block came from decoding the PEM bytes in the first place, and all we're doing is adding some headers we ignore the possibility of an error while encoding the block
|
||||||
|
pem.Encode(&keyPEM, block)
|
||||||
|
f.Set(keyID, keyPEM.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FilesystemStore) migrateTo0Dot4() {
|
||||||
|
rootKeysSubDir := filepath.Clean(filepath.Join(f.Location(), notary.RootKeysSubdir))
|
||||||
|
nonRootKeysSubDir := filepath.Clean(filepath.Join(f.Location(), notary.NonRootKeysSubdir))
|
||||||
|
if _, err := os.Stat(rootKeysSubDir); !os.IsNotExist(err) && f.Location() != rootKeysSubDir {
|
||||||
|
if rootKeysSubDir == "" || rootKeysSubDir == "/" {
|
||||||
|
// making sure we don't remove a user's homedir
|
||||||
|
logrus.Warn("The directory for root keys is an unsafe value, we are not going to delete the directory. Please delete it manually")
|
||||||
|
} else {
|
||||||
|
// root_keys exists, migrate things from it
|
||||||
|
listOnlyRootKeysDirStore, _ := NewFileStore(rootKeysSubDir, f.ext)
|
||||||
|
for _, file := range listOnlyRootKeysDirStore.ListFiles() {
|
||||||
|
f.moveKeyTo0Dot4Location(filepath.Join(notary.RootKeysSubdir, file))
|
||||||
|
}
|
||||||
|
// delete the old directory
|
||||||
|
os.RemoveAll(rootKeysSubDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(nonRootKeysSubDir); !os.IsNotExist(err) && f.Location() != nonRootKeysSubDir {
|
||||||
|
if nonRootKeysSubDir == "" || nonRootKeysSubDir == "/" {
|
||||||
|
// making sure we don't remove a user's homedir
|
||||||
|
logrus.Warn("The directory for non root keys is an unsafe value, we are not going to delete the directory. Please delete it manually")
|
||||||
|
} else {
|
||||||
|
// tuf_keys exists, migrate things from it
|
||||||
|
listOnlyNonRootKeysDirStore, _ := NewFileStore(nonRootKeysSubDir, f.ext)
|
||||||
|
for _, file := range listOnlyNonRootKeysDirStore.ListFiles() {
|
||||||
|
f.moveKeyTo0Dot4Location(filepath.Join(notary.NonRootKeysSubdir, file))
|
||||||
|
}
|
||||||
|
// delete the old directory
|
||||||
|
os.RemoveAll(nonRootKeysSubDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we have a trusted_certificates folder, let's delete for a complete migration since it is unused by new clients
|
||||||
|
certsSubDir := filepath.Join(f.Location(), "trusted_certificates")
|
||||||
|
if certsSubDir == "" || certsSubDir == "/" {
|
||||||
|
logrus.Warn("The directory for trusted certificate is an unsafe value, we are not going to delete the directory. Please delete it manually")
|
||||||
|
} else {
|
||||||
|
os.RemoveAll(certsSubDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FilesystemStore) getPath(name string) (string, error) {
|
||||||
|
fileName := fmt.Sprintf("%s%s", name, f.ext)
|
||||||
|
fullPath := filepath.Join(f.baseDir, fileName)
|
||||||
|
|
||||||
|
if !strings.HasPrefix(fullPath, f.baseDir) {
|
||||||
|
return "", ErrPathOutsideStore
|
||||||
|
}
|
||||||
|
return fullPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSized returns the meta for the given name (a role) up to size bytes
|
||||||
|
// If size is "NoSizeLimit", this corresponds to "infinite," but we cut off at a
|
||||||
|
// predefined threshold "notary.MaxDownloadSize". If the file is larger than size
|
||||||
|
// we return ErrMaliciousServer for consistency with the HTTPStore
|
||||||
|
func (f *FilesystemStore) GetSized(name string, size int64) ([]byte, error) {
|
||||||
|
p, err := f.getPath(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
file, err := os.OpenFile(p, os.O_RDONLY, notary.PrivNoExecPerms)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = ErrMetaNotFound{Resource: name}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
if size == NoSizeLimit {
|
||||||
|
size = notary.MaxDownloadSize
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, err := file.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if stat.Size() > size {
|
||||||
|
return nil, ErrMaliciousServer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
l := io.LimitReader(file, size)
|
||||||
|
return ioutil.ReadAll(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the meta for the given name.
|
||||||
|
func (f *FilesystemStore) Get(name string) ([]byte, error) {
|
||||||
|
p, err := f.getPath(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
meta, err := ioutil.ReadFile(p)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = ErrMetaNotFound{Resource: name}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return meta, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMulti sets the metadata for multiple roles in one operation
|
||||||
|
func (f *FilesystemStore) SetMulti(metas map[string][]byte) error {
|
||||||
|
for role, blob := range metas {
|
||||||
|
err := f.Set(role, blob)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the meta for a single role
|
||||||
|
func (f *FilesystemStore) Set(name string, meta []byte) error {
|
||||||
|
fp, err := f.getPath(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensures the parent directories of the file we are about to write exist
|
||||||
|
err = os.MkdirAll(filepath.Dir(fp), notary.PrivExecPerms)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if something already exists, just delete it and re-write it
|
||||||
|
os.RemoveAll(fp)
|
||||||
|
|
||||||
|
// Write the file to disk
|
||||||
|
if err = ioutil.WriteFile(fp, meta, notary.PrivNoExecPerms); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAll clears the existing filestore by removing its base directory
|
||||||
|
func (f *FilesystemStore) RemoveAll() error {
|
||||||
|
return os.RemoveAll(f.baseDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove removes the metadata for a single role - if the metadata doesn't
|
||||||
|
// exist, no error is returned
|
||||||
|
func (f *FilesystemStore) Remove(name string) error {
|
||||||
|
p, err := f.getPath(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.RemoveAll(p) // RemoveAll succeeds if path doesn't exist
|
||||||
|
}
|
||||||
|
|
||||||
|
// Location returns a human readable name for the storage location
|
||||||
|
func (f FilesystemStore) Location() string {
|
||||||
|
return f.baseDir
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListFiles returns a list of all the filenames that can be used with Get*
|
||||||
|
// to retrieve content from this filestore
|
||||||
|
func (f FilesystemStore) ListFiles() []string {
|
||||||
|
files := make([]string, 0, 0)
|
||||||
|
filepath.Walk(f.baseDir, func(fp string, fi os.FileInfo, err error) error {
|
||||||
|
// If there are errors, ignore this particular file
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Ignore if it is a directory
|
||||||
|
if fi.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a symlink, ignore it
|
||||||
|
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only allow matches that end with our certificate extension (e.g. *.crt)
|
||||||
|
matched, _ := filepath.Match("*"+f.ext, fi.Name())
|
||||||
|
|
||||||
|
if matched {
|
||||||
|
// Find the relative path for this file relative to the base path.
|
||||||
|
fp, err = filepath.Rel(f.baseDir, fp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
trimmed := strings.TrimSuffix(fp, f.ext)
|
||||||
|
files = append(files, trimmed)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
|
// createDirectory receives a string of the path to a directory.
|
||||||
|
// It does not support passing files, so the caller has to remove
|
||||||
|
// the filename by doing filepath.Dir(full_path_to_file)
|
||||||
|
func createDirectory(dir string, perms os.FileMode) error {
|
||||||
|
// This prevents someone passing /path/to/dir and 'dir' not being created
|
||||||
|
// If two '//' exist, MkdirAll deals it with correctly
|
||||||
|
dir = dir + "/"
|
||||||
|
return os.MkdirAll(dir, perms)
|
||||||
|
}
|
355
src/vendor/github.com/docker/notary/storage/httpstore.go
generated
vendored
Normal file
355
src/vendor/github.com/docker/notary/storage/httpstore.go
generated
vendored
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
// A Store that can fetch and set metadata on a remote server.
|
||||||
|
// Some API constraints:
|
||||||
|
// - Response bodies for error codes should be unmarshallable as:
|
||||||
|
// {"errors": [{..., "detail": <serialized validation error>}]}
|
||||||
|
// else validation error details, etc. will be unparsable. The errors
|
||||||
|
// should have a github.com/docker/notary/tuf/validation/SerializableError
|
||||||
|
// in the Details field.
|
||||||
|
// If writing your own server, please have a look at
|
||||||
|
// github.com/docker/distribution/registry/api/errcode
|
||||||
|
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/notary"
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"github.com/docker/notary/tuf/validation"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrServerUnavailable indicates an error from the server. code allows us to
|
||||||
|
// populate the http error we received
|
||||||
|
type ErrServerUnavailable struct {
|
||||||
|
code int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkError represents any kind of network error when attempting to make a request
|
||||||
|
type NetworkError struct {
|
||||||
|
Wrapped error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n NetworkError) Error() string {
|
||||||
|
if _, ok := n.Wrapped.(*url.Error); ok {
|
||||||
|
// QueryUnescape does the inverse transformation of QueryEscape,
|
||||||
|
// converting %AB into the byte 0xAB and '+' into ' ' (space).
|
||||||
|
// It returns an error if any % is not followed by two hexadecimal digits.
|
||||||
|
//
|
||||||
|
// If this happens, we log out the QueryUnescape error and return the
|
||||||
|
// original error to client.
|
||||||
|
res, err := url.QueryUnescape(n.Wrapped.Error())
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("unescape network error message failed: %s", err)
|
||||||
|
return n.Wrapped.Error()
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
return n.Wrapped.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrServerUnavailable) Error() string {
|
||||||
|
if err.code == 401 {
|
||||||
|
return fmt.Sprintf("you are not authorized to perform this operation: server returned 401.")
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("unable to reach trust server at this time: %d.", err.code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrMaliciousServer indicates the server returned a response that is highly suspected
|
||||||
|
// of being malicious. i.e. it attempted to send us more data than the known size of a
|
||||||
|
// particular role metadata.
|
||||||
|
type ErrMaliciousServer struct{}
|
||||||
|
|
||||||
|
func (err ErrMaliciousServer) Error() string {
|
||||||
|
return "trust server returned a bad response."
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrInvalidOperation indicates that the server returned a 400 response and
|
||||||
|
// propagate any body we received.
|
||||||
|
type ErrInvalidOperation struct {
|
||||||
|
msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrInvalidOperation) Error() string {
|
||||||
|
if err.msg != "" {
|
||||||
|
return fmt.Sprintf("trust server rejected operation: %s", err.msg)
|
||||||
|
}
|
||||||
|
return "trust server rejected operation."
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPStore manages pulling and pushing metadata from and to a remote
|
||||||
|
// service over HTTP. It assumes the URL structure of the remote service
|
||||||
|
// maps identically to the structure of the TUF repo:
|
||||||
|
// <baseURL>/<metaPrefix>/(root|targets|snapshot|timestamp).json
|
||||||
|
// <baseURL>/<targetsPrefix>/foo.sh
|
||||||
|
//
|
||||||
|
// If consistent snapshots are disabled, it is advised that caching is not
|
||||||
|
// enabled. Simple set a cachePath (and ensure it's writeable) to enable
|
||||||
|
// caching.
|
||||||
|
type HTTPStore struct {
|
||||||
|
baseURL url.URL
|
||||||
|
metaPrefix string
|
||||||
|
metaExtension string
|
||||||
|
keyExtension string
|
||||||
|
roundTrip http.RoundTripper
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPStore initializes a new store against a URL and a number of configuration options
|
||||||
|
func NewHTTPStore(baseURL, metaPrefix, metaExtension, keyExtension string, roundTrip http.RoundTripper) (RemoteStore, error) {
|
||||||
|
base, err := url.Parse(baseURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !base.IsAbs() {
|
||||||
|
return nil, errors.New("HTTPStore requires an absolute baseURL")
|
||||||
|
}
|
||||||
|
if roundTrip == nil {
|
||||||
|
return &OfflineStore{}, nil
|
||||||
|
}
|
||||||
|
return &HTTPStore{
|
||||||
|
baseURL: *base,
|
||||||
|
metaPrefix: metaPrefix,
|
||||||
|
metaExtension: metaExtension,
|
||||||
|
keyExtension: keyExtension,
|
||||||
|
roundTrip: roundTrip,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryUnmarshalError(resp *http.Response, defaultError error) error {
|
||||||
|
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return defaultError
|
||||||
|
}
|
||||||
|
var parsedErrors struct {
|
||||||
|
Errors []struct {
|
||||||
|
Detail validation.SerializableError `json:"detail"`
|
||||||
|
} `json:"errors"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(bodyBytes, &parsedErrors); err != nil {
|
||||||
|
return defaultError
|
||||||
|
}
|
||||||
|
if len(parsedErrors.Errors) != 1 {
|
||||||
|
return defaultError
|
||||||
|
}
|
||||||
|
err = parsedErrors.Errors[0].Detail.Error
|
||||||
|
if err == nil {
|
||||||
|
return defaultError
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func translateStatusToError(resp *http.Response, resource string) error {
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case http.StatusOK:
|
||||||
|
return nil
|
||||||
|
case http.StatusNotFound:
|
||||||
|
return ErrMetaNotFound{Resource: resource}
|
||||||
|
case http.StatusBadRequest:
|
||||||
|
return tryUnmarshalError(resp, ErrInvalidOperation{})
|
||||||
|
default:
|
||||||
|
return ErrServerUnavailable{code: resp.StatusCode}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSized downloads the named meta file with the given size. A short body
|
||||||
|
// is acceptable because in the case of timestamp.json, the size is a cap,
|
||||||
|
// not an exact length.
|
||||||
|
// If size is "NoSizeLimit", this corresponds to "infinite," but we cut off at a
|
||||||
|
// predefined threshold "notary.MaxDownloadSize".
|
||||||
|
func (s HTTPStore) GetSized(name string, size int64) ([]byte, error) {
|
||||||
|
url, err := s.buildMetaURL(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("GET", url.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp, err := s.roundTrip.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, NetworkError{Wrapped: err}
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if err := translateStatusToError(resp, name); err != nil {
|
||||||
|
logrus.Debugf("received HTTP status %d when requesting %s.", resp.StatusCode, name)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if size == NoSizeLimit {
|
||||||
|
size = notary.MaxDownloadSize
|
||||||
|
}
|
||||||
|
if resp.ContentLength > size {
|
||||||
|
return nil, ErrMaliciousServer{}
|
||||||
|
}
|
||||||
|
logrus.Debugf("%d when retrieving metadata for %s", resp.StatusCode, name)
|
||||||
|
b := io.LimitReader(resp.Body, size)
|
||||||
|
body, err := ioutil.ReadAll(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sends a single piece of metadata to the TUF server
|
||||||
|
func (s HTTPStore) Set(name string, blob []byte) error {
|
||||||
|
return s.SetMulti(map[string][]byte{name: blob})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove always fails, because we should never be able to delete metadata
|
||||||
|
// remotely
|
||||||
|
func (s HTTPStore) Remove(name string) error {
|
||||||
|
return ErrInvalidOperation{msg: "cannot delete individual metadata files"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMultiPartMetaRequest builds a request with the provided metadata updates
|
||||||
|
// in multipart form
|
||||||
|
func NewMultiPartMetaRequest(url string, metas map[string][]byte) (*http.Request, error) {
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
for role, blob := range metas {
|
||||||
|
part, err := writer.CreateFormFile("files", role)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(part, bytes.NewBuffer(blob))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("POST", url, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMulti does a single batch upload of multiple pieces of TUF metadata.
|
||||||
|
// This should be preferred for updating a remote server as it enable the server
|
||||||
|
// to remain consistent, either accepting or rejecting the complete update.
|
||||||
|
func (s HTTPStore) SetMulti(metas map[string][]byte) error {
|
||||||
|
url, err := s.buildMetaURL("")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req, err := NewMultiPartMetaRequest(url.String(), metas)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resp, err := s.roundTrip.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
return NetworkError{Wrapped: err}
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
// if this 404's something is pretty wrong
|
||||||
|
return translateStatusToError(resp, "POST metadata endpoint")
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAll will attempt to delete all TUF metadata for a GUN
|
||||||
|
func (s HTTPStore) RemoveAll() error {
|
||||||
|
url, err := s.buildMetaURL("")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("DELETE", url.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resp, err := s.roundTrip.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
return NetworkError{Wrapped: err}
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
return translateStatusToError(resp, "DELETE metadata for GUN endpoint")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s HTTPStore) buildMetaURL(name string) (*url.URL, error) {
|
||||||
|
var filename string
|
||||||
|
if name != "" {
|
||||||
|
filename = fmt.Sprintf("%s.%s", name, s.metaExtension)
|
||||||
|
}
|
||||||
|
uri := path.Join(s.metaPrefix, filename)
|
||||||
|
return s.buildURL(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s HTTPStore) buildKeyURL(name data.RoleName) (*url.URL, error) {
|
||||||
|
filename := fmt.Sprintf("%s.%s", name.String(), s.keyExtension)
|
||||||
|
uri := path.Join(s.metaPrefix, filename)
|
||||||
|
return s.buildURL(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s HTTPStore) buildURL(uri string) (*url.URL, error) {
|
||||||
|
sub, err := url.Parse(uri)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s.baseURL.ResolveReference(sub), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKey retrieves a public key from the remote server
|
||||||
|
func (s HTTPStore) GetKey(role data.RoleName) ([]byte, error) {
|
||||||
|
url, err := s.buildKeyURL(role)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("GET", url.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp, err := s.roundTrip.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, NetworkError{Wrapped: err}
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if err := translateStatusToError(resp, role.String()+" key"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RotateKey rotates a private key and returns the public component from the remote server
|
||||||
|
func (s HTTPStore) RotateKey(role data.RoleName) ([]byte, error) {
|
||||||
|
url, err := s.buildKeyURL(role)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("POST", url.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp, err := s.roundTrip.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, NetworkError{Wrapped: err}
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if err := translateStatusToError(resp, role.String()+" key"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Location returns a human readable name for the storage location
|
||||||
|
func (s HTTPStore) Location() string {
|
||||||
|
return s.baseURL.String()
|
||||||
|
}
|
38
src/vendor/github.com/docker/notary/storage/interfaces.go
generated
vendored
Normal file
38
src/vendor/github.com/docker/notary/storage/interfaces.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NoSizeLimit is represented as -1 for arguments to GetMeta
|
||||||
|
const NoSizeLimit int64 = -1
|
||||||
|
|
||||||
|
// MetadataStore must be implemented by anything that intends to interact
|
||||||
|
// with a store of TUF files
|
||||||
|
type MetadataStore interface {
|
||||||
|
GetSized(name string, size int64) ([]byte, error)
|
||||||
|
Set(name string, blob []byte) error
|
||||||
|
SetMulti(map[string][]byte) error
|
||||||
|
RemoveAll() error
|
||||||
|
Remove(name string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublicKeyStore must be implemented by a key service
|
||||||
|
type PublicKeyStore interface {
|
||||||
|
GetKey(role data.RoleName) ([]byte, error)
|
||||||
|
RotateKey(role data.RoleName) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteStore is similar to LocalStore with the added expectation that it should
|
||||||
|
// provide a way to download targets once located
|
||||||
|
type RemoteStore interface {
|
||||||
|
MetadataStore
|
||||||
|
PublicKeyStore
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bootstrapper is a thing that can set itself up
|
||||||
|
type Bootstrapper interface {
|
||||||
|
// Bootstrap instructs a configured Bootstrapper to perform
|
||||||
|
// its setup operations.
|
||||||
|
Bootstrap() error
|
||||||
|
}
|
137
src/vendor/github.com/docker/notary/storage/memorystore.go
generated
vendored
Normal file
137
src/vendor/github.com/docker/notary/storage/memorystore.go
generated
vendored
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/notary"
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"github.com/docker/notary/tuf/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewMemoryStore returns a MetadataStore that operates entirely in memory.
|
||||||
|
// Very useful for testing
|
||||||
|
func NewMemoryStore(seed map[data.RoleName][]byte) *MemoryStore {
|
||||||
|
var (
|
||||||
|
consistent = make(map[string][]byte)
|
||||||
|
initial = make(map[string][]byte)
|
||||||
|
)
|
||||||
|
// add all seed meta to consistent
|
||||||
|
for name, d := range seed {
|
||||||
|
checksum := sha256.Sum256(d)
|
||||||
|
path := utils.ConsistentName(name.String(), checksum[:])
|
||||||
|
initial[name.String()] = d
|
||||||
|
consistent[path] = d
|
||||||
|
}
|
||||||
|
|
||||||
|
return &MemoryStore{
|
||||||
|
data: initial,
|
||||||
|
consistent: consistent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemoryStore implements a mock RemoteStore entirely in memory.
|
||||||
|
// For testing purposes only.
|
||||||
|
type MemoryStore struct {
|
||||||
|
data map[string][]byte
|
||||||
|
consistent map[string][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSized returns up to size bytes of data references by name.
|
||||||
|
// If size is "NoSizeLimit", this corresponds to "infinite," but we cut off at a
|
||||||
|
// predefined threshold "notary.MaxDownloadSize", as we will always know the
|
||||||
|
// size for everything but a timestamp and sometimes a root,
|
||||||
|
// neither of which should be exceptionally large
|
||||||
|
func (m MemoryStore) GetSized(name string, size int64) ([]byte, error) {
|
||||||
|
d, ok := m.data[name]
|
||||||
|
if ok {
|
||||||
|
if size == NoSizeLimit {
|
||||||
|
size = notary.MaxDownloadSize
|
||||||
|
}
|
||||||
|
if int64(len(d)) < size {
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
return d[:size], nil
|
||||||
|
}
|
||||||
|
d, ok = m.consistent[name]
|
||||||
|
if ok {
|
||||||
|
if int64(len(d)) < size {
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
return d[:size], nil
|
||||||
|
}
|
||||||
|
return nil, ErrMetaNotFound{Resource: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the data associated with name
|
||||||
|
func (m MemoryStore) Get(name string) ([]byte, error) {
|
||||||
|
if d, ok := m.data[name]; ok {
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
if d, ok := m.consistent[name]; ok {
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
return nil, ErrMetaNotFound{Resource: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the metadata value for the given name
|
||||||
|
func (m *MemoryStore) Set(name string, meta []byte) error {
|
||||||
|
m.data[name] = meta
|
||||||
|
|
||||||
|
parsedMeta := &data.SignedMeta{}
|
||||||
|
err := json.Unmarshal(meta, parsedMeta)
|
||||||
|
if err == nil {
|
||||||
|
// no parse error means this is metadata and not a key, so store by version
|
||||||
|
version := parsedMeta.Signed.Version
|
||||||
|
versionedName := fmt.Sprintf("%d.%s", version, name)
|
||||||
|
m.data[versionedName] = meta
|
||||||
|
}
|
||||||
|
|
||||||
|
checksum := sha256.Sum256(meta)
|
||||||
|
path := utils.ConsistentName(name, checksum[:])
|
||||||
|
m.consistent[path] = meta
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMulti sets multiple pieces of metadata for multiple names
|
||||||
|
// in a single operation.
|
||||||
|
func (m *MemoryStore) SetMulti(metas map[string][]byte) error {
|
||||||
|
for role, blob := range metas {
|
||||||
|
m.Set(role, blob)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove removes the metadata for a single role - if the metadata doesn't
|
||||||
|
// exist, no error is returned
|
||||||
|
func (m *MemoryStore) Remove(name string) error {
|
||||||
|
if meta, ok := m.data[name]; ok {
|
||||||
|
checksum := sha256.Sum256(meta)
|
||||||
|
path := utils.ConsistentName(name, checksum[:])
|
||||||
|
delete(m.data, name)
|
||||||
|
delete(m.consistent, path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAll clears the existing memory store by setting this store as new empty one
|
||||||
|
func (m *MemoryStore) RemoveAll() error {
|
||||||
|
*m = *NewMemoryStore(nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Location provides a human readable name for the storage location
|
||||||
|
func (m MemoryStore) Location() string {
|
||||||
|
return "memory"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListFiles returns a list of all files. The names returned should be
|
||||||
|
// usable with Get directly, with no modification.
|
||||||
|
func (m *MemoryStore) ListFiles() []string {
|
||||||
|
names := make([]string, 0, len(m.data))
|
||||||
|
for n := range m.data {
|
||||||
|
names = append(names, n)
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
58
src/vendor/github.com/docker/notary/storage/offlinestore.go
generated
vendored
Normal file
58
src/vendor/github.com/docker/notary/storage/offlinestore.go
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrOffline is used to indicate we are operating offline
|
||||||
|
type ErrOffline struct{}
|
||||||
|
|
||||||
|
func (e ErrOffline) Error() string {
|
||||||
|
return "client is offline"
|
||||||
|
}
|
||||||
|
|
||||||
|
var err = ErrOffline{}
|
||||||
|
|
||||||
|
// OfflineStore is to be used as a placeholder for a nil store. It simply
|
||||||
|
// returns ErrOffline for every operation
|
||||||
|
type OfflineStore struct{}
|
||||||
|
|
||||||
|
// GetSized returns ErrOffline
|
||||||
|
func (es OfflineStore) GetSized(name string, size int64) ([]byte, error) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set returns ErrOffline
|
||||||
|
func (es OfflineStore) Set(name string, blob []byte) error {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMulti returns ErrOffline
|
||||||
|
func (es OfflineStore) SetMulti(map[string][]byte) error {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove returns ErrOffline
|
||||||
|
func (es OfflineStore) Remove(name string) error {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKey returns ErrOffline
|
||||||
|
func (es OfflineStore) GetKey(role data.RoleName) ([]byte, error) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RotateKey returns ErrOffline
|
||||||
|
func (es OfflineStore) RotateKey(role data.RoleName) ([]byte, error) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAll return ErrOffline
|
||||||
|
func (es OfflineStore) RemoveAll() error {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Location returns a human readable name for the storage location
|
||||||
|
func (es OfflineStore) Location() string {
|
||||||
|
return "offline"
|
||||||
|
}
|
31
src/vendor/github.com/docker/notary/trustmanager/errors.go
generated
vendored
Normal file
31
src/vendor/github.com/docker/notary/trustmanager/errors.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package trustmanager
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// ErrAttemptsExceeded is returned when too many attempts have been made to decrypt a key
|
||||||
|
type ErrAttemptsExceeded struct{}
|
||||||
|
|
||||||
|
// ErrAttemptsExceeded is returned when too many attempts have been made to decrypt a key
|
||||||
|
func (err ErrAttemptsExceeded) Error() string {
|
||||||
|
return "maximum number of passphrase attempts exceeded"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrPasswordInvalid is returned when signing fails. It could also mean the signing
|
||||||
|
// key file was corrupted, but we have no way to distinguish.
|
||||||
|
type ErrPasswordInvalid struct{}
|
||||||
|
|
||||||
|
// ErrPasswordInvalid is returned when signing fails. It could also mean the signing
|
||||||
|
// key file was corrupted, but we have no way to distinguish.
|
||||||
|
func (err ErrPasswordInvalid) Error() string {
|
||||||
|
return "password invalid, operation has failed."
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrKeyNotFound is returned when the keystore fails to retrieve a specific key.
|
||||||
|
type ErrKeyNotFound struct {
|
||||||
|
KeyID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrKeyNotFound is returned when the keystore fails to retrieve a specific key.
|
||||||
|
func (err ErrKeyNotFound) Error() string {
|
||||||
|
return fmt.Sprintf("signing key not found: %s", err.KeyID)
|
||||||
|
}
|
54
src/vendor/github.com/docker/notary/trustmanager/interfaces.go
generated
vendored
Normal file
54
src/vendor/github.com/docker/notary/trustmanager/interfaces.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package trustmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Storage implements the bare bones primitives (no hierarchy)
|
||||||
|
type Storage interface {
|
||||||
|
// Add writes a file to the specified location, returning an error if this
|
||||||
|
// is not possible (reasons may include permissions errors). The path is cleaned
|
||||||
|
// before being made absolute against the store's base dir.
|
||||||
|
Set(fileName string, data []byte) error
|
||||||
|
|
||||||
|
// Remove deletes a file from the store relative to the store's base directory.
|
||||||
|
// The path is cleaned before being made absolute to ensure no path traversal
|
||||||
|
// outside the base directory is possible.
|
||||||
|
Remove(fileName string) error
|
||||||
|
|
||||||
|
// Get returns the file content found at fileName relative to the base directory
|
||||||
|
// of the file store. The path is cleaned before being made absolute to ensure
|
||||||
|
// path traversal outside the store is not possible. If the file is not found
|
||||||
|
// an error to that effect is returned.
|
||||||
|
Get(fileName string) ([]byte, error)
|
||||||
|
|
||||||
|
// ListFiles returns a list of paths relative to the base directory of the
|
||||||
|
// filestore. Any of these paths must be retrievable via the
|
||||||
|
// Storage.Get method.
|
||||||
|
ListFiles() []string
|
||||||
|
|
||||||
|
// Location returns a human readable name indicating where the implementer
|
||||||
|
// is storing keys
|
||||||
|
Location() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyInfo stores the role and gun for a corresponding private key ID
|
||||||
|
// It is assumed that each private key ID is unique
|
||||||
|
type KeyInfo struct {
|
||||||
|
Gun data.GUN
|
||||||
|
Role data.RoleName
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyStore is a generic interface for private key storage
|
||||||
|
type KeyStore interface {
|
||||||
|
// AddKey adds a key to the KeyStore, and if the key already exists,
|
||||||
|
// succeeds. Otherwise, returns an error if it cannot add.
|
||||||
|
AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error
|
||||||
|
// Should fail with ErrKeyNotFound if the keystore is operating normally
|
||||||
|
// and knows that it does not store the requested key.
|
||||||
|
GetKey(keyID string) (data.PrivateKey, data.RoleName, error)
|
||||||
|
GetKeyInfo(keyID string) (KeyInfo, error)
|
||||||
|
ListKeys() map[string]KeyInfo
|
||||||
|
RemoveKey(keyID string) error
|
||||||
|
Name() string
|
||||||
|
}
|
265
src/vendor/github.com/docker/notary/trustmanager/keystore.go
generated
vendored
Normal file
265
src/vendor/github.com/docker/notary/trustmanager/keystore.go
generated
vendored
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
package trustmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/notary"
|
||||||
|
store "github.com/docker/notary/storage"
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"github.com/docker/notary/tuf/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type keyInfoMap map[string]KeyInfo
|
||||||
|
|
||||||
|
type cachedKey struct {
|
||||||
|
role data.RoleName
|
||||||
|
key data.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenericKeyStore is a wrapper for Storage instances that provides
|
||||||
|
// translation between the []byte form and Public/PrivateKey objects
|
||||||
|
type GenericKeyStore struct {
|
||||||
|
store Storage
|
||||||
|
sync.Mutex
|
||||||
|
notary.PassRetriever
|
||||||
|
cachedKeys map[string]*cachedKey
|
||||||
|
keyInfoMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKeyFileStore returns a new KeyFileStore creating a private directory to
|
||||||
|
// hold the keys.
|
||||||
|
func NewKeyFileStore(baseDir string, p notary.PassRetriever) (*GenericKeyStore, error) {
|
||||||
|
fileStore, err := store.NewPrivateKeyFileStorage(baseDir, notary.KeyExtension)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewGenericKeyStore(fileStore, p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKeyMemoryStore returns a new KeyMemoryStore which holds keys in memory
|
||||||
|
func NewKeyMemoryStore(p notary.PassRetriever) *GenericKeyStore {
|
||||||
|
memStore := store.NewMemoryStore(nil)
|
||||||
|
return NewGenericKeyStore(memStore, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGenericKeyStore creates a GenericKeyStore wrapping the provided
|
||||||
|
// Storage instance, using the PassRetriever to enc/decrypt keys
|
||||||
|
func NewGenericKeyStore(s Storage, p notary.PassRetriever) *GenericKeyStore {
|
||||||
|
ks := GenericKeyStore{
|
||||||
|
store: s,
|
||||||
|
PassRetriever: p,
|
||||||
|
cachedKeys: make(map[string]*cachedKey),
|
||||||
|
keyInfoMap: make(keyInfoMap),
|
||||||
|
}
|
||||||
|
ks.loadKeyInfo()
|
||||||
|
return &ks
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateKeyInfoMap(s Storage) map[string]KeyInfo {
|
||||||
|
keyInfoMap := make(map[string]KeyInfo)
|
||||||
|
for _, keyPath := range s.ListFiles() {
|
||||||
|
d, err := s.Get(keyPath)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keyID, keyInfo, err := KeyInfoFromPEM(d, keyPath)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keyInfoMap[keyID] = keyInfo
|
||||||
|
}
|
||||||
|
return keyInfoMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GenericKeyStore) loadKeyInfo() {
|
||||||
|
s.keyInfoMap = generateKeyInfoMap(s.store)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKeyInfo returns the corresponding gun and role key info for a keyID
|
||||||
|
func (s *GenericKeyStore) GetKeyInfo(keyID string) (KeyInfo, error) {
|
||||||
|
if info, ok := s.keyInfoMap[keyID]; ok {
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
return KeyInfo{}, fmt.Errorf("Could not find info for keyID %s", keyID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddKey stores the contents of a PEM-encoded private key as a PEM block
|
||||||
|
func (s *GenericKeyStore) AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error {
|
||||||
|
var (
|
||||||
|
chosenPassphrase string
|
||||||
|
giveup bool
|
||||||
|
err error
|
||||||
|
pemPrivKey []byte
|
||||||
|
)
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
if keyInfo.Role == data.CanonicalRootRole || data.IsDelegation(keyInfo.Role) || !data.ValidRole(keyInfo.Role) {
|
||||||
|
keyInfo.Gun = ""
|
||||||
|
}
|
||||||
|
keyID := privKey.ID()
|
||||||
|
for attempts := 0; ; attempts++ {
|
||||||
|
chosenPassphrase, giveup, err = s.PassRetriever(keyID, keyInfo.Role.String(), true, attempts)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if giveup || attempts > 10 {
|
||||||
|
return ErrAttemptsExceeded{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if chosenPassphrase != "" {
|
||||||
|
pemPrivKey, err = utils.EncryptPrivateKey(privKey, keyInfo.Role, keyInfo.Gun, chosenPassphrase)
|
||||||
|
} else {
|
||||||
|
pemPrivKey, err = utils.KeyToPEM(privKey, keyInfo.Role, keyInfo.Gun)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.cachedKeys[keyID] = &cachedKey{role: keyInfo.Role, key: privKey}
|
||||||
|
err = s.store.Set(keyID, pemPrivKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.keyInfoMap[privKey.ID()] = keyInfo
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKey returns the PrivateKey given a KeyID
|
||||||
|
func (s *GenericKeyStore) GetKey(keyID string) (data.PrivateKey, data.RoleName, error) {
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
|
||||||
|
cachedKeyEntry, ok := s.cachedKeys[keyID]
|
||||||
|
if ok {
|
||||||
|
return cachedKeyEntry.key, cachedKeyEntry.role, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
role, err := getKeyRole(s.store, keyID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
keyBytes, err := s.store.Get(keyID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// See if the key is encrypted. If its encrypted we'll fail to parse the private key
|
||||||
|
privKey, err := utils.ParsePEMPrivateKey(keyBytes, "")
|
||||||
|
if err != nil {
|
||||||
|
privKey, _, err = GetPasswdDecryptBytes(s.PassRetriever, keyBytes, keyID, string(role))
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.cachedKeys[keyID] = &cachedKey{role: role, key: privKey}
|
||||||
|
return privKey, role, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListKeys returns a list of unique PublicKeys present on the KeyFileStore, by returning a copy of the keyInfoMap
|
||||||
|
func (s *GenericKeyStore) ListKeys() map[string]KeyInfo {
|
||||||
|
return copyKeyInfoMap(s.keyInfoMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveKey removes the key from the keyfilestore
|
||||||
|
func (s *GenericKeyStore) RemoveKey(keyID string) error {
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
delete(s.cachedKeys, keyID)
|
||||||
|
|
||||||
|
err := s.store.Remove(keyID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(s.keyInfoMap, keyID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns a user friendly name for the location this store
|
||||||
|
// keeps its data
|
||||||
|
func (s *GenericKeyStore) Name() string {
|
||||||
|
return s.store.Location()
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyKeyInfoMap returns a deep copy of the passed-in keyInfoMap
|
||||||
|
func copyKeyInfoMap(keyInfoMap map[string]KeyInfo) map[string]KeyInfo {
|
||||||
|
copyMap := make(map[string]KeyInfo)
|
||||||
|
for keyID, keyInfo := range keyInfoMap {
|
||||||
|
copyMap[keyID] = KeyInfo{Role: keyInfo.Role, Gun: keyInfo.Gun}
|
||||||
|
}
|
||||||
|
return copyMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyInfoFromPEM attempts to get a keyID and KeyInfo from the filename and PEM bytes of a key
|
||||||
|
func KeyInfoFromPEM(pemBytes []byte, filename string) (string, KeyInfo, error) {
|
||||||
|
var keyID string
|
||||||
|
keyID = filepath.Base(filename)
|
||||||
|
block, _ := pem.Decode(pemBytes)
|
||||||
|
if block == nil {
|
||||||
|
return "", KeyInfo{}, fmt.Errorf("could not decode PEM block for key %s", filename)
|
||||||
|
}
|
||||||
|
return keyID, KeyInfo{Gun: data.GUN(block.Headers["gun"]), Role: data.RoleName(block.Headers["role"])}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getKeyRole finds the role for the given keyID. It attempts to look
|
||||||
|
// both in the newer format PEM headers, and also in the legacy filename
|
||||||
|
// format. It returns: the role, and an error
|
||||||
|
func getKeyRole(s Storage, keyID string) (data.RoleName, error) {
|
||||||
|
name := strings.TrimSpace(strings.TrimSuffix(filepath.Base(keyID), filepath.Ext(keyID)))
|
||||||
|
|
||||||
|
for _, file := range s.ListFiles() {
|
||||||
|
filename := filepath.Base(file)
|
||||||
|
if strings.HasPrefix(filename, name) {
|
||||||
|
d, err := s.Get(file)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
block, _ := pem.Decode(d)
|
||||||
|
if block != nil {
|
||||||
|
return data.RoleName(block.Headers["role"]), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", ErrKeyNotFound{KeyID: keyID}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPasswdDecryptBytes gets the password to decrypt the given pem bytes.
|
||||||
|
// Returns the password and private key
|
||||||
|
func GetPasswdDecryptBytes(passphraseRetriever notary.PassRetriever, pemBytes []byte, name, alias string) (data.PrivateKey, string, error) {
|
||||||
|
var (
|
||||||
|
passwd string
|
||||||
|
privKey data.PrivateKey
|
||||||
|
)
|
||||||
|
for attempts := 0; ; attempts++ {
|
||||||
|
var (
|
||||||
|
giveup bool
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if attempts > 10 {
|
||||||
|
return nil, "", ErrAttemptsExceeded{}
|
||||||
|
}
|
||||||
|
passwd, giveup, err = passphraseRetriever(name, alias, false, attempts)
|
||||||
|
// Check if the passphrase retriever got an error or if it is telling us to give up
|
||||||
|
if giveup || err != nil {
|
||||||
|
return nil, "", ErrPasswordInvalid{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to convert PEM encoded bytes back to a PrivateKey using the passphrase
|
||||||
|
privKey, err = utils.ParsePEMPrivateKey(pemBytes, passwd)
|
||||||
|
if err == nil {
|
||||||
|
// We managed to parse the PrivateKey. We've succeeded!
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return privKey, passwd, nil
|
||||||
|
}
|
58
src/vendor/github.com/docker/notary/trustmanager/yubikey/import.go
generated
vendored
Normal file
58
src/vendor/github.com/docker/notary/trustmanager/yubikey/import.go
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// +build pkcs11
|
||||||
|
|
||||||
|
package yubikey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"github.com/docker/notary"
|
||||||
|
"github.com/docker/notary/trustmanager"
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"github.com/docker/notary/tuf/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// YubiImport is a wrapper around the YubiStore that allows us to import private
|
||||||
|
// keys to the yubikey
|
||||||
|
type YubiImport struct {
|
||||||
|
dest *YubiStore
|
||||||
|
passRetriever notary.PassRetriever
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewImporter returns a wrapper for the YubiStore provided that enables importing
|
||||||
|
// keys via the simple Set(string, []byte) interface
|
||||||
|
func NewImporter(ys *YubiStore, ret notary.PassRetriever) *YubiImport {
|
||||||
|
return &YubiImport{
|
||||||
|
dest: ys,
|
||||||
|
passRetriever: ret,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set determines if we are allowed to set the given key on the Yubikey and
|
||||||
|
// calls through to YubiStore.AddKey if it's valid
|
||||||
|
func (s *YubiImport) Set(name string, bytes []byte) error {
|
||||||
|
block, _ := pem.Decode(bytes)
|
||||||
|
if block == nil {
|
||||||
|
return errors.New("invalid PEM data, could not parse")
|
||||||
|
}
|
||||||
|
role, ok := block.Headers["role"]
|
||||||
|
if !ok {
|
||||||
|
return errors.New("no role found for key")
|
||||||
|
}
|
||||||
|
ki := trustmanager.KeyInfo{
|
||||||
|
// GUN is ignored by YubiStore
|
||||||
|
Role: data.RoleName(role),
|
||||||
|
}
|
||||||
|
privKey, err := utils.ParsePEMPrivateKey(bytes, "")
|
||||||
|
if err != nil {
|
||||||
|
privKey, _, err = trustmanager.GetPasswdDecryptBytes(
|
||||||
|
s.passRetriever,
|
||||||
|
bytes,
|
||||||
|
name,
|
||||||
|
ki.Role.String(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s.dest.AddKey(ki, privKey)
|
||||||
|
}
|
9
src/vendor/github.com/docker/notary/trustmanager/yubikey/non_pkcs11.go
generated
vendored
Normal file
9
src/vendor/github.com/docker/notary/trustmanager/yubikey/non_pkcs11.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// go list ./... and go test ./... will not pick up this package without this
|
||||||
|
// file, because go ? ./... does not honor build tags.
|
||||||
|
|
||||||
|
// e.g. "go list -tags pkcs11 ./..." will not list this package if all the
|
||||||
|
// files in it have a build tag.
|
||||||
|
|
||||||
|
// See https://github.com/golang/go/issues/11246
|
||||||
|
|
||||||
|
package yubikey
|
9
src/vendor/github.com/docker/notary/trustmanager/yubikey/pkcs11_darwin.go
generated
vendored
Normal file
9
src/vendor/github.com/docker/notary/trustmanager/yubikey/pkcs11_darwin.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// +build pkcs11,darwin
|
||||||
|
|
||||||
|
package yubikey
|
||||||
|
|
||||||
|
var possiblePkcs11Libs = []string{
|
||||||
|
"/usr/local/lib/libykcs11.dylib",
|
||||||
|
"/usr/local/docker/lib/libykcs11.dylib",
|
||||||
|
"/usr/local/docker-experimental/lib/libykcs11.dylib",
|
||||||
|
}
|
40
src/vendor/github.com/docker/notary/trustmanager/yubikey/pkcs11_interface.go
generated
vendored
Normal file
40
src/vendor/github.com/docker/notary/trustmanager/yubikey/pkcs11_interface.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// +build pkcs11
|
||||||
|
|
||||||
|
// an interface around the pkcs11 library, so that things can be mocked out
|
||||||
|
// for testing
|
||||||
|
|
||||||
|
package yubikey
|
||||||
|
|
||||||
|
import "github.com/miekg/pkcs11"
|
||||||
|
|
||||||
|
// IPKCS11 is an interface for wrapping github.com/miekg/pkcs11
|
||||||
|
type pkcs11LibLoader func(module string) IPKCS11Ctx
|
||||||
|
|
||||||
|
func defaultLoader(module string) IPKCS11Ctx {
|
||||||
|
return pkcs11.New(module)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPKCS11Ctx is an interface for wrapping the parts of
|
||||||
|
// github.com/miekg/pkcs11.Ctx that yubikeystore requires
|
||||||
|
type IPKCS11Ctx interface {
|
||||||
|
Destroy()
|
||||||
|
Initialize() error
|
||||||
|
Finalize() error
|
||||||
|
GetSlotList(tokenPresent bool) ([]uint, error)
|
||||||
|
OpenSession(slotID uint, flags uint) (pkcs11.SessionHandle, error)
|
||||||
|
CloseSession(sh pkcs11.SessionHandle) error
|
||||||
|
Login(sh pkcs11.SessionHandle, userType uint, pin string) error
|
||||||
|
Logout(sh pkcs11.SessionHandle) error
|
||||||
|
CreateObject(sh pkcs11.SessionHandle, temp []*pkcs11.Attribute) (
|
||||||
|
pkcs11.ObjectHandle, error)
|
||||||
|
DestroyObject(sh pkcs11.SessionHandle, oh pkcs11.ObjectHandle) error
|
||||||
|
GetAttributeValue(sh pkcs11.SessionHandle, o pkcs11.ObjectHandle,
|
||||||
|
a []*pkcs11.Attribute) ([]*pkcs11.Attribute, error)
|
||||||
|
FindObjectsInit(sh pkcs11.SessionHandle, temp []*pkcs11.Attribute) error
|
||||||
|
FindObjects(sh pkcs11.SessionHandle, max int) (
|
||||||
|
[]pkcs11.ObjectHandle, bool, error)
|
||||||
|
FindObjectsFinal(sh pkcs11.SessionHandle) error
|
||||||
|
SignInit(sh pkcs11.SessionHandle, m []*pkcs11.Mechanism,
|
||||||
|
o pkcs11.ObjectHandle) error
|
||||||
|
Sign(sh pkcs11.SessionHandle, message []byte) ([]byte, error)
|
||||||
|
}
|
10
src/vendor/github.com/docker/notary/trustmanager/yubikey/pkcs11_linux.go
generated
vendored
Normal file
10
src/vendor/github.com/docker/notary/trustmanager/yubikey/pkcs11_linux.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// +build pkcs11,linux
|
||||||
|
|
||||||
|
package yubikey
|
||||||
|
|
||||||
|
var possiblePkcs11Libs = []string{
|
||||||
|
"/usr/lib/libykcs11.so",
|
||||||
|
"/usr/lib64/libykcs11.so",
|
||||||
|
"/usr/lib/x86_64-linux-gnu/libykcs11.so",
|
||||||
|
"/usr/local/lib/libykcs11.so",
|
||||||
|
}
|
924
src/vendor/github.com/docker/notary/trustmanager/yubikey/yubikeystore.go
generated
vendored
Normal file
924
src/vendor/github.com/docker/notary/trustmanager/yubikey/yubikeystore.go
generated
vendored
Normal file
@ -0,0 +1,924 @@
|
|||||||
|
// +build pkcs11
|
||||||
|
|
||||||
|
package yubikey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/notary"
|
||||||
|
"github.com/docker/notary/trustmanager"
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"github.com/docker/notary/tuf/signed"
|
||||||
|
"github.com/docker/notary/tuf/utils"
|
||||||
|
"github.com/miekg/pkcs11"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// UserPin is the user pin of a yubikey (in PIV parlance, is the PIN)
|
||||||
|
UserPin = "123456"
|
||||||
|
// SOUserPin is the "Security Officer" user pin - this is the PIV management
|
||||||
|
// (MGM) key, which is different than the admin pin of the Yubikey PGP interface
|
||||||
|
// (which in PIV parlance is the PUK, and defaults to 12345678)
|
||||||
|
SOUserPin = "010203040506070801020304050607080102030405060708"
|
||||||
|
numSlots = 4 // number of slots in the yubikey
|
||||||
|
|
||||||
|
// KeymodeNone means that no touch or PIN is required to sign with the yubikey
|
||||||
|
KeymodeNone = 0
|
||||||
|
// KeymodeTouch means that only touch is required to sign with the yubikey
|
||||||
|
KeymodeTouch = 1
|
||||||
|
// KeymodePinOnce means that the pin entry is required once the first time to sign with the yubikey
|
||||||
|
KeymodePinOnce = 2
|
||||||
|
// KeymodePinAlways means that pin entry is required every time to sign with the yubikey
|
||||||
|
KeymodePinAlways = 4
|
||||||
|
|
||||||
|
// the key size, when importing a key into yubikey, MUST be 32 bytes
|
||||||
|
ecdsaPrivateKeySize = 32
|
||||||
|
|
||||||
|
sigAttempts = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
// what key mode to use when generating keys
|
||||||
|
var (
|
||||||
|
yubikeyKeymode = KeymodeTouch | KeymodePinOnce
|
||||||
|
// order in which to prefer token locations on the yubikey.
|
||||||
|
// corresponds to: 9c, 9e, 9d, 9a
|
||||||
|
slotIDs = []int{2, 1, 3, 0}
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetYubikeyKeyMode - sets the mode when generating yubikey keys.
|
||||||
|
// This is to be used for testing. It does nothing if not building with tag
|
||||||
|
// pkcs11.
|
||||||
|
func SetYubikeyKeyMode(keyMode int) error {
|
||||||
|
// technically 7 (1 | 2 | 4) is valid, but KeymodePinOnce +
|
||||||
|
// KeymdoePinAlways don't really make sense together
|
||||||
|
if keyMode < 0 || keyMode > 5 {
|
||||||
|
return errors.New("Invalid key mode")
|
||||||
|
}
|
||||||
|
yubikeyKeymode = keyMode
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTouchToSignUI - allows configurable UX for notifying a user that they
|
||||||
|
// need to touch the yubikey to sign. The callback may be used to provide a
|
||||||
|
// mechanism for updating a GUI (such as removing a modal) after the touch
|
||||||
|
// has been made
|
||||||
|
func SetTouchToSignUI(notifier func(), callback func()) {
|
||||||
|
touchToSignUI = notifier
|
||||||
|
if callback != nil {
|
||||||
|
touchDoneCallback = callback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var touchToSignUI = func() {
|
||||||
|
fmt.Println("Please touch the attached Yubikey to perform signing.")
|
||||||
|
}
|
||||||
|
|
||||||
|
var touchDoneCallback = func() {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
var pkcs11Lib string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for _, loc := range possiblePkcs11Libs {
|
||||||
|
_, err := os.Stat(loc)
|
||||||
|
if err == nil {
|
||||||
|
p := pkcs11.New(loc)
|
||||||
|
if p != nil {
|
||||||
|
pkcs11Lib = loc
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrBackupFailed is returned when a YubiStore fails to back up a key that
|
||||||
|
// is added
|
||||||
|
type ErrBackupFailed struct {
|
||||||
|
err string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrBackupFailed) Error() string {
|
||||||
|
return fmt.Sprintf("Failed to backup private key to: %s", err.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An error indicating that the HSM is not present (as opposed to failing),
|
||||||
|
// i.e. that we can confidently claim that the key is not stored in the HSM
|
||||||
|
// without notifying the user about a missing or failing HSM.
|
||||||
|
type errHSMNotPresent struct {
|
||||||
|
err string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err errHSMNotPresent) Error() string {
|
||||||
|
return err.err
|
||||||
|
}
|
||||||
|
|
||||||
|
type yubiSlot struct {
|
||||||
|
role data.RoleName
|
||||||
|
slotID []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// YubiPrivateKey represents a private key inside of a yubikey
|
||||||
|
type YubiPrivateKey struct {
|
||||||
|
data.ECDSAPublicKey
|
||||||
|
passRetriever notary.PassRetriever
|
||||||
|
slot []byte
|
||||||
|
libLoader pkcs11LibLoader
|
||||||
|
}
|
||||||
|
|
||||||
|
// yubikeySigner wraps a YubiPrivateKey and implements the crypto.Signer interface
|
||||||
|
type yubikeySigner struct {
|
||||||
|
YubiPrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewYubiPrivateKey returns a YubiPrivateKey, which implements the data.PrivateKey
|
||||||
|
// interface except that the private material is inaccessible
|
||||||
|
func NewYubiPrivateKey(slot []byte, pubKey data.ECDSAPublicKey,
|
||||||
|
passRetriever notary.PassRetriever) *YubiPrivateKey {
|
||||||
|
|
||||||
|
return &YubiPrivateKey{
|
||||||
|
ECDSAPublicKey: pubKey,
|
||||||
|
passRetriever: passRetriever,
|
||||||
|
slot: slot,
|
||||||
|
libLoader: defaultLoader,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public is a required method of the crypto.Signer interface
|
||||||
|
func (ys *yubikeySigner) Public() crypto.PublicKey {
|
||||||
|
publicKey, err := x509.ParsePKIXPublicKey(ys.YubiPrivateKey.Public())
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return publicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (y *YubiPrivateKey) setLibLoader(loader pkcs11LibLoader) {
|
||||||
|
y.libLoader = loader
|
||||||
|
}
|
||||||
|
|
||||||
|
// CryptoSigner returns a crypto.Signer tha wraps the YubiPrivateKey. Needed for
|
||||||
|
// Certificate generation only
|
||||||
|
func (y *YubiPrivateKey) CryptoSigner() crypto.Signer {
|
||||||
|
return &yubikeySigner{YubiPrivateKey: *y}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private is not implemented in hardware keys
|
||||||
|
func (y *YubiPrivateKey) Private() []byte {
|
||||||
|
// We cannot return the private material from a Yubikey
|
||||||
|
// TODO(david): We probably want to return an error here
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignatureAlgorithm returns which algorithm this key uses to sign - currently
|
||||||
|
// hardcoded to ECDSA
|
||||||
|
func (y YubiPrivateKey) SignatureAlgorithm() data.SigAlgorithm {
|
||||||
|
return data.ECDSASignature
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign is a required method of the crypto.Signer interface and the data.PrivateKey
|
||||||
|
// interface
|
||||||
|
func (y *YubiPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error) {
|
||||||
|
ctx, session, err := SetupHSMEnv(pkcs11Lib, y.libLoader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cleanup(ctx, session)
|
||||||
|
|
||||||
|
v := signed.Verifiers[data.ECDSASignature]
|
||||||
|
for i := 0; i < sigAttempts; i++ {
|
||||||
|
sig, err := sign(ctx, session, y.slot, y.passRetriever, msg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to sign using Yubikey: %v", err)
|
||||||
|
}
|
||||||
|
if err := v.Verify(&y.ECDSAPublicKey, sig, msg); err == nil {
|
||||||
|
return sig, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("failed to generate signature on Yubikey")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a byte array is less than the number of bytes specified by
|
||||||
|
// ecdsaPrivateKeySize, left-zero-pad the byte array until
|
||||||
|
// it is the required size.
|
||||||
|
func ensurePrivateKeySize(payload []byte) []byte {
|
||||||
|
final := payload
|
||||||
|
if len(payload) < ecdsaPrivateKeySize {
|
||||||
|
final = make([]byte, ecdsaPrivateKeySize)
|
||||||
|
copy(final[ecdsaPrivateKeySize-len(payload):], payload)
|
||||||
|
}
|
||||||
|
return final
|
||||||
|
}
|
||||||
|
|
||||||
|
// addECDSAKey adds a key to the yubikey
|
||||||
|
func addECDSAKey(
|
||||||
|
ctx IPKCS11Ctx,
|
||||||
|
session pkcs11.SessionHandle,
|
||||||
|
privKey data.PrivateKey,
|
||||||
|
pkcs11KeyID []byte,
|
||||||
|
passRetriever notary.PassRetriever,
|
||||||
|
role data.RoleName,
|
||||||
|
) error {
|
||||||
|
logrus.Debugf("Attempting to add key to yubikey with ID: %s", privKey.ID())
|
||||||
|
|
||||||
|
err := login(ctx, session, passRetriever, pkcs11.CKU_SO, SOUserPin)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ctx.Logout(session)
|
||||||
|
|
||||||
|
// Create an ecdsa.PrivateKey out of the private key bytes
|
||||||
|
ecdsaPrivKey, err := x509.ParseECPrivateKey(privKey.Private())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ecdsaPrivKeyD := ensurePrivateKeySize(ecdsaPrivKey.D.Bytes())
|
||||||
|
|
||||||
|
// Hard-coded policy: the generated certificate expires in 10 years.
|
||||||
|
startTime := time.Now()
|
||||||
|
template, err := utils.NewCertificate(role.String(), startTime, startTime.AddDate(10, 0, 0))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create the certificate template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certBytes, err := x509.CreateCertificate(rand.Reader, template, template, ecdsaPrivKey.Public(), ecdsaPrivKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create the certificate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certTemplate := []*pkcs11.Attribute{
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_VALUE, certBytes),
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKeyTemplate := []*pkcs11.Attribute{
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_ECDSA),
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07}),
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_VALUE, ecdsaPrivKeyD),
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_VENDOR_DEFINED, yubikeyKeymode),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = ctx.CreateObject(session, certTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error importing: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = ctx.CreateObject(session, privateKeyTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error importing: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getECDSAKey(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte) (*data.ECDSAPublicKey, data.RoleName, error) {
|
||||||
|
findTemplate := []*pkcs11.Attribute{
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY),
|
||||||
|
}
|
||||||
|
|
||||||
|
attrTemplate := []*pkcs11.Attribute{
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, []byte{0}),
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, []byte{0}),
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{0}),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ctx.FindObjectsInit(session, findTemplate); err != nil {
|
||||||
|
logrus.Debugf("Failed to init: %s", err.Error())
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
obj, _, err := ctx.FindObjects(session, 1)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Failed to find objects: %v", err)
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
if err := ctx.FindObjectsFinal(session); err != nil {
|
||||||
|
logrus.Debugf("Failed to finalize: %s", err.Error())
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
if len(obj) != 1 {
|
||||||
|
logrus.Debugf("should have found one object")
|
||||||
|
return nil, "", errors.New("no matching keys found inside of yubikey")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the public-key material to be able to create a new ECSAKey
|
||||||
|
attr, err := ctx.GetAttributeValue(session, obj[0], attrTemplate)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Failed to get Attribute for: %v", obj[0])
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through all the attributes of this key and saves CKA_PUBLIC_EXPONENT and CKA_MODULUS. Removes ordering specific issues.
|
||||||
|
var rawPubKey []byte
|
||||||
|
for _, a := range attr {
|
||||||
|
if a.Type == pkcs11.CKA_EC_POINT {
|
||||||
|
rawPubKey = a.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ecdsaPubKey := ecdsa.PublicKey{Curve: elliptic.P256(), X: new(big.Int).SetBytes(rawPubKey[3:35]), Y: new(big.Int).SetBytes(rawPubKey[35:])}
|
||||||
|
pubBytes, err := x509.MarshalPKIXPublicKey(&ecdsaPubKey)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Failed to Marshal public key")
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.NewECDSAPublicKey(pubBytes), data.CanonicalRootRole, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sign returns a signature for a given signature request
|
||||||
|
func sign(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, passRetriever notary.PassRetriever, payload []byte) ([]byte, error) {
|
||||||
|
err := login(ctx, session, passRetriever, pkcs11.CKU_USER, UserPin)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error logging in: %v", err)
|
||||||
|
}
|
||||||
|
defer ctx.Logout(session)
|
||||||
|
|
||||||
|
// Define the ECDSA Private key template
|
||||||
|
class := pkcs11.CKO_PRIVATE_KEY
|
||||||
|
privateKeyTemplate := []*pkcs11.Attribute{
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_CLASS, class),
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_ECDSA),
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ctx.FindObjectsInit(session, privateKeyTemplate); err != nil {
|
||||||
|
logrus.Debugf("Failed to init find objects: %s", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
obj, _, err := ctx.FindObjects(session, 1)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Failed to find objects: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = ctx.FindObjectsFinal(session); err != nil {
|
||||||
|
logrus.Debugf("Failed to finalize find objects: %s", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(obj) != 1 {
|
||||||
|
return nil, errors.New("length of objects found not 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
var sig []byte
|
||||||
|
err = ctx.SignInit(
|
||||||
|
session, []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_ECDSA, nil)}, obj[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the SHA256 of the payload
|
||||||
|
digest := sha256.Sum256(payload)
|
||||||
|
|
||||||
|
if (yubikeyKeymode & KeymodeTouch) > 0 {
|
||||||
|
touchToSignUI()
|
||||||
|
defer touchDoneCallback()
|
||||||
|
}
|
||||||
|
// a call to Sign, whether or not Sign fails, will clear the SignInit
|
||||||
|
sig, err = ctx.Sign(session, digest[:])
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Error while signing: %s", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if sig == nil {
|
||||||
|
return nil, errors.New("Failed to create signature")
|
||||||
|
}
|
||||||
|
return sig[:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func yubiRemoveKey(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, passRetriever notary.PassRetriever, keyID string) error {
|
||||||
|
err := login(ctx, session, passRetriever, pkcs11.CKU_SO, SOUserPin)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ctx.Logout(session)
|
||||||
|
|
||||||
|
template := []*pkcs11.Attribute{
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
|
||||||
|
//pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ctx.FindObjectsInit(session, template); err != nil {
|
||||||
|
logrus.Debugf("Failed to init find objects: %s", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
obj, b, err := ctx.FindObjects(session, 1)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Failed to find objects: %s %v", err.Error(), b)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ctx.FindObjectsFinal(session); err != nil {
|
||||||
|
logrus.Debugf("Failed to finalize find objects: %s", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(obj) != 1 {
|
||||||
|
logrus.Debugf("should have found exactly one object")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the certificate
|
||||||
|
err = ctx.DestroyObject(session, obj[0])
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Failed to delete cert")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func yubiListKeys(ctx IPKCS11Ctx, session pkcs11.SessionHandle) (keys map[string]yubiSlot, err error) {
|
||||||
|
keys = make(map[string]yubiSlot)
|
||||||
|
|
||||||
|
attrTemplate := []*pkcs11.Attribute{
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_ID, []byte{0}),
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_VALUE, []byte{0}),
|
||||||
|
}
|
||||||
|
|
||||||
|
objs, err := listObjects(ctx, session)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(objs) == 0 {
|
||||||
|
return nil, errors.New("no keys found in yubikey")
|
||||||
|
}
|
||||||
|
logrus.Debugf("Found %d objects matching list filters", len(objs))
|
||||||
|
for _, obj := range objs {
|
||||||
|
var (
|
||||||
|
cert *x509.Certificate
|
||||||
|
slot []byte
|
||||||
|
)
|
||||||
|
// Retrieve the public-key material to be able to create a new ECDSA
|
||||||
|
attr, err := ctx.GetAttributeValue(session, obj, attrTemplate)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Failed to get Attribute for: %v", obj)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through all the attributes of this key and saves CKA_PUBLIC_EXPONENT and CKA_MODULUS. Removes ordering specific issues.
|
||||||
|
for _, a := range attr {
|
||||||
|
if a.Type == pkcs11.CKA_ID {
|
||||||
|
slot = a.Value
|
||||||
|
}
|
||||||
|
if a.Type == pkcs11.CKA_VALUE {
|
||||||
|
cert, err = x509.ParseCertificate(a.Value)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !data.ValidRole(data.RoleName(cert.Subject.CommonName)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we found nothing
|
||||||
|
if cert == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var ecdsaPubKey *ecdsa.PublicKey
|
||||||
|
switch cert.PublicKeyAlgorithm {
|
||||||
|
case x509.ECDSA:
|
||||||
|
ecdsaPubKey = cert.PublicKey.(*ecdsa.PublicKey)
|
||||||
|
default:
|
||||||
|
logrus.Infof("Unsupported x509 PublicKeyAlgorithm: %d", cert.PublicKeyAlgorithm)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pubBytes, err := x509.MarshalPKIXPublicKey(ecdsaPubKey)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Failed to Marshal public key")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
keys[data.NewECDSAPublicKey(pubBytes).ID()] = yubiSlot{
|
||||||
|
role: data.RoleName(cert.Subject.CommonName),
|
||||||
|
slotID: slot,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func listObjects(ctx IPKCS11Ctx, session pkcs11.SessionHandle) ([]pkcs11.ObjectHandle, error) {
|
||||||
|
findTemplate := []*pkcs11.Attribute{
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ctx.FindObjectsInit(session, findTemplate); err != nil {
|
||||||
|
logrus.Debugf("Failed to init: %s", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
objs, b, err := ctx.FindObjects(session, numSlots)
|
||||||
|
for err == nil {
|
||||||
|
var o []pkcs11.ObjectHandle
|
||||||
|
o, b, err = ctx.FindObjects(session, numSlots)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(o) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
objs = append(objs, o...)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Failed to find: %s %v", err.Error(), b)
|
||||||
|
if len(objs) == 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := ctx.FindObjectsFinal(session); err != nil {
|
||||||
|
logrus.Debugf("Failed to finalize: %s", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return objs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNextEmptySlot(ctx IPKCS11Ctx, session pkcs11.SessionHandle) ([]byte, error) {
|
||||||
|
findTemplate := []*pkcs11.Attribute{
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
|
||||||
|
}
|
||||||
|
attrTemplate := []*pkcs11.Attribute{
|
||||||
|
pkcs11.NewAttribute(pkcs11.CKA_ID, []byte{0}),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ctx.FindObjectsInit(session, findTemplate); err != nil {
|
||||||
|
logrus.Debugf("Failed to init: %s", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
objs, b, err := ctx.FindObjects(session, numSlots)
|
||||||
|
// if there are more objects than `numSlots`, get all of them until
|
||||||
|
// there are no more to get
|
||||||
|
for err == nil {
|
||||||
|
var o []pkcs11.ObjectHandle
|
||||||
|
o, b, err = ctx.FindObjects(session, numSlots)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(o) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
objs = append(objs, o...)
|
||||||
|
}
|
||||||
|
taken := make(map[int]bool)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Failed to find: %s %v", err.Error(), b)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = ctx.FindObjectsFinal(session); err != nil {
|
||||||
|
logrus.Debugf("Failed to finalize: %s\n", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, obj := range objs {
|
||||||
|
// Retrieve the slot ID
|
||||||
|
attr, err := ctx.GetAttributeValue(session, obj, attrTemplate)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through attributes. If an ID attr was found, mark it as taken
|
||||||
|
for _, a := range attr {
|
||||||
|
if a.Type == pkcs11.CKA_ID {
|
||||||
|
if len(a.Value) < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// a byte will always be capable of representing all slot IDs
|
||||||
|
// for the Yubikeys
|
||||||
|
slotNum := int(a.Value[0])
|
||||||
|
if slotNum >= numSlots {
|
||||||
|
// defensive
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
taken[slotNum] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// iterate the token locations in our preferred order and use the first
|
||||||
|
// available one. Otherwise exit the loop and return an error.
|
||||||
|
for _, loc := range slotIDs {
|
||||||
|
if !taken[loc] {
|
||||||
|
return []byte{byte(loc)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("yubikey has no available slots")
|
||||||
|
}
|
||||||
|
|
||||||
|
// YubiStore is a KeyStore for private keys inside a Yubikey
|
||||||
|
type YubiStore struct {
|
||||||
|
passRetriever notary.PassRetriever
|
||||||
|
keys map[string]yubiSlot
|
||||||
|
backupStore trustmanager.KeyStore
|
||||||
|
libLoader pkcs11LibLoader
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewYubiStore returns a YubiStore, given a backup key store to write any
|
||||||
|
// generated keys to (usually a KeyFileStore)
|
||||||
|
func NewYubiStore(backupStore trustmanager.KeyStore, passphraseRetriever notary.PassRetriever) (
|
||||||
|
*YubiStore, error) {
|
||||||
|
|
||||||
|
s := &YubiStore{
|
||||||
|
passRetriever: passphraseRetriever,
|
||||||
|
keys: make(map[string]yubiSlot),
|
||||||
|
backupStore: backupStore,
|
||||||
|
libLoader: defaultLoader,
|
||||||
|
}
|
||||||
|
s.ListKeys() // populate keys field
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns a user friendly name for the location this store
|
||||||
|
// keeps its data
|
||||||
|
func (s YubiStore) Name() string {
|
||||||
|
return "yubikey"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *YubiStore) setLibLoader(loader pkcs11LibLoader) {
|
||||||
|
s.libLoader = loader
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListKeys returns a list of keys in the yubikey store
|
||||||
|
func (s *YubiStore) ListKeys() map[string]trustmanager.KeyInfo {
|
||||||
|
if len(s.keys) > 0 {
|
||||||
|
return buildKeyMap(s.keys)
|
||||||
|
}
|
||||||
|
ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("No yubikey found, using alternative key storage: %s", err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer cleanup(ctx, session)
|
||||||
|
|
||||||
|
keys, err := yubiListKeys(ctx, session)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Failed to list key from the yubikey: %s", err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s.keys = keys
|
||||||
|
|
||||||
|
return buildKeyMap(keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddKey puts a key inside the Yubikey, as well as writing it to the backup store
|
||||||
|
func (s *YubiStore) AddKey(keyInfo trustmanager.KeyInfo, privKey data.PrivateKey) error {
|
||||||
|
added, err := s.addKey(privKey.ID(), keyInfo.Role, privKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if added && s.backupStore != nil {
|
||||||
|
err = s.backupStore.AddKey(keyInfo, privKey)
|
||||||
|
if err != nil {
|
||||||
|
defer s.RemoveKey(privKey.ID())
|
||||||
|
return ErrBackupFailed{err: err.Error()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only add if we haven't seen the key already. Return whether the key was
|
||||||
|
// added.
|
||||||
|
func (s *YubiStore) addKey(keyID string, role data.RoleName, privKey data.PrivateKey) (
|
||||||
|
bool, error) {
|
||||||
|
|
||||||
|
// We only allow adding root keys for now
|
||||||
|
if role != data.CanonicalRootRole {
|
||||||
|
return false, fmt.Errorf(
|
||||||
|
"yubikey only supports storing root keys, got %s for key: %s", role, keyID)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("No yubikey found, using alternative key storage: %s", err.Error())
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer cleanup(ctx, session)
|
||||||
|
|
||||||
|
if k, ok := s.keys[keyID]; ok {
|
||||||
|
if k.role == role {
|
||||||
|
// already have the key and it's associated with the correct role
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slot, err := getNextEmptySlot(ctx, session)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Failed to get an empty yubikey slot: %s", err.Error())
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
logrus.Debugf("Attempting to store key using yubikey slot %v", slot)
|
||||||
|
|
||||||
|
err = addECDSAKey(
|
||||||
|
ctx, session, privKey, slot, s.passRetriever, role)
|
||||||
|
if err == nil {
|
||||||
|
s.keys[privKey.ID()] = yubiSlot{
|
||||||
|
role: role,
|
||||||
|
slotID: slot,
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
logrus.Debugf("Failed to add key to yubikey: %v", err)
|
||||||
|
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKey retrieves a key from the Yubikey only (it does not look inside the
|
||||||
|
// backup store)
|
||||||
|
func (s *YubiStore) GetKey(keyID string) (data.PrivateKey, data.RoleName, error) {
|
||||||
|
ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("No yubikey found, using alternative key storage: %s", err.Error())
|
||||||
|
if _, ok := err.(errHSMNotPresent); ok {
|
||||||
|
err = trustmanager.ErrKeyNotFound{KeyID: keyID}
|
||||||
|
}
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
defer cleanup(ctx, session)
|
||||||
|
|
||||||
|
key, ok := s.keys[keyID]
|
||||||
|
if !ok {
|
||||||
|
return nil, "", trustmanager.ErrKeyNotFound{KeyID: keyID}
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKey, alias, err := getECDSAKey(ctx, session, key.slotID)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Failed to get key from slot %s: %s", key.slotID, err.Error())
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
// Check to see if we're returning the intended keyID
|
||||||
|
if pubKey.ID() != keyID {
|
||||||
|
return nil, "", fmt.Errorf("expected root key: %s, but found: %s", keyID, pubKey.ID())
|
||||||
|
}
|
||||||
|
privKey := NewYubiPrivateKey(key.slotID, *pubKey, s.passRetriever)
|
||||||
|
if privKey == nil {
|
||||||
|
return nil, "", errors.New("could not initialize new YubiPrivateKey")
|
||||||
|
}
|
||||||
|
|
||||||
|
return privKey, alias, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveKey deletes a key from the Yubikey only (it does not remove it from the
|
||||||
|
// backup store)
|
||||||
|
func (s *YubiStore) RemoveKey(keyID string) error {
|
||||||
|
ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("No yubikey found, using alternative key storage: %s", err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer cleanup(ctx, session)
|
||||||
|
|
||||||
|
key, ok := s.keys[keyID]
|
||||||
|
if !ok {
|
||||||
|
return errors.New("Key not present in yubikey")
|
||||||
|
}
|
||||||
|
err = yubiRemoveKey(ctx, session, key.slotID, s.passRetriever, keyID)
|
||||||
|
if err == nil {
|
||||||
|
delete(s.keys, keyID)
|
||||||
|
} else {
|
||||||
|
logrus.Debugf("Failed to remove from the yubikey KeyID %s: %v", keyID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKeyInfo is not yet implemented
|
||||||
|
func (s *YubiStore) GetKeyInfo(keyID string) (trustmanager.KeyInfo, error) {
|
||||||
|
return trustmanager.KeyInfo{}, fmt.Errorf("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanup(ctx IPKCS11Ctx, session pkcs11.SessionHandle) {
|
||||||
|
err := ctx.CloseSession(session)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Error closing session: %s", err.Error())
|
||||||
|
}
|
||||||
|
finalizeAndDestroy(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func finalizeAndDestroy(ctx IPKCS11Ctx) {
|
||||||
|
err := ctx.Finalize()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Error finalizing: %s", err.Error())
|
||||||
|
}
|
||||||
|
ctx.Destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetupHSMEnv is a method that depends on the existences
|
||||||
|
func SetupHSMEnv(libraryPath string, libLoader pkcs11LibLoader) (
|
||||||
|
IPKCS11Ctx, pkcs11.SessionHandle, error) {
|
||||||
|
|
||||||
|
if libraryPath == "" {
|
||||||
|
return nil, 0, errHSMNotPresent{err: "no library found"}
|
||||||
|
}
|
||||||
|
p := libLoader(libraryPath)
|
||||||
|
|
||||||
|
if p == nil {
|
||||||
|
return nil, 0, fmt.Errorf("failed to load library %s", libraryPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.Initialize(); err != nil {
|
||||||
|
defer finalizeAndDestroy(p)
|
||||||
|
return nil, 0, fmt.Errorf("found library %s, but initialize error %s", libraryPath, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
slots, err := p.GetSlotList(true)
|
||||||
|
if err != nil {
|
||||||
|
defer finalizeAndDestroy(p)
|
||||||
|
return nil, 0, fmt.Errorf(
|
||||||
|
"loaded library %s, but failed to list HSM slots %s", libraryPath, err)
|
||||||
|
}
|
||||||
|
// Check to see if we got any slots from the HSM.
|
||||||
|
if len(slots) < 1 {
|
||||||
|
defer finalizeAndDestroy(p)
|
||||||
|
return nil, 0, fmt.Errorf(
|
||||||
|
"loaded library %s, but no HSM slots found", libraryPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CKF_SERIAL_SESSION: TRUE if cryptographic functions are performed in serial with the application; FALSE if the functions may be performed in parallel with the application.
|
||||||
|
// CKF_RW_SESSION: TRUE if the session is read/write; FALSE if the session is read-only
|
||||||
|
session, err := p.OpenSession(slots[0], pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION)
|
||||||
|
if err != nil {
|
||||||
|
defer cleanup(p, session)
|
||||||
|
return nil, 0, fmt.Errorf(
|
||||||
|
"loaded library %s, but failed to start session with HSM %s",
|
||||||
|
libraryPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("Initialized PKCS11 library %s and started HSM session", libraryPath)
|
||||||
|
return p, session, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAccessible returns true if a Yubikey can be accessed
|
||||||
|
func IsAccessible() bool {
|
||||||
|
if pkcs11Lib == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ctx, session, err := SetupHSMEnv(pkcs11Lib, defaultLoader)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer cleanup(ctx, session)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func login(ctx IPKCS11Ctx, session pkcs11.SessionHandle, passRetriever notary.PassRetriever, userFlag uint, defaultPassw string) error {
|
||||||
|
// try default password
|
||||||
|
err := ctx.Login(session, userFlag, defaultPassw)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// default failed, ask user for password
|
||||||
|
for attempts := 0; ; attempts++ {
|
||||||
|
var (
|
||||||
|
giveup bool
|
||||||
|
err error
|
||||||
|
user string
|
||||||
|
)
|
||||||
|
if userFlag == pkcs11.CKU_SO {
|
||||||
|
user = "SO Pin"
|
||||||
|
} else {
|
||||||
|
user = "User Pin"
|
||||||
|
}
|
||||||
|
passwd, giveup, err := passRetriever(user, "yubikey", false, attempts)
|
||||||
|
// Check if the passphrase retriever got an error or if it is telling us to give up
|
||||||
|
if giveup || err != nil {
|
||||||
|
return trustmanager.ErrPasswordInvalid{}
|
||||||
|
}
|
||||||
|
if attempts > 2 {
|
||||||
|
return trustmanager.ErrAttemptsExceeded{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempt to login. Loop if failed
|
||||||
|
err = ctx.Login(session, userFlag, passwd)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildKeyMap(keys map[string]yubiSlot) map[string]trustmanager.KeyInfo {
|
||||||
|
res := make(map[string]trustmanager.KeyInfo)
|
||||||
|
for k, v := range keys {
|
||||||
|
res[k] = trustmanager.KeyInfo{Role: v.role, Gun: ""}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
37
src/vendor/github.com/docker/notary/trustpinning/ca.crt
generated
vendored
Normal file
37
src/vendor/github.com/docker/notary/trustpinning/ca.crt
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIGMzCCBBugAwIBAgIBATANBgkqhkiG9w0BAQsFADBfMQswCQYDVQQGEwJVUzEL
|
||||||
|
MAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDzANBgNVBAoMBkRv
|
||||||
|
Y2tlcjEaMBgGA1UEAwwRTm90YXJ5IFRlc3RpbmcgQ0EwHhcNMTUwNzE2MDQyNTAz
|
||||||
|
WhcNMjUwNzEzMDQyNTAzWjBfMRowGAYDVQQDDBFOb3RhcnkgVGVzdGluZyBDQTEL
|
||||||
|
MAkGA1UEBhMCVVMxFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDzANBgNVBAoMBkRv
|
||||||
|
Y2tlcjELMAkGA1UECAwCQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
|
||||||
|
AQCwVVD4pK7z7pXPpJbaZ1Hg5eRXIcaYtbFPCnN0iqy9HsVEGnEn5BPNSEsuP+m0
|
||||||
|
5N0qVV7DGb1SjiloLXD1qDDvhXWk+giS9ppqPHPLVPB4bvzsqwDYrtpbqkYvO0YK
|
||||||
|
0SL3kxPXUFdlkFfgu0xjlczm2PhWG3Jd8aAtspL/L+VfPA13JUaWxSLpui1In8rh
|
||||||
|
gAyQTK6Q4Of6GbJYTnAHb59UoLXSzB5AfqiUq6L7nEYYKoPflPbRAIWL/UBm0c+H
|
||||||
|
ocms706PYpmPS2RQv3iOGmnn9hEVp3P6jq7WAevbA4aYGx5EsbVtYABqJBbFWAuw
|
||||||
|
wTGRYmzn0Mj0eTMge9ztYB2/2sxdTe6uhmFgpUXngDqJI5O9N3zPfvlEImCky3HM
|
||||||
|
jJoL7g5smqX9o1P+ESLh0VZzhh7IDPzQTXpcPIS/6z0l22QGkK/1N1PaADaUHdLL
|
||||||
|
vSav3y2BaEmPvf2fkZj8yP5eYgi7Cw5ONhHLDYHFcl9Zm/ywmdxHJETz9nfgXnsW
|
||||||
|
HNxDqrkCVO46r/u6rSrUt6hr3oddJG8s8Jo06earw6XU3MzM+3giwkK0SSM3uRPq
|
||||||
|
4AscR1Tv+E31AuOAmjqYQoT29bMIxoSzeljj/YnedwjW45pWyc3JoHaibDwvW9Uo
|
||||||
|
GSZBVy4hrM/Fa7XCWv1WfHNW1gDwaLYwDnl5jFmRBvcfuQIDAQABo4H5MIH2MIGR
|
||||||
|
BgNVHSMEgYkwgYaAFHUM1U3E4WyL1nvFd+dPY8f4O2hZoWOkYTBfMQswCQYDVQQG
|
||||||
|
EwJVUzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDzANBgNV
|
||||||
|
BAoMBkRvY2tlcjEaMBgGA1UEAwwRTm90YXJ5IFRlc3RpbmcgQ0GCCQDCeDLbemIT
|
||||||
|
SzASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEF
|
||||||
|
BQcDATAOBgNVHQ8BAf8EBAMCAUYwHQYDVR0OBBYEFHe48hcBcAp0bUVlTxXeRA4o
|
||||||
|
E16pMA0GCSqGSIb3DQEBCwUAA4ICAQAWUtAPdUFpwRq+N1SzGUejSikeMGyPZscZ
|
||||||
|
JBUCmhZoFufgXGbLO5OpcRLaV3Xda0t/5PtdGMSEzczeoZHWknDtw+79OBittPPj
|
||||||
|
Sh1oFDuPo35R7eP624lUCch/InZCphTaLx9oDLGcaK3ailQ9wjBdKdlBl8KNKIZp
|
||||||
|
a13aP5rnSm2Jva+tXy/yi3BSds3dGD8ITKZyI/6AFHxGvObrDIBpo4FF/zcWXVDj
|
||||||
|
paOmxplRtM4Hitm+sXGvfqJe4x5DuOXOnPrT3dHvRT6vSZUoKobxMqmRTOcrOIPa
|
||||||
|
EeMpOobshORuRntMDYvvgO3D6p6iciDW2Vp9N6rdMdfOWEQN8JVWvB7IxRHk9qKJ
|
||||||
|
vYOWVbczAt0qpMvXF3PXLjZbUM0knOdUKIEbqP4YUbgdzx6RtgiiY930Aj6tAtce
|
||||||
|
0fpgNlvjMRpSBuWTlAfNNjG/YhndMz9uI68TMfFpR3PcgVIv30krw/9VzoLi2Dpe
|
||||||
|
ow6DrGO6oi+DhN78P4jY/O9UczZK2roZL1Oi5P0RIxf23UZC7x1DlcN3nBr4sYSv
|
||||||
|
rBx4cFTMNpwU+nzsIi4djcFDKmJdEOyjMnkP2v0Lwe7yvK08pZdEu+0zbrq17kue
|
||||||
|
XpXLc7K68QB15yxzGylU5rRwzmC/YsAVyE4eoGu8PxWxrERvHby4B8YP0vAfOraL
|
||||||
|
lKmXlK4dTg==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
304
src/vendor/github.com/docker/notary/trustpinning/certs.go
generated
vendored
Normal file
304
src/vendor/github.com/docker/notary/trustpinning/certs.go
generated
vendored
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
package trustpinning
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"github.com/docker/notary/tuf/signed"
|
||||||
|
"github.com/docker/notary/tuf/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
const wildcard = "*"
|
||||||
|
|
||||||
|
// ErrValidationFail is returned when there is no valid trusted certificates
|
||||||
|
// being served inside of the roots.json
|
||||||
|
type ErrValidationFail struct {
|
||||||
|
Reason string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrValidationFail is returned when there is no valid trusted certificates
|
||||||
|
// being served inside of the roots.json
|
||||||
|
func (err ErrValidationFail) Error() string {
|
||||||
|
return fmt.Sprintf("could not validate the path to a trusted root: %s", err.Reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrRootRotationFail is returned when we fail to do a full root key rotation
|
||||||
|
// by either failing to add the new root certificate, or delete the old ones
|
||||||
|
type ErrRootRotationFail struct {
|
||||||
|
Reason string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrRootRotationFail is returned when we fail to do a full root key rotation
|
||||||
|
// by either failing to add the new root certificate, or delete the old ones
|
||||||
|
func (err ErrRootRotationFail) Error() string {
|
||||||
|
return fmt.Sprintf("could not rotate trust to a new trusted root: %s", err.Reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
func prettyFormatCertIDs(certs map[string]*x509.Certificate) string {
|
||||||
|
ids := make([]string, 0, len(certs))
|
||||||
|
for id := range certs {
|
||||||
|
ids = append(ids, id)
|
||||||
|
}
|
||||||
|
return strings.Join(ids, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
ValidateRoot receives a new root, validates its correctness and attempts to
|
||||||
|
do root key rotation if needed.
|
||||||
|
|
||||||
|
First we check if we have any trusted certificates for a particular GUN in
|
||||||
|
a previous root, if we have one. If the previous root is not nil and we find
|
||||||
|
certificates for this GUN, we've already seen this repository before, and
|
||||||
|
have a list of trusted certificates for it. In this case, we use this list of
|
||||||
|
certificates to attempt to validate this root file.
|
||||||
|
|
||||||
|
If the previous validation succeeds, we check the integrity of the root by
|
||||||
|
making sure that it is validated by itself. This means that we will attempt to
|
||||||
|
validate the root data with the certificates that are included in the root keys
|
||||||
|
themselves.
|
||||||
|
|
||||||
|
However, if we do not have any current trusted certificates for this GUN, we
|
||||||
|
check if there are any pinned certificates specified in the trust_pinning section
|
||||||
|
of the notary client config. If this section specifies a Certs section with this
|
||||||
|
GUN, we attempt to validate that the certificates present in the downloaded root
|
||||||
|
file match the pinned ID.
|
||||||
|
|
||||||
|
If the Certs section is empty for this GUN, we check if the trust_pinning
|
||||||
|
section specifies a CA section specified in the config for this GUN. If so, we check
|
||||||
|
that the specified CA is valid and has signed a certificate included in the downloaded
|
||||||
|
root file. The specified CA can be a prefix for this GUN.
|
||||||
|
|
||||||
|
If both the Certs and CA configs do not match this GUN, we fall back to the TOFU
|
||||||
|
section in the config: if true, we trust certificates specified in the root for
|
||||||
|
this GUN. If later we see a different certificate for that certificate, we return
|
||||||
|
an ErrValidationFailed error.
|
||||||
|
|
||||||
|
Note that since we only allow trust data to be downloaded over an HTTPS channel
|
||||||
|
we are using the current public PKI to validate the first download of the certificate
|
||||||
|
adding an extra layer of security over the normal (SSH style) trust model.
|
||||||
|
We shall call this: TOFUS.
|
||||||
|
|
||||||
|
Validation failure at any step will result in an ErrValidationFailed error.
|
||||||
|
*/
|
||||||
|
func ValidateRoot(prevRoot *data.SignedRoot, root *data.Signed, gun data.GUN, trustPinning TrustPinConfig) (*data.SignedRoot, error) {
|
||||||
|
logrus.Debugf("entered ValidateRoot with dns: %s", gun)
|
||||||
|
signedRoot, err := data.RootFromSigned(root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rootRole, err := signedRoot.BuildBaseRole(data.CanonicalRootRole)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve all the leaf and intermediate certificates in root for which the CN matches the GUN
|
||||||
|
allLeafCerts, allIntCerts := parseAllCerts(signedRoot)
|
||||||
|
certsFromRoot, err := validRootLeafCerts(allLeafCerts, gun, true)
|
||||||
|
validIntCerts := validRootIntCerts(allIntCerts)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("error retrieving valid leaf certificates for: %s, %v", gun, err)
|
||||||
|
return nil, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"}
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("found %d leaf certs, of which %d are valid leaf certs for %s", len(allLeafCerts), len(certsFromRoot), gun)
|
||||||
|
|
||||||
|
// If we have a previous root, let's try to use it to validate that this new root is valid.
|
||||||
|
havePrevRoot := prevRoot != nil
|
||||||
|
if havePrevRoot {
|
||||||
|
// Retrieve all the trusted certificates from our previous root
|
||||||
|
// Note that we do not validate expiries here since our originally trusted root might have expired certs
|
||||||
|
allTrustedLeafCerts, allTrustedIntCerts := parseAllCerts(prevRoot)
|
||||||
|
trustedLeafCerts, err := validRootLeafCerts(allTrustedLeafCerts, gun, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &ErrValidationFail{Reason: "could not retrieve trusted certs from previous root role data"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the certificates we found in the previous root for the GUN to verify its signatures
|
||||||
|
// This could potentially be an empty set, in which case we will fail to verify
|
||||||
|
logrus.Debugf("found %d valid root leaf certificates for %s: %s", len(trustedLeafCerts), gun,
|
||||||
|
prettyFormatCertIDs(trustedLeafCerts))
|
||||||
|
|
||||||
|
// Extract the previous root's threshold for signature verification
|
||||||
|
prevRootRoleData, ok := prevRoot.Signed.Roles[data.CanonicalRootRole]
|
||||||
|
if !ok {
|
||||||
|
return nil, &ErrValidationFail{Reason: "could not retrieve previous root role data"}
|
||||||
|
}
|
||||||
|
err = signed.VerifySignatures(
|
||||||
|
root, data.BaseRole{Keys: utils.CertsToKeys(trustedLeafCerts, allTrustedIntCerts), Threshold: prevRootRoleData.Threshold})
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err)
|
||||||
|
return nil, &ErrRootRotationFail{Reason: "failed to validate data with current trusted certificates"}
|
||||||
|
}
|
||||||
|
// Clear the IsValid marks we could have received from VerifySignatures
|
||||||
|
for i := range root.Signatures {
|
||||||
|
root.Signatures[i].IsValid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regardless of having a previous root or not, confirm that the new root validates against the trust pinning
|
||||||
|
logrus.Debugf("checking root against trust_pinning config for %s", gun)
|
||||||
|
trustPinCheckFunc, err := NewTrustPinChecker(trustPinning, gun, !havePrevRoot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &ErrValidationFail{Reason: err.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
|
validPinnedCerts := map[string]*x509.Certificate{}
|
||||||
|
for id, cert := range certsFromRoot {
|
||||||
|
logrus.Debugf("checking trust-pinning for cert: %s", id)
|
||||||
|
if ok := trustPinCheckFunc(cert, validIntCerts[id]); !ok {
|
||||||
|
logrus.Debugf("trust-pinning check failed for cert: %s", id)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
validPinnedCerts[id] = cert
|
||||||
|
}
|
||||||
|
if len(validPinnedCerts) == 0 {
|
||||||
|
return nil, &ErrValidationFail{Reason: "unable to match any certificates to trust_pinning config"}
|
||||||
|
}
|
||||||
|
certsFromRoot = validPinnedCerts
|
||||||
|
|
||||||
|
// Validate the integrity of the new root (does it have valid signatures)
|
||||||
|
// Note that certsFromRoot is guaranteed to be unchanged only if we had prior cert data for this GUN or enabled TOFUS
|
||||||
|
// If we attempted to pin a certain certificate or CA, certsFromRoot could have been pruned accordingly
|
||||||
|
err = signed.VerifySignatures(root, data.BaseRole{
|
||||||
|
Keys: utils.CertsToKeys(certsFromRoot, validIntCerts), Threshold: rootRole.Threshold})
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err)
|
||||||
|
return nil, &ErrValidationFail{Reason: "failed to validate integrity of roots"}
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("root validation succeeded for %s", gun)
|
||||||
|
// Call RootFromSigned to make sure we pick up on the IsValid markings from VerifySignatures
|
||||||
|
return data.RootFromSigned(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchCNToGun checks that the common name in a cert is valid for the given gun.
|
||||||
|
// This allows wildcards as suffixes, e.g. `namespace/*`
|
||||||
|
func MatchCNToGun(commonName string, gun data.GUN) bool {
|
||||||
|
if strings.HasSuffix(commonName, wildcard) {
|
||||||
|
prefix := strings.TrimRight(commonName, wildcard)
|
||||||
|
logrus.Debugf("checking gun %s against wildcard prefix %s", gun, prefix)
|
||||||
|
return strings.HasPrefix(gun.String(), prefix)
|
||||||
|
}
|
||||||
|
return commonName == gun.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// validRootLeafCerts returns a list of possibly (if checkExpiry is true) non-expired, non-sha1 certificates
|
||||||
|
// found in root whose Common-Names match the provided GUN. Note that this
|
||||||
|
// "validity" alone does not imply any measure of trust.
|
||||||
|
func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun data.GUN, checkExpiry bool) (map[string]*x509.Certificate, error) {
|
||||||
|
validLeafCerts := make(map[string]*x509.Certificate)
|
||||||
|
|
||||||
|
// Go through every leaf certificate and check that the CN matches the gun
|
||||||
|
for id, cert := range allLeafCerts {
|
||||||
|
// Validate that this leaf certificate has a CN that matches the gun
|
||||||
|
if !MatchCNToGun(cert.Subject.CommonName, gun) {
|
||||||
|
logrus.Debugf("error leaf certificate CN: %s doesn't match the given GUN: %s",
|
||||||
|
cert.Subject.CommonName, gun)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Make sure the certificate is not expired if checkExpiry is true
|
||||||
|
// and warn if it hasn't expired yet but is within 6 months of expiry
|
||||||
|
if err := utils.ValidateCertificate(cert, checkExpiry); err != nil {
|
||||||
|
logrus.Debugf("%s is invalid: %s", id, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
validLeafCerts[id] = cert
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(validLeafCerts) < 1 {
|
||||||
|
logrus.Debugf("didn't find any valid leaf certificates for %s", gun)
|
||||||
|
return nil, errors.New("no valid leaf certificates found in any of the root keys")
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("found %d valid leaf certificates for %s: %s", len(validLeafCerts), gun,
|
||||||
|
prettyFormatCertIDs(validLeafCerts))
|
||||||
|
return validLeafCerts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validRootIntCerts filters the passed in structure of intermediate certificates to only include non-expired, non-sha1 certificates
|
||||||
|
// Note that this "validity" alone does not imply any measure of trust.
|
||||||
|
func validRootIntCerts(allIntCerts map[string][]*x509.Certificate) map[string][]*x509.Certificate {
|
||||||
|
validIntCerts := make(map[string][]*x509.Certificate)
|
||||||
|
|
||||||
|
// Go through every leaf cert ID, and build its valid intermediate certificate list
|
||||||
|
for leafID, intCertList := range allIntCerts {
|
||||||
|
for _, intCert := range intCertList {
|
||||||
|
if err := utils.ValidateCertificate(intCert, true); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
validIntCerts[leafID] = append(validIntCerts[leafID], intCert)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return validIntCerts
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseAllCerts returns two maps, one with all of the leafCertificates and one
|
||||||
|
// with all the intermediate certificates found in signedRoot
|
||||||
|
func parseAllCerts(signedRoot *data.SignedRoot) (map[string]*x509.Certificate, map[string][]*x509.Certificate) {
|
||||||
|
if signedRoot == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
leafCerts := make(map[string]*x509.Certificate)
|
||||||
|
intCerts := make(map[string][]*x509.Certificate)
|
||||||
|
|
||||||
|
// Before we loop through all root keys available, make sure any exist
|
||||||
|
rootRoles, ok := signedRoot.Signed.Roles[data.CanonicalRootRole]
|
||||||
|
if !ok {
|
||||||
|
logrus.Debugf("tried to parse certificates from invalid root signed data")
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("found the following root keys: %v", rootRoles.KeyIDs)
|
||||||
|
// Iterate over every keyID for the root role inside of roots.json
|
||||||
|
for _, keyID := range rootRoles.KeyIDs {
|
||||||
|
// check that the key exists in the signed root keys map
|
||||||
|
key, ok := signedRoot.Signed.Keys[keyID]
|
||||||
|
if !ok {
|
||||||
|
logrus.Debugf("error while getting data for keyID: %s", keyID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode all the x509 certificates that were bundled with this
|
||||||
|
// Specific root key
|
||||||
|
decodedCerts, err := utils.LoadCertBundleFromPEM(key.Public())
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("error while parsing root certificate with keyID: %s, %v", keyID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all non-CA certificates in the decoded certificates
|
||||||
|
leafCertList := utils.GetLeafCerts(decodedCerts)
|
||||||
|
|
||||||
|
// If we got no leaf certificates or we got more than one, fail
|
||||||
|
if len(leafCertList) != 1 {
|
||||||
|
logrus.Debugf("invalid chain due to leaf certificate missing or too many leaf certificates for keyID: %s", keyID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// If we found a leaf certificate, assert that the cert bundle started with a leaf
|
||||||
|
if decodedCerts[0].IsCA {
|
||||||
|
logrus.Debugf("invalid chain due to leaf certificate not being first certificate for keyID: %s", keyID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the ID of the leaf certificate
|
||||||
|
leafCert := leafCertList[0]
|
||||||
|
|
||||||
|
// Store the leaf cert in the map
|
||||||
|
leafCerts[key.ID()] = leafCert
|
||||||
|
|
||||||
|
// Get all the remainder certificates marked as a CA to be used as intermediates
|
||||||
|
intermediateCerts := utils.GetIntermediateCerts(decodedCerts)
|
||||||
|
intCerts[key.ID()] = intermediateCerts
|
||||||
|
}
|
||||||
|
|
||||||
|
return leafCerts, intCerts
|
||||||
|
}
|
31
src/vendor/github.com/docker/notary/trustpinning/test.crt
generated
vendored
Normal file
31
src/vendor/github.com/docker/notary/trustpinning/test.crt
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIFKzCCAxWgAwIBAgIQRyp9QqcJfd3ayqdjiz8xIDALBgkqhkiG9w0BAQswODEa
|
||||||
|
MBgGA1UEChMRZG9ja2VyLmNvbS9ub3RhcnkxGjAYBgNVBAMTEWRvY2tlci5jb20v
|
||||||
|
bm90YXJ5MB4XDTE1MDcxNzA2MzQyM1oXDTE3MDcxNjA2MzQyM1owODEaMBgGA1UE
|
||||||
|
ChMRZG9ja2VyLmNvbS9ub3RhcnkxGjAYBgNVBAMTEWRvY2tlci5jb20vbm90YXJ5
|
||||||
|
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoQffrzsYnsH8vGf4Jh55
|
||||||
|
Cj5wrjUGzD/sHkaFHptjJ6ToJGJv5yMAPxzyInu5sIoGLJapnYVBoAU0YgI9qlAc
|
||||||
|
YA6SxaSwgm6rpvmnl8Qn0qc6ger3inpGaUJylWHuPwWkvcimQAqHZx2dQtL7g6kp
|
||||||
|
rmKeTWpWoWLw3JoAUZUVhZMd6a22ZL/DvAw+Hrogbz4XeyahFb9IH402zPxN6vga
|
||||||
|
JEFTF0Ji1jtNg0Mo4pb9SHsMsiw+LZK7SffHVKPxvd21m/biNmwsgExA3U8OOG8p
|
||||||
|
uygfacys5c8+ZrX+ZFG/cvwKz0k6/QfJU40s6MhXw5C2WttdVmsG9/7rGFYjHoIJ
|
||||||
|
weDyxgWk7vxKzRJI/un7cagDIaQsKrJQcCHIGFRlpIR5TwX7vl3R7cRncrDRMVvc
|
||||||
|
VSEG2esxbw7jtzIp/ypnVRxcOny7IypyjKqVeqZ6HgxZtTBVrF1O/aHo2kvlwyRS
|
||||||
|
Aus4kvh6z3+jzTm9EzfXiPQzY9BEk5gOLxhW9rc6UhlS+pe5lkaN/Hyqy/lPuq89
|
||||||
|
fMr2rr7lf5WFdFnze6WNYMAaW7dNA4NE0dyD53428ZLXxNVPL4WU66Gac6lynQ8l
|
||||||
|
r5tPsYIFXzh6FVaRKGQUtW1hz9ecO6Y27Rh2JsyiIxgUqk2ooxE69uN42t+dtqKC
|
||||||
|
1s8G/7VtY8GDALFLYTnzLvsCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgCgMBMGA1Ud
|
||||||
|
JQQMMAoGCCsGAQUFBwMDMAwGA1UdEwEB/wQCMAAwCwYJKoZIhvcNAQELA4ICAQBM
|
||||||
|
Oll3G/XBz8idiNdNJDWUh+5w3ojmwanrTBdCdqEk1WenaR6DtcflJx6Z3f/mwV4o
|
||||||
|
b1skOAX1yX5RCahJHUMxMicz/Q38pOVelGPrWnc3TJB+VKjGyHXlQDVkZFb+4+ef
|
||||||
|
wtj7HngXhHFFDSgjm3EdMndvgDQ7SQb4skOnCNS9iyX7eXxhFBCZmZL+HALKBj2B
|
||||||
|
yhV4IcBDqmp504t14rx9/Jvty0dG7fY7I51gEQpm4S02JML5xvTm1xfboWIhZODI
|
||||||
|
swEAO+ekBoFHbS1Q9KMPjIAw3TrCHH8x8XZq5zsYtAC1yZHdCKa26aWdy56A9eHj
|
||||||
|
O1VxzwmbNyXRenVuBYP+0wr3HVKFG4JJ4ZZpNZzQW/pqEPghCTJIvIueK652ByUc
|
||||||
|
//sv+nXd5f19LeES9pf0l253NDaFZPb6aegKfquWh8qlQBmUQ2GzaTLbtmNd28M6
|
||||||
|
W7iL7tkKZe1ZnBz9RKgtPrDjjWGZInjjcOU8EtT4SLq7kCVDmPs5MD8vaAm96JsE
|
||||||
|
jmLC3Uu/4k7HiDYX0i0mOWkFjZQMdVatcIF5FPSppwsSbW8QidnXt54UtwtFDEPz
|
||||||
|
lpjs7ybeQE71JXcMZnVIK4bjRXsEFPI98RpIlEdedbSUdYAncLNJRT7HZBMPGSwZ
|
||||||
|
0PNJuglnlr3srVzdW1dz2xQjdvLwxy6mNUF6rbQBWA==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
123
src/vendor/github.com/docker/notary/trustpinning/trustpin.go
generated
vendored
Normal file
123
src/vendor/github.com/docker/notary/trustpinning/trustpin.go
generated
vendored
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package trustpinning
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"github.com/docker/notary/tuf/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TrustPinConfig represents the configuration under the trust_pinning section of the config file
|
||||||
|
// This struct represents the preferred way to bootstrap trust for this repository
|
||||||
|
type TrustPinConfig struct {
|
||||||
|
CA map[string]string
|
||||||
|
Certs map[string][]string
|
||||||
|
DisableTOFU bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type trustPinChecker struct {
|
||||||
|
gun data.GUN
|
||||||
|
config TrustPinConfig
|
||||||
|
pinnedCAPool *x509.CertPool
|
||||||
|
pinnedCertIDs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CertChecker is a function type that will be used to check leaf certs against pinned trust
|
||||||
|
type CertChecker func(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool
|
||||||
|
|
||||||
|
// NewTrustPinChecker returns a new certChecker function from a TrustPinConfig for a GUN
|
||||||
|
func NewTrustPinChecker(trustPinConfig TrustPinConfig, gun data.GUN, firstBootstrap bool) (CertChecker, error) {
|
||||||
|
t := trustPinChecker{gun: gun, config: trustPinConfig}
|
||||||
|
// Determine the mode, and if it's even valid
|
||||||
|
if pinnedCerts, ok := trustPinConfig.Certs[gun.String()]; ok {
|
||||||
|
logrus.Debugf("trust-pinning using Cert IDs")
|
||||||
|
t.pinnedCertIDs = pinnedCerts
|
||||||
|
return t.certsCheck, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if caFilepath, err := getPinnedCAFilepathByPrefix(gun, trustPinConfig); err == nil {
|
||||||
|
logrus.Debugf("trust-pinning using root CA bundle at: %s", caFilepath)
|
||||||
|
|
||||||
|
// Try to add the CA certs from its bundle file to our certificate store,
|
||||||
|
// and use it to validate certs in the root.json later
|
||||||
|
caCerts, err := utils.LoadCertBundleFromFile(caFilepath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not load root cert from CA path")
|
||||||
|
}
|
||||||
|
// Now only consider certificates that are direct children from this CA cert chain
|
||||||
|
caRootPool := x509.NewCertPool()
|
||||||
|
for _, caCert := range caCerts {
|
||||||
|
if err = utils.ValidateCertificate(caCert, true); err != nil {
|
||||||
|
logrus.Debugf("ignoring root CA certificate with CN %s in bundle: %s", caCert.Subject.CommonName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
caRootPool.AddCert(caCert)
|
||||||
|
}
|
||||||
|
// If we didn't have any valid CA certs, error out
|
||||||
|
if len(caRootPool.Subjects()) == 0 {
|
||||||
|
return nil, fmt.Errorf("invalid CA certs provided")
|
||||||
|
}
|
||||||
|
t.pinnedCAPool = caRootPool
|
||||||
|
return t.caCheck, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If TOFUs is disabled and we don't have any previous trusted root data for this GUN, we error out
|
||||||
|
if trustPinConfig.DisableTOFU && firstBootstrap {
|
||||||
|
return nil, fmt.Errorf("invalid trust pinning specified")
|
||||||
|
|
||||||
|
}
|
||||||
|
return t.tofusCheck, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t trustPinChecker) certsCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool {
|
||||||
|
// reconstruct the leaf + intermediate cert chain, which is bundled as {leaf, intermediates...},
|
||||||
|
// in order to get the matching id in the root file
|
||||||
|
key, err := utils.CertBundleToKey(leafCert, intCerts)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debug("error creating cert bundle: ", err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return utils.StrSliceContains(t.pinnedCertIDs, key.ID())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t trustPinChecker) caCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool {
|
||||||
|
// Use intermediate certificates included in the root TUF metadata for our validation
|
||||||
|
caIntPool := x509.NewCertPool()
|
||||||
|
for _, intCert := range intCerts {
|
||||||
|
caIntPool.AddCert(intCert)
|
||||||
|
}
|
||||||
|
// Attempt to find a valid certificate chain from the leaf cert to CA root
|
||||||
|
// Use this certificate if such a valid chain exists (possibly using intermediates)
|
||||||
|
var err error
|
||||||
|
if _, err = leafCert.Verify(x509.VerifyOptions{Roots: t.pinnedCAPool, Intermediates: caIntPool}); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
logrus.Debugf("unable to find a valid certificate chain from leaf cert to CA root: %s", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t trustPinChecker) tofusCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Will return the CA filepath corresponding to the most specific (longest) entry in the map that is still a prefix
|
||||||
|
// of the provided gun. Returns an error if no entry matches this GUN as a prefix.
|
||||||
|
func getPinnedCAFilepathByPrefix(gun data.GUN, t TrustPinConfig) (string, error) {
|
||||||
|
specificGUN := ""
|
||||||
|
specificCAFilepath := ""
|
||||||
|
foundCA := false
|
||||||
|
for gunPrefix, caFilepath := range t.CA {
|
||||||
|
if strings.HasPrefix(gun.String(), gunPrefix) && len(gunPrefix) >= len(specificGUN) {
|
||||||
|
specificGUN = gunPrefix
|
||||||
|
specificCAFilepath = caFilepath
|
||||||
|
foundCA = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !foundCA {
|
||||||
|
return "", fmt.Errorf("could not find pinned CA for GUN: %s", gun)
|
||||||
|
}
|
||||||
|
return specificCAFilepath, nil
|
||||||
|
}
|
30
src/vendor/github.com/docker/notary/tuf/LICENSE
generated
vendored
Normal file
30
src/vendor/github.com/docker/notary/tuf/LICENSE
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
Copyright (c) 2015, Docker Inc.
|
||||||
|
Copyright (c) 2014-2015 Prime Directive, Inc.
|
||||||
|
|
||||||
|
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 Prime Directive, 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.
|
6
src/vendor/github.com/docker/notary/tuf/README.md
generated
vendored
Normal file
6
src/vendor/github.com/docker/notary/tuf/README.md
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
## Credits
|
||||||
|
|
||||||
|
This implementation was originally forked from [flynn/go-tuf](https://github.com/flynn/go-tuf)
|
||||||
|
|
||||||
|
This implementation retains the same 3 Clause BSD license present on
|
||||||
|
the original flynn implementation.
|
732
src/vendor/github.com/docker/notary/tuf/builder.go
generated
vendored
Normal file
732
src/vendor/github.com/docker/notary/tuf/builder.go
generated
vendored
Normal file
@ -0,0 +1,732 @@
|
|||||||
|
package tuf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/go/canonical/json"
|
||||||
|
"github.com/docker/notary"
|
||||||
|
|
||||||
|
"github.com/docker/notary/trustpinning"
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"github.com/docker/notary/tuf/signed"
|
||||||
|
"github.com/docker/notary/tuf/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrBuildDone is returned when any functions are called on RepoBuilder, and it
|
||||||
|
// is already finished building
|
||||||
|
var ErrBuildDone = fmt.Errorf(
|
||||||
|
"the builder has finished building and cannot accept any more input or produce any more output")
|
||||||
|
|
||||||
|
// ErrInvalidBuilderInput is returned when RepoBuilder.Load is called
|
||||||
|
// with the wrong type of metadata for the state that it's in
|
||||||
|
type ErrInvalidBuilderInput struct{ msg string }
|
||||||
|
|
||||||
|
func (e ErrInvalidBuilderInput) Error() string {
|
||||||
|
return e.msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConsistentInfo is the consistent name and size of a role, or just the name
|
||||||
|
// of the role and a -1 if no file metadata for the role is known
|
||||||
|
type ConsistentInfo struct {
|
||||||
|
RoleName data.RoleName
|
||||||
|
fileMeta data.FileMeta
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChecksumKnown determines whether or not we know enough to provide a size and
|
||||||
|
// consistent name
|
||||||
|
func (c ConsistentInfo) ChecksumKnown() bool {
|
||||||
|
// empty hash, no size : this is the zero value
|
||||||
|
return len(c.fileMeta.Hashes) > 0 || c.fileMeta.Length != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConsistentName returns the consistent name (rolename.sha256) for the role
|
||||||
|
// given this consistent information
|
||||||
|
func (c ConsistentInfo) ConsistentName() string {
|
||||||
|
return utils.ConsistentName(c.RoleName.String(), c.fileMeta.Hashes[notary.SHA256])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Length returns the expected length of the role as per this consistent
|
||||||
|
// information - if no checksum information is known, the size is -1.
|
||||||
|
func (c ConsistentInfo) Length() int64 {
|
||||||
|
if c.ChecksumKnown() {
|
||||||
|
return c.fileMeta.Length
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// RepoBuilder is an interface for an object which builds a tuf.Repo
|
||||||
|
type RepoBuilder interface {
|
||||||
|
Load(roleName data.RoleName, content []byte, minVersion int, allowExpired bool) error
|
||||||
|
LoadRootForUpdate(content []byte, minVersion int, isFinal bool) error
|
||||||
|
GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int, error)
|
||||||
|
GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, int, error)
|
||||||
|
Finish() (*Repo, *Repo, error)
|
||||||
|
BootstrapNewBuilder() RepoBuilder
|
||||||
|
BootstrapNewBuilderWithNewTrustpin(trustpin trustpinning.TrustPinConfig) RepoBuilder
|
||||||
|
|
||||||
|
// informative functions
|
||||||
|
IsLoaded(roleName data.RoleName) bool
|
||||||
|
GetLoadedVersion(roleName data.RoleName) int
|
||||||
|
GetConsistentInfo(roleName data.RoleName) ConsistentInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// finishedBuilder refuses any more input or output
|
||||||
|
type finishedBuilder struct{}
|
||||||
|
|
||||||
|
func (f finishedBuilder) Load(roleName data.RoleName, content []byte, minVersion int, allowExpired bool) error {
|
||||||
|
return ErrBuildDone
|
||||||
|
}
|
||||||
|
func (f finishedBuilder) LoadRootForUpdate(content []byte, minVersion int, isFinal bool) error {
|
||||||
|
return ErrBuildDone
|
||||||
|
}
|
||||||
|
func (f finishedBuilder) GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int, error) {
|
||||||
|
return nil, 0, ErrBuildDone
|
||||||
|
}
|
||||||
|
func (f finishedBuilder) GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, int, error) {
|
||||||
|
return nil, 0, ErrBuildDone
|
||||||
|
}
|
||||||
|
func (f finishedBuilder) Finish() (*Repo, *Repo, error) { return nil, nil, ErrBuildDone }
|
||||||
|
func (f finishedBuilder) BootstrapNewBuilder() RepoBuilder { return f }
|
||||||
|
func (f finishedBuilder) BootstrapNewBuilderWithNewTrustpin(trustpin trustpinning.TrustPinConfig) RepoBuilder {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
func (f finishedBuilder) IsLoaded(roleName data.RoleName) bool { return false }
|
||||||
|
func (f finishedBuilder) GetLoadedVersion(roleName data.RoleName) int { return 0 }
|
||||||
|
func (f finishedBuilder) GetConsistentInfo(roleName data.RoleName) ConsistentInfo {
|
||||||
|
return ConsistentInfo{RoleName: roleName}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRepoBuilder is the only way to get a pre-built RepoBuilder
|
||||||
|
func NewRepoBuilder(gun data.GUN, cs signed.CryptoService, trustpin trustpinning.TrustPinConfig) RepoBuilder {
|
||||||
|
return NewBuilderFromRepo(gun, NewRepo(cs), trustpin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBuilderFromRepo allows us to bootstrap a builder given existing repo data.
|
||||||
|
// YOU PROBABLY SHOULDN'T BE USING THIS OUTSIDE OF TESTING CODE!!!
|
||||||
|
func NewBuilderFromRepo(gun data.GUN, repo *Repo, trustpin trustpinning.TrustPinConfig) RepoBuilder {
|
||||||
|
return &repoBuilderWrapper{
|
||||||
|
RepoBuilder: &repoBuilder{
|
||||||
|
repo: repo,
|
||||||
|
invalidRoles: NewRepo(nil),
|
||||||
|
gun: gun,
|
||||||
|
trustpin: trustpin,
|
||||||
|
loadedNotChecksummed: make(map[data.RoleName][]byte),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// repoBuilderWrapper embeds a repoBuilder, but once Finish is called, swaps
|
||||||
|
// the embed out with a finishedBuilder
|
||||||
|
type repoBuilderWrapper struct {
|
||||||
|
RepoBuilder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rbw *repoBuilderWrapper) Finish() (*Repo, *Repo, error) {
|
||||||
|
switch rbw.RepoBuilder.(type) {
|
||||||
|
case finishedBuilder:
|
||||||
|
return rbw.RepoBuilder.Finish()
|
||||||
|
default:
|
||||||
|
old := rbw.RepoBuilder
|
||||||
|
rbw.RepoBuilder = finishedBuilder{}
|
||||||
|
return old.Finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// repoBuilder actually builds a tuf.Repo
|
||||||
|
type repoBuilder struct {
|
||||||
|
repo *Repo
|
||||||
|
invalidRoles *Repo
|
||||||
|
|
||||||
|
// needed for root trust pininng verification
|
||||||
|
gun data.GUN
|
||||||
|
trustpin trustpinning.TrustPinConfig
|
||||||
|
|
||||||
|
// in case we load root and/or targets before snapshot and timestamp (
|
||||||
|
// or snapshot and not timestamp), so we know what to verify when the
|
||||||
|
// data with checksums come in
|
||||||
|
loadedNotChecksummed map[data.RoleName][]byte
|
||||||
|
|
||||||
|
// bootstrapped values to validate a new root
|
||||||
|
prevRoot *data.SignedRoot
|
||||||
|
bootstrappedRootChecksum *data.FileMeta
|
||||||
|
|
||||||
|
// for bootstrapping the next builder
|
||||||
|
nextRootChecksum *data.FileMeta
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *repoBuilder) Finish() (*Repo, *Repo, error) {
|
||||||
|
return rb.repo, rb.invalidRoles, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *repoBuilder) BootstrapNewBuilder() RepoBuilder {
|
||||||
|
return &repoBuilderWrapper{RepoBuilder: &repoBuilder{
|
||||||
|
repo: NewRepo(rb.repo.cryptoService),
|
||||||
|
invalidRoles: NewRepo(nil),
|
||||||
|
gun: rb.gun,
|
||||||
|
loadedNotChecksummed: make(map[data.RoleName][]byte),
|
||||||
|
trustpin: rb.trustpin,
|
||||||
|
|
||||||
|
prevRoot: rb.repo.Root,
|
||||||
|
bootstrappedRootChecksum: rb.nextRootChecksum,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *repoBuilder) BootstrapNewBuilderWithNewTrustpin(trustpin trustpinning.TrustPinConfig) RepoBuilder {
|
||||||
|
return &repoBuilderWrapper{RepoBuilder: &repoBuilder{
|
||||||
|
repo: NewRepo(rb.repo.cryptoService),
|
||||||
|
gun: rb.gun,
|
||||||
|
loadedNotChecksummed: make(map[data.RoleName][]byte),
|
||||||
|
trustpin: trustpin,
|
||||||
|
|
||||||
|
prevRoot: rb.repo.Root,
|
||||||
|
bootstrappedRootChecksum: rb.nextRootChecksum,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLoaded returns whether a particular role has already been loaded
|
||||||
|
func (rb *repoBuilder) IsLoaded(roleName data.RoleName) bool {
|
||||||
|
switch roleName {
|
||||||
|
case data.CanonicalRootRole:
|
||||||
|
return rb.repo.Root != nil
|
||||||
|
case data.CanonicalSnapshotRole:
|
||||||
|
return rb.repo.Snapshot != nil
|
||||||
|
case data.CanonicalTimestampRole:
|
||||||
|
return rb.repo.Timestamp != nil
|
||||||
|
default:
|
||||||
|
return rb.repo.Targets[roleName] != nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLoadedVersion returns the metadata version, if it is loaded, or 1 (the
|
||||||
|
// minimum valid version number) otherwise
|
||||||
|
func (rb *repoBuilder) GetLoadedVersion(roleName data.RoleName) int {
|
||||||
|
switch {
|
||||||
|
case roleName == data.CanonicalRootRole && rb.repo.Root != nil:
|
||||||
|
return rb.repo.Root.Signed.Version
|
||||||
|
case roleName == data.CanonicalSnapshotRole && rb.repo.Snapshot != nil:
|
||||||
|
return rb.repo.Snapshot.Signed.Version
|
||||||
|
case roleName == data.CanonicalTimestampRole && rb.repo.Timestamp != nil:
|
||||||
|
return rb.repo.Timestamp.Signed.Version
|
||||||
|
default:
|
||||||
|
if tgts, ok := rb.repo.Targets[roleName]; ok {
|
||||||
|
return tgts.Signed.Version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConsistentInfo returns the consistent name and size of a role, if it is known,
|
||||||
|
// otherwise just the rolename and a -1 for size (both of which are inside a
|
||||||
|
// ConsistentInfo object)
|
||||||
|
func (rb *repoBuilder) GetConsistentInfo(roleName data.RoleName) ConsistentInfo {
|
||||||
|
info := ConsistentInfo{RoleName: roleName} // starts out with unknown filemeta
|
||||||
|
switch roleName {
|
||||||
|
case data.CanonicalTimestampRole:
|
||||||
|
// we do not want to get a consistent timestamp, but we do want to
|
||||||
|
// limit its size
|
||||||
|
info.fileMeta.Length = notary.MaxTimestampSize
|
||||||
|
case data.CanonicalSnapshotRole:
|
||||||
|
if rb.repo.Timestamp != nil {
|
||||||
|
info.fileMeta = rb.repo.Timestamp.Signed.Meta[roleName.String()]
|
||||||
|
}
|
||||||
|
case data.CanonicalRootRole:
|
||||||
|
switch {
|
||||||
|
case rb.bootstrappedRootChecksum != nil:
|
||||||
|
info.fileMeta = *rb.bootstrappedRootChecksum
|
||||||
|
case rb.repo.Snapshot != nil:
|
||||||
|
info.fileMeta = rb.repo.Snapshot.Signed.Meta[roleName.String()]
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if rb.repo.Snapshot != nil {
|
||||||
|
info.fileMeta = rb.repo.Snapshot.Signed.Meta[roleName.String()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *repoBuilder) Load(roleName data.RoleName, content []byte, minVersion int, allowExpired bool) error {
|
||||||
|
return rb.loadOptions(roleName, content, minVersion, allowExpired, false, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadRootForUpdate adds additional flags for updating the root.json file
|
||||||
|
func (rb *repoBuilder) LoadRootForUpdate(content []byte, minVersion int, isFinal bool) error {
|
||||||
|
if err := rb.loadOptions(data.CanonicalRootRole, content, minVersion, !isFinal, !isFinal, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !isFinal {
|
||||||
|
rb.prevRoot = rb.repo.Root
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadOptions adds additional flags that should only be used for updating the root.json
|
||||||
|
func (rb *repoBuilder) loadOptions(roleName data.RoleName, content []byte, minVersion int, allowExpired, skipChecksum, allowLoaded bool) error {
|
||||||
|
if !data.ValidRole(roleName) {
|
||||||
|
return ErrInvalidBuilderInput{msg: fmt.Sprintf("%s is an invalid role", roleName)}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allowLoaded && rb.IsLoaded(roleName) {
|
||||||
|
return ErrInvalidBuilderInput{msg: fmt.Sprintf("%s has already been loaded", roleName)}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
switch roleName {
|
||||||
|
case data.CanonicalRootRole:
|
||||||
|
break
|
||||||
|
case data.CanonicalTimestampRole, data.CanonicalSnapshotRole, data.CanonicalTargetsRole:
|
||||||
|
err = rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalRootRole})
|
||||||
|
default: // delegations
|
||||||
|
err = rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalRootRole, data.CanonicalTargetsRole})
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch roleName {
|
||||||
|
case data.CanonicalRootRole:
|
||||||
|
return rb.loadRoot(content, minVersion, allowExpired, skipChecksum)
|
||||||
|
case data.CanonicalSnapshotRole:
|
||||||
|
return rb.loadSnapshot(content, minVersion, allowExpired)
|
||||||
|
case data.CanonicalTimestampRole:
|
||||||
|
return rb.loadTimestamp(content, minVersion, allowExpired)
|
||||||
|
case data.CanonicalTargetsRole:
|
||||||
|
return rb.loadTargets(content, minVersion, allowExpired)
|
||||||
|
default:
|
||||||
|
return rb.loadDelegation(roleName, content, minVersion, allowExpired)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *repoBuilder) checkPrereqsLoaded(prereqRoles []data.RoleName) error {
|
||||||
|
for _, req := range prereqRoles {
|
||||||
|
if !rb.IsLoaded(req) {
|
||||||
|
return ErrInvalidBuilderInput{msg: fmt.Sprintf("%s must be loaded first", req)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateSnapshot generates a new snapshot given a previous (optional) snapshot
|
||||||
|
// We can't just load the previous snapshot, because it may have been signed by a different
|
||||||
|
// snapshot key (maybe from a previous root version). Note that we need the root role and
|
||||||
|
// targets role to be loaded, because we need to generate metadata for both (and we need
|
||||||
|
// the root to be loaded so we can get the snapshot role to sign with)
|
||||||
|
func (rb *repoBuilder) GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int, error) {
|
||||||
|
switch {
|
||||||
|
case rb.repo.cryptoService == nil:
|
||||||
|
return nil, 0, ErrInvalidBuilderInput{msg: "cannot generate snapshot without a cryptoservice"}
|
||||||
|
case rb.IsLoaded(data.CanonicalSnapshotRole):
|
||||||
|
return nil, 0, ErrInvalidBuilderInput{msg: "snapshot has already been loaded"}
|
||||||
|
case rb.IsLoaded(data.CanonicalTimestampRole):
|
||||||
|
return nil, 0, ErrInvalidBuilderInput{msg: "cannot generate snapshot if timestamp has already been loaded"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalRootRole}); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no previous snapshot, we need to generate one, and so the targets must
|
||||||
|
// have already been loaded. Otherwise, so long as the previous snapshot structure is
|
||||||
|
// valid (it has a targets meta), we're good.
|
||||||
|
switch prev {
|
||||||
|
case nil:
|
||||||
|
if err := rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalTargetsRole}); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rb.repo.InitSnapshot(); err != nil {
|
||||||
|
rb.repo.Snapshot = nil
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if err := data.IsValidSnapshotStructure(prev.Signed); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
rb.repo.Snapshot = prev
|
||||||
|
}
|
||||||
|
|
||||||
|
sgnd, err := rb.repo.SignSnapshot(data.DefaultExpires(data.CanonicalSnapshotRole))
|
||||||
|
if err != nil {
|
||||||
|
rb.repo.Snapshot = nil
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sgndJSON, err := json.Marshal(sgnd)
|
||||||
|
if err != nil {
|
||||||
|
rb.repo.Snapshot = nil
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadedNotChecksummed should currently contain the root awaiting checksumming,
|
||||||
|
// 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,
|
||||||
|
// remove all of them from rb.loadedNotChecksummed
|
||||||
|
for tgtName := range rb.repo.Targets {
|
||||||
|
delete(rb.loadedNotChecksummed, data.RoleName(tgtName))
|
||||||
|
}
|
||||||
|
delete(rb.loadedNotChecksummed, data.CanonicalRootRole)
|
||||||
|
|
||||||
|
// The timestamp can't have been loaded yet, so we want to cache the snapshot
|
||||||
|
// bytes so we can validate the checksum when a timestamp gets generated or
|
||||||
|
// loaded later.
|
||||||
|
rb.loadedNotChecksummed[data.CanonicalSnapshotRole] = sgndJSON
|
||||||
|
|
||||||
|
return sgndJSON, rb.repo.Snapshot.Signed.Version, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateTimestamp generates a new timestamp given a previous (optional) timestamp
|
||||||
|
// We can't just load the previous timestamp, because it may have been signed by a different
|
||||||
|
// timestamp key (maybe from a previous root version)
|
||||||
|
func (rb *repoBuilder) GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, int, error) {
|
||||||
|
switch {
|
||||||
|
case rb.repo.cryptoService == nil:
|
||||||
|
return nil, 0, ErrInvalidBuilderInput{msg: "cannot generate timestamp without a cryptoservice"}
|
||||||
|
case rb.IsLoaded(data.CanonicalTimestampRole):
|
||||||
|
return nil, 0, ErrInvalidBuilderInput{msg: "timestamp has already been loaded"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignTimestamp always serializes the loaded snapshot and signs in the data, so we must always
|
||||||
|
// have the snapshot loaded first
|
||||||
|
if err := rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalRootRole, data.CanonicalSnapshotRole}); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch prev {
|
||||||
|
case nil:
|
||||||
|
if err := rb.repo.InitTimestamp(); err != nil {
|
||||||
|
rb.repo.Timestamp = nil
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if err := data.IsValidTimestampStructure(prev.Signed); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
rb.repo.Timestamp = prev
|
||||||
|
}
|
||||||
|
|
||||||
|
sgnd, err := rb.repo.SignTimestamp(data.DefaultExpires(data.CanonicalTimestampRole))
|
||||||
|
if err != nil {
|
||||||
|
rb.repo.Timestamp = nil
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sgndJSON, err := json.Marshal(sgnd)
|
||||||
|
if err != nil {
|
||||||
|
rb.repo.Timestamp = nil
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The snapshot should have been loaded (and not checksummed, since a timestamp
|
||||||
|
// cannot have been loaded), so it is awaiting checksumming. Since this
|
||||||
|
// timestamp was generated using the snapshot awaiting checksumming, we can
|
||||||
|
// remove it from rb.loadedNotChecksummed. There should be no other items
|
||||||
|
// awaiting checksumming now since loading/generating a snapshot should have
|
||||||
|
// cleared out everything else in `loadNotChecksummed`.
|
||||||
|
delete(rb.loadedNotChecksummed, data.CanonicalSnapshotRole)
|
||||||
|
|
||||||
|
return sgndJSON, rb.repo.Timestamp.Signed.Version, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadRoot loads a root if one has not been loaded
|
||||||
|
func (rb *repoBuilder) loadRoot(content []byte, minVersion int, allowExpired, skipChecksum bool) error {
|
||||||
|
roleName := data.CanonicalRootRole
|
||||||
|
|
||||||
|
signedObj, err := rb.bytesToSigned(content, data.CanonicalRootRole, skipChecksum)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// ValidateRoot validates against the previous root's role, as well as validates that the root
|
||||||
|
// itself is self-consistent with its own signatures and thresholds.
|
||||||
|
// This assumes that ValidateRoot calls data.RootFromSigned, which validates
|
||||||
|
// the metadata, rather than just unmarshalling signedObject into a SignedRoot object itself.
|
||||||
|
signedRoot, err := trustpinning.ValidateRoot(rb.prevRoot, signedObj, rb.gun, rb.trustpin)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := signed.VerifyVersion(&(signedRoot.Signed.SignedCommon), minVersion); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allowExpired { // check must go at the end because all other validation should pass
|
||||||
|
if err := signed.VerifyExpiry(&(signedRoot.Signed.SignedCommon), roleName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootRole, err := signedRoot.BuildBaseRole(data.CanonicalRootRole)
|
||||||
|
if err != nil { // this should never happen since the root has been validated
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rb.repo.Root = signedRoot
|
||||||
|
rb.repo.originalRootRole = rootRole
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *repoBuilder) loadTimestamp(content []byte, minVersion int, allowExpired bool) error {
|
||||||
|
roleName := data.CanonicalTimestampRole
|
||||||
|
|
||||||
|
timestampRole, err := rb.repo.Root.BuildBaseRole(roleName)
|
||||||
|
if err != nil { // this should never happen, since it's already been validated
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
signedObj, err := rb.bytesToSignedAndValidateSigs(timestampRole, content)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
signedTimestamp, err := data.TimestampFromSigned(signedObj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := signed.VerifyVersion(&(signedTimestamp.Signed.SignedCommon), minVersion); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allowExpired { // check must go at the end because all other validation should pass
|
||||||
|
if err := signed.VerifyExpiry(&(signedTimestamp.Signed.SignedCommon), roleName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rb.validateChecksumsFromTimestamp(signedTimestamp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rb.repo.Timestamp = signedTimestamp
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *repoBuilder) loadSnapshot(content []byte, minVersion int, allowExpired bool) error {
|
||||||
|
roleName := data.CanonicalSnapshotRole
|
||||||
|
|
||||||
|
snapshotRole, err := rb.repo.Root.BuildBaseRole(roleName)
|
||||||
|
if err != nil { // this should never happen, since it's already been validated
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
signedObj, err := rb.bytesToSignedAndValidateSigs(snapshotRole, content)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
signedSnapshot, err := data.SnapshotFromSigned(signedObj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := signed.VerifyVersion(&(signedSnapshot.Signed.SignedCommon), minVersion); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allowExpired { // check must go at the end because all other validation should pass
|
||||||
|
if err := signed.VerifyExpiry(&(signedSnapshot.Signed.SignedCommon), roleName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// at this point, the only thing left to validate is existing checksums - we can use
|
||||||
|
// this snapshot to bootstrap the next builder if needed - and we don't need to do
|
||||||
|
// the 2-value assignment since we've already validated the signedSnapshot, which MUST
|
||||||
|
// have root metadata
|
||||||
|
rootMeta := signedSnapshot.Signed.Meta[data.CanonicalRootRole.String()]
|
||||||
|
rb.nextRootChecksum = &rootMeta
|
||||||
|
|
||||||
|
if err := rb.validateChecksumsFromSnapshot(signedSnapshot); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rb.repo.Snapshot = signedSnapshot
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *repoBuilder) loadTargets(content []byte, minVersion int, allowExpired bool) error {
|
||||||
|
roleName := data.CanonicalTargetsRole
|
||||||
|
|
||||||
|
targetsRole, err := rb.repo.Root.BuildBaseRole(roleName)
|
||||||
|
if err != nil { // this should never happen, since it's already been validated
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
signedObj, err := rb.bytesToSignedAndValidateSigs(targetsRole, content)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
signedTargets, err := data.TargetsFromSigned(signedObj, roleName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := signed.VerifyVersion(&(signedTargets.Signed.SignedCommon), minVersion); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allowExpired { // check must go at the end because all other validation should pass
|
||||||
|
if err := signed.VerifyExpiry(&(signedTargets.Signed.SignedCommon), roleName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signedTargets.Signatures = signedObj.Signatures
|
||||||
|
rb.repo.Targets[roleName] = signedTargets
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *repoBuilder) loadDelegation(roleName data.RoleName, content []byte, minVersion int, allowExpired bool) error {
|
||||||
|
delegationRole, err := rb.repo.GetDelegationRole(roleName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// bytesToSigned checks checksum
|
||||||
|
signedObj, err := rb.bytesToSigned(content, roleName, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
signedTargets, err := data.TargetsFromSigned(signedObj, roleName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := signed.VerifyVersion(&(signedTargets.Signed.SignedCommon), minVersion); err != nil {
|
||||||
|
// don't capture in invalidRoles because the role we received is a rollback
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify signature
|
||||||
|
if err := signed.VerifySignatures(signedObj, delegationRole.BaseRole); err != nil {
|
||||||
|
rb.invalidRoles.Targets[roleName] = signedTargets
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allowExpired { // check must go at the end because all other validation should pass
|
||||||
|
if err := signed.VerifyExpiry(&(signedTargets.Signed.SignedCommon), roleName); err != nil {
|
||||||
|
rb.invalidRoles.Targets[roleName] = signedTargets
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signedTargets.Signatures = signedObj.Signatures
|
||||||
|
rb.repo.Targets[roleName] = signedTargets
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *repoBuilder) validateChecksumsFromTimestamp(ts *data.SignedTimestamp) error {
|
||||||
|
sn, ok := rb.loadedNotChecksummed[data.CanonicalSnapshotRole]
|
||||||
|
if ok {
|
||||||
|
// by this point, the SignedTimestamp has been validated so it must have a snapshot hash
|
||||||
|
snMeta := ts.Signed.Meta[data.CanonicalSnapshotRole.String()].Hashes
|
||||||
|
if err := data.CheckHashes(sn, data.CanonicalSnapshotRole.String(), snMeta); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
delete(rb.loadedNotChecksummed, data.CanonicalSnapshotRole)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *repoBuilder) validateChecksumsFromSnapshot(sn *data.SignedSnapshot) error {
|
||||||
|
var goodRoles []data.RoleName
|
||||||
|
for roleName, loadedBytes := range rb.loadedNotChecksummed {
|
||||||
|
switch roleName {
|
||||||
|
case data.CanonicalSnapshotRole, data.CanonicalTimestampRole:
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
if err := data.CheckHashes(loadedBytes, roleName.String(), sn.Signed.Meta[roleName.String()].Hashes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
goodRoles = append(goodRoles, roleName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, roleName := range goodRoles {
|
||||||
|
delete(rb.loadedNotChecksummed, roleName)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *repoBuilder) validateChecksumFor(content []byte, roleName data.RoleName) error {
|
||||||
|
// validate the bootstrap checksum for root, if provided
|
||||||
|
if roleName == data.CanonicalRootRole && rb.bootstrappedRootChecksum != nil {
|
||||||
|
if err := data.CheckHashes(content, roleName.String(), rb.bootstrappedRootChecksum.Hashes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// but we also want to cache the root content, so that when the snapshot is
|
||||||
|
// loaded it is validated (to make sure everything in the repo is self-consistent)
|
||||||
|
checksums := rb.getChecksumsFor(roleName)
|
||||||
|
if checksums != nil { // as opposed to empty, in which case hash check should fail
|
||||||
|
if err := data.CheckHashes(content, roleName.String(), *checksums); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if roleName != data.CanonicalTimestampRole {
|
||||||
|
// timestamp is the only role which does not need to be checksummed, but
|
||||||
|
// for everything else, cache the contents in the list of roles that have
|
||||||
|
// not been checksummed by the snapshot/timestamp yet
|
||||||
|
rb.loadedNotChecksummed[roleName] = content
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checksums the given bytes, and if they validate, convert to a data.Signed object.
|
||||||
|
// If a checksums are nil (as opposed to empty), adds the bytes to the list of roles that
|
||||||
|
// haven't been checksummed (unless it's a timestamp, which has no checksum reference).
|
||||||
|
func (rb *repoBuilder) bytesToSigned(content []byte, roleName data.RoleName, skipChecksum bool) (*data.Signed, error) {
|
||||||
|
if !skipChecksum {
|
||||||
|
if err := rb.validateChecksumFor(content, roleName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unmarshal to signed
|
||||||
|
signedObj := &data.Signed{}
|
||||||
|
if err := json.Unmarshal(content, signedObj); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return signedObj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *repoBuilder) bytesToSignedAndValidateSigs(role data.BaseRole, content []byte) (*data.Signed, error) {
|
||||||
|
|
||||||
|
signedObj, err := rb.bytesToSigned(content, role.Name, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify signature
|
||||||
|
if err := signed.VerifySignatures(signedObj, role); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return signedObj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the checksum reference (the loaded timestamp for the snapshot role, and
|
||||||
|
// the loaded snapshot for every other role except timestamp and snapshot) is nil,
|
||||||
|
// then return nil for the checksums, meaning that the checksum is not yet
|
||||||
|
// available. If the checksum reference *is* loaded, then always returns the
|
||||||
|
// Hashes object for the given role - if it doesn't exist, returns an empty Hash
|
||||||
|
// object (against which any checksum validation would fail).
|
||||||
|
func (rb *repoBuilder) getChecksumsFor(role data.RoleName) *data.Hashes {
|
||||||
|
var hashes data.Hashes
|
||||||
|
switch role {
|
||||||
|
case data.CanonicalTimestampRole:
|
||||||
|
return nil
|
||||||
|
case data.CanonicalSnapshotRole:
|
||||||
|
if rb.repo.Timestamp == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
hashes = rb.repo.Timestamp.Signed.Meta[data.CanonicalSnapshotRole.String()].Hashes
|
||||||
|
default:
|
||||||
|
if rb.repo.Snapshot == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
hashes = rb.repo.Snapshot.Signed.Meta[role.String()].Hashes
|
||||||
|
}
|
||||||
|
return &hashes
|
||||||
|
}
|
53
src/vendor/github.com/docker/notary/tuf/data/errors.go
generated
vendored
Normal file
53
src/vendor/github.com/docker/notary/tuf/data/errors.go
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// ErrInvalidMetadata is the error to be returned when metadata is invalid
|
||||||
|
type ErrInvalidMetadata struct {
|
||||||
|
role RoleName
|
||||||
|
msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrInvalidMetadata) Error() string {
|
||||||
|
return fmt.Sprintf("%s type metadata invalid: %s", e.role.String(), e.msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrMissingMeta - couldn't find the FileMeta object for the given Role, or
|
||||||
|
// the FileMeta object contained no supported checksums
|
||||||
|
type ErrMissingMeta struct {
|
||||||
|
Role string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrMissingMeta) Error() string {
|
||||||
|
return fmt.Sprintf("no checksums for supported algorithms were provided for %s", e.Role)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrInvalidChecksum is the error to be returned when checksum is invalid
|
||||||
|
type ErrInvalidChecksum struct {
|
||||||
|
alg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrInvalidChecksum) Error() string {
|
||||||
|
return fmt.Sprintf("%s checksum invalid", e.alg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrMismatchedChecksum is the error to be returned when checksum is mismatched
|
||||||
|
type ErrMismatchedChecksum struct {
|
||||||
|
alg string
|
||||||
|
name string
|
||||||
|
expected string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrMismatchedChecksum) Error() string {
|
||||||
|
return fmt.Sprintf("%s checksum for %s did not match: expected %s", e.alg, e.name,
|
||||||
|
e.expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrCertExpired is the error to be returned when a certificate has expired
|
||||||
|
type ErrCertExpired struct {
|
||||||
|
CN string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrCertExpired) Error() string {
|
||||||
|
return fmt.Sprintf("certificate with CN %s is expired", e.CN)
|
||||||
|
}
|
528
src/vendor/github.com/docker/notary/tuf/data/keys.go
generated
vendored
Normal file
528
src/vendor/github.com/docker/notary/tuf/data/keys.go
generated
vendored
Normal file
@ -0,0 +1,528 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/asn1"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/agl/ed25519"
|
||||||
|
"github.com/docker/go/canonical/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PublicKey is the necessary interface for public keys
|
||||||
|
type PublicKey interface {
|
||||||
|
ID() string
|
||||||
|
Algorithm() string
|
||||||
|
Public() []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrivateKey adds the ability to access the private key
|
||||||
|
type PrivateKey interface {
|
||||||
|
PublicKey
|
||||||
|
Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error)
|
||||||
|
Private() []byte
|
||||||
|
CryptoSigner() crypto.Signer
|
||||||
|
SignatureAlgorithm() SigAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyPair holds the public and private key bytes
|
||||||
|
type KeyPair struct {
|
||||||
|
Public []byte `json:"public"`
|
||||||
|
Private []byte `json:"private"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys represents a map of key ID to PublicKey object. It's necessary
|
||||||
|
// to allow us to unmarshal into an interface via the json.Unmarshaller
|
||||||
|
// interface
|
||||||
|
type Keys map[string]PublicKey
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaller interface
|
||||||
|
func (ks *Keys) UnmarshalJSON(data []byte) error {
|
||||||
|
parsed := make(map[string]TUFKey)
|
||||||
|
err := json.Unmarshal(data, &parsed)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
final := make(map[string]PublicKey)
|
||||||
|
for k, tk := range parsed {
|
||||||
|
final[k] = typedPublicKey(tk)
|
||||||
|
}
|
||||||
|
*ks = final
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyList represents a list of keys
|
||||||
|
type KeyList []PublicKey
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaller interface
|
||||||
|
func (ks *KeyList) UnmarshalJSON(data []byte) error {
|
||||||
|
parsed := make([]TUFKey, 0, 1)
|
||||||
|
err := json.Unmarshal(data, &parsed)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
final := make([]PublicKey, 0, len(parsed))
|
||||||
|
for _, tk := range parsed {
|
||||||
|
final = append(final, typedPublicKey(tk))
|
||||||
|
}
|
||||||
|
*ks = final
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDs generates a list of the hex encoded key IDs in the KeyList
|
||||||
|
func (ks KeyList) IDs() []string {
|
||||||
|
keyIDs := make([]string, 0, len(ks))
|
||||||
|
for _, k := range ks {
|
||||||
|
keyIDs = append(keyIDs, k.ID())
|
||||||
|
}
|
||||||
|
return keyIDs
|
||||||
|
}
|
||||||
|
|
||||||
|
func typedPublicKey(tk TUFKey) PublicKey {
|
||||||
|
switch tk.Algorithm() {
|
||||||
|
case ECDSAKey:
|
||||||
|
return &ECDSAPublicKey{TUFKey: tk}
|
||||||
|
case ECDSAx509Key:
|
||||||
|
return &ECDSAx509PublicKey{TUFKey: tk}
|
||||||
|
case RSAKey:
|
||||||
|
return &RSAPublicKey{TUFKey: tk}
|
||||||
|
case RSAx509Key:
|
||||||
|
return &RSAx509PublicKey{TUFKey: tk}
|
||||||
|
case ED25519Key:
|
||||||
|
return &ED25519PublicKey{TUFKey: tk}
|
||||||
|
}
|
||||||
|
return &UnknownPublicKey{TUFKey: tk}
|
||||||
|
}
|
||||||
|
|
||||||
|
func typedPrivateKey(tk TUFKey) (PrivateKey, error) {
|
||||||
|
private := tk.Value.Private
|
||||||
|
tk.Value.Private = nil
|
||||||
|
switch tk.Algorithm() {
|
||||||
|
case ECDSAKey:
|
||||||
|
return NewECDSAPrivateKey(
|
||||||
|
&ECDSAPublicKey{
|
||||||
|
TUFKey: tk,
|
||||||
|
},
|
||||||
|
private,
|
||||||
|
)
|
||||||
|
case ECDSAx509Key:
|
||||||
|
return NewECDSAPrivateKey(
|
||||||
|
&ECDSAx509PublicKey{
|
||||||
|
TUFKey: tk,
|
||||||
|
},
|
||||||
|
private,
|
||||||
|
)
|
||||||
|
case RSAKey:
|
||||||
|
return NewRSAPrivateKey(
|
||||||
|
&RSAPublicKey{
|
||||||
|
TUFKey: tk,
|
||||||
|
},
|
||||||
|
private,
|
||||||
|
)
|
||||||
|
case RSAx509Key:
|
||||||
|
return NewRSAPrivateKey(
|
||||||
|
&RSAx509PublicKey{
|
||||||
|
TUFKey: tk,
|
||||||
|
},
|
||||||
|
private,
|
||||||
|
)
|
||||||
|
case ED25519Key:
|
||||||
|
return NewED25519PrivateKey(
|
||||||
|
ED25519PublicKey{
|
||||||
|
TUFKey: tk,
|
||||||
|
},
|
||||||
|
private,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return &UnknownPrivateKey{
|
||||||
|
TUFKey: tk,
|
||||||
|
privateKey: privateKey{private: private},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPublicKey creates a new, correctly typed PublicKey, using the
|
||||||
|
// UnknownPublicKey catchall for unsupported ciphers
|
||||||
|
func NewPublicKey(alg string, public []byte) PublicKey {
|
||||||
|
tk := TUFKey{
|
||||||
|
Type: alg,
|
||||||
|
Value: KeyPair{
|
||||||
|
Public: public,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return typedPublicKey(tk)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPrivateKey creates a new, correctly typed PrivateKey, using the
|
||||||
|
// UnknownPrivateKey catchall for unsupported ciphers
|
||||||
|
func NewPrivateKey(pubKey PublicKey, private []byte) (PrivateKey, error) {
|
||||||
|
tk := TUFKey{
|
||||||
|
Type: pubKey.Algorithm(),
|
||||||
|
Value: KeyPair{
|
||||||
|
Public: pubKey.Public(),
|
||||||
|
Private: private, // typedPrivateKey moves this value
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return typedPrivateKey(tk)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalPublicKey is used to parse individual public keys in JSON
|
||||||
|
func UnmarshalPublicKey(data []byte) (PublicKey, error) {
|
||||||
|
var parsed TUFKey
|
||||||
|
err := json.Unmarshal(data, &parsed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return typedPublicKey(parsed), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalPrivateKey is used to parse individual private keys in JSON
|
||||||
|
func UnmarshalPrivateKey(data []byte) (PrivateKey, error) {
|
||||||
|
var parsed TUFKey
|
||||||
|
err := json.Unmarshal(data, &parsed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return typedPrivateKey(parsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TUFKey is the structure used for both public and private keys in TUF.
|
||||||
|
// Normally it would make sense to use a different structures for public and
|
||||||
|
// private keys, but that would change the key ID algorithm (since the canonical
|
||||||
|
// JSON would be different). This structure should normally be accessed through
|
||||||
|
// the PublicKey or PrivateKey interfaces.
|
||||||
|
type TUFKey struct {
|
||||||
|
id string
|
||||||
|
Type string `json:"keytype"`
|
||||||
|
Value KeyPair `json:"keyval"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Algorithm returns the algorithm of the key
|
||||||
|
func (k TUFKey) Algorithm() string {
|
||||||
|
return k.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID efficiently generates if necessary, and caches the ID of the key
|
||||||
|
func (k *TUFKey) ID() string {
|
||||||
|
if k.id == "" {
|
||||||
|
pubK := TUFKey{
|
||||||
|
Type: k.Algorithm(),
|
||||||
|
Value: KeyPair{
|
||||||
|
Public: k.Public(),
|
||||||
|
Private: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
data, err := json.MarshalCanonical(&pubK)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Error("Error generating key ID:", err)
|
||||||
|
}
|
||||||
|
digest := sha256.Sum256(data)
|
||||||
|
k.id = hex.EncodeToString(digest[:])
|
||||||
|
}
|
||||||
|
return k.id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public returns the public bytes
|
||||||
|
func (k TUFKey) Public() []byte {
|
||||||
|
return k.Value.Public
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public key types
|
||||||
|
|
||||||
|
// ECDSAPublicKey represents an ECDSA key using a raw serialization
|
||||||
|
// of the public key
|
||||||
|
type ECDSAPublicKey struct {
|
||||||
|
TUFKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// ECDSAx509PublicKey represents an ECDSA key using an x509 cert
|
||||||
|
// as the serialized format of the public key
|
||||||
|
type ECDSAx509PublicKey struct {
|
||||||
|
TUFKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// RSAPublicKey represents an RSA key using a raw serialization
|
||||||
|
// of the public key
|
||||||
|
type RSAPublicKey struct {
|
||||||
|
TUFKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// RSAx509PublicKey represents an RSA key using an x509 cert
|
||||||
|
// as the serialized format of the public key
|
||||||
|
type RSAx509PublicKey struct {
|
||||||
|
TUFKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// ED25519PublicKey represents an ED25519 key using a raw serialization
|
||||||
|
// of the public key
|
||||||
|
type ED25519PublicKey struct {
|
||||||
|
TUFKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnknownPublicKey is a catchall for key types that are not supported
|
||||||
|
type UnknownPublicKey struct {
|
||||||
|
TUFKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewECDSAPublicKey initializes a new public key with the ECDSAKey type
|
||||||
|
func NewECDSAPublicKey(public []byte) *ECDSAPublicKey {
|
||||||
|
return &ECDSAPublicKey{
|
||||||
|
TUFKey: TUFKey{
|
||||||
|
Type: ECDSAKey,
|
||||||
|
Value: KeyPair{
|
||||||
|
Public: public,
|
||||||
|
Private: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewECDSAx509PublicKey initializes a new public key with the ECDSAx509Key type
|
||||||
|
func NewECDSAx509PublicKey(public []byte) *ECDSAx509PublicKey {
|
||||||
|
return &ECDSAx509PublicKey{
|
||||||
|
TUFKey: TUFKey{
|
||||||
|
Type: ECDSAx509Key,
|
||||||
|
Value: KeyPair{
|
||||||
|
Public: public,
|
||||||
|
Private: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRSAPublicKey initializes a new public key with the RSA type
|
||||||
|
func NewRSAPublicKey(public []byte) *RSAPublicKey {
|
||||||
|
return &RSAPublicKey{
|
||||||
|
TUFKey: TUFKey{
|
||||||
|
Type: RSAKey,
|
||||||
|
Value: KeyPair{
|
||||||
|
Public: public,
|
||||||
|
Private: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRSAx509PublicKey initializes a new public key with the RSAx509Key type
|
||||||
|
func NewRSAx509PublicKey(public []byte) *RSAx509PublicKey {
|
||||||
|
return &RSAx509PublicKey{
|
||||||
|
TUFKey: TUFKey{
|
||||||
|
Type: RSAx509Key,
|
||||||
|
Value: KeyPair{
|
||||||
|
Public: public,
|
||||||
|
Private: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewED25519PublicKey initializes a new public key with the ED25519Key type
|
||||||
|
func NewED25519PublicKey(public []byte) *ED25519PublicKey {
|
||||||
|
return &ED25519PublicKey{
|
||||||
|
TUFKey: TUFKey{
|
||||||
|
Type: ED25519Key,
|
||||||
|
Value: KeyPair{
|
||||||
|
Public: public,
|
||||||
|
Private: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private key types
|
||||||
|
type privateKey struct {
|
||||||
|
private []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type signer struct {
|
||||||
|
signer crypto.Signer
|
||||||
|
}
|
||||||
|
|
||||||
|
// ECDSAPrivateKey represents a private ECDSA key
|
||||||
|
type ECDSAPrivateKey struct {
|
||||||
|
PublicKey
|
||||||
|
privateKey
|
||||||
|
signer
|
||||||
|
}
|
||||||
|
|
||||||
|
// RSAPrivateKey represents a private RSA key
|
||||||
|
type RSAPrivateKey struct {
|
||||||
|
PublicKey
|
||||||
|
privateKey
|
||||||
|
signer
|
||||||
|
}
|
||||||
|
|
||||||
|
// ED25519PrivateKey represents a private ED25519 key
|
||||||
|
type ED25519PrivateKey struct {
|
||||||
|
ED25519PublicKey
|
||||||
|
privateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnknownPrivateKey is a catchall for unsupported key types
|
||||||
|
type UnknownPrivateKey struct {
|
||||||
|
TUFKey
|
||||||
|
privateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewECDSAPrivateKey initializes a new ECDSA private key
|
||||||
|
func NewECDSAPrivateKey(public PublicKey, private []byte) (*ECDSAPrivateKey, error) {
|
||||||
|
switch public.(type) {
|
||||||
|
case *ECDSAPublicKey, *ECDSAx509PublicKey:
|
||||||
|
default:
|
||||||
|
return nil, errors.New("invalid public key type provided to NewECDSAPrivateKey")
|
||||||
|
}
|
||||||
|
ecdsaPrivKey, err := x509.ParseECPrivateKey(private)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ECDSAPrivateKey{
|
||||||
|
PublicKey: public,
|
||||||
|
privateKey: privateKey{private: private},
|
||||||
|
signer: signer{signer: ecdsaPrivKey},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRSAPrivateKey initialized a new RSA private key
|
||||||
|
func NewRSAPrivateKey(public PublicKey, private []byte) (*RSAPrivateKey, error) {
|
||||||
|
switch public.(type) {
|
||||||
|
case *RSAPublicKey, *RSAx509PublicKey:
|
||||||
|
default:
|
||||||
|
return nil, errors.New("invalid public key type provided to NewRSAPrivateKey")
|
||||||
|
}
|
||||||
|
rsaPrivKey, err := x509.ParsePKCS1PrivateKey(private)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &RSAPrivateKey{
|
||||||
|
PublicKey: public,
|
||||||
|
privateKey: privateKey{private: private},
|
||||||
|
signer: signer{signer: rsaPrivKey},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewED25519PrivateKey initialized a new ED25519 private key
|
||||||
|
func NewED25519PrivateKey(public ED25519PublicKey, private []byte) (*ED25519PrivateKey, error) {
|
||||||
|
return &ED25519PrivateKey{
|
||||||
|
ED25519PublicKey: public,
|
||||||
|
privateKey: privateKey{private: private},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private return the serialized private bytes of the key
|
||||||
|
func (k privateKey) Private() []byte {
|
||||||
|
return k.private
|
||||||
|
}
|
||||||
|
|
||||||
|
// CryptoSigner returns the underlying crypto.Signer for use cases where we need the default
|
||||||
|
// signature or public key functionality (like when we generate certificates)
|
||||||
|
func (s signer) CryptoSigner() crypto.Signer {
|
||||||
|
return s.signer
|
||||||
|
}
|
||||||
|
|
||||||
|
// CryptoSigner returns the ED25519PrivateKey which already implements crypto.Signer
|
||||||
|
func (k ED25519PrivateKey) CryptoSigner() crypto.Signer {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CryptoSigner returns the UnknownPrivateKey which already implements crypto.Signer
|
||||||
|
func (k UnknownPrivateKey) CryptoSigner() crypto.Signer {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ecdsaSig struct {
|
||||||
|
R *big.Int
|
||||||
|
S *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign creates an ecdsa signature
|
||||||
|
func (k ECDSAPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||||
|
ecdsaPrivKey, ok := k.CryptoSigner().(*ecdsa.PrivateKey)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("signer was based on the wrong key type")
|
||||||
|
}
|
||||||
|
hashed := sha256.Sum256(msg)
|
||||||
|
sigASN1, err := ecdsaPrivKey.Sign(rand, hashed[:], opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sig := ecdsaSig{}
|
||||||
|
_, err = asn1.Unmarshal(sigASN1, &sig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rBytes, sBytes := sig.R.Bytes(), sig.S.Bytes()
|
||||||
|
octetLength := (ecdsaPrivKey.Params().BitSize + 7) >> 3
|
||||||
|
|
||||||
|
// MUST include leading zeros in the output
|
||||||
|
rBuf := make([]byte, octetLength-len(rBytes), octetLength)
|
||||||
|
sBuf := make([]byte, octetLength-len(sBytes), octetLength)
|
||||||
|
|
||||||
|
rBuf = append(rBuf, rBytes...)
|
||||||
|
sBuf = append(sBuf, sBytes...)
|
||||||
|
return append(rBuf, sBuf...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign creates an rsa signature
|
||||||
|
func (k RSAPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||||
|
hashed := sha256.Sum256(msg)
|
||||||
|
if opts == nil {
|
||||||
|
opts = &rsa.PSSOptions{
|
||||||
|
SaltLength: rsa.PSSSaltLengthEqualsHash,
|
||||||
|
Hash: crypto.SHA256,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return k.CryptoSigner().Sign(rand, hashed[:], opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign creates an ed25519 signature
|
||||||
|
func (k ED25519PrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||||
|
priv := [ed25519.PrivateKeySize]byte{}
|
||||||
|
copy(priv[:], k.private[ed25519.PublicKeySize:])
|
||||||
|
return ed25519.Sign(&priv, msg)[:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign on an UnknownPrivateKey raises an error because the client does not
|
||||||
|
// know how to sign with this key type.
|
||||||
|
func (k UnknownPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||||
|
return nil, errors.New("unknown key type, cannot sign")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignatureAlgorithm returns the SigAlgorithm for a ECDSAPrivateKey
|
||||||
|
func (k ECDSAPrivateKey) SignatureAlgorithm() SigAlgorithm {
|
||||||
|
return ECDSASignature
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignatureAlgorithm returns the SigAlgorithm for a RSAPrivateKey
|
||||||
|
func (k RSAPrivateKey) SignatureAlgorithm() SigAlgorithm {
|
||||||
|
return RSAPSSSignature
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignatureAlgorithm returns the SigAlgorithm for a ED25519PrivateKey
|
||||||
|
func (k ED25519PrivateKey) SignatureAlgorithm() SigAlgorithm {
|
||||||
|
return EDDSASignature
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignatureAlgorithm returns the SigAlgorithm for an UnknownPrivateKey
|
||||||
|
func (k UnknownPrivateKey) SignatureAlgorithm() SigAlgorithm {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublicKeyFromPrivate returns a new TUFKey based on a private key, with
|
||||||
|
// the private key bytes guaranteed to be nil.
|
||||||
|
func PublicKeyFromPrivate(pk PrivateKey) PublicKey {
|
||||||
|
return typedPublicKey(TUFKey{
|
||||||
|
Type: pk.Algorithm(),
|
||||||
|
Value: KeyPair{
|
||||||
|
Public: pk.Public(),
|
||||||
|
Private: nil,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
339
src/vendor/github.com/docker/notary/tuf/data/roles.go
generated
vendored
Normal file
339
src/vendor/github.com/docker/notary/tuf/data/roles.go
generated
vendored
Normal file
@ -0,0 +1,339 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Canonical base role names
|
||||||
|
var (
|
||||||
|
CanonicalRootRole RoleName = "root"
|
||||||
|
CanonicalTargetsRole RoleName = "targets"
|
||||||
|
CanonicalSnapshotRole RoleName = "snapshot"
|
||||||
|
CanonicalTimestampRole RoleName = "timestamp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BaseRoles is an easy to iterate list of the top level
|
||||||
|
// roles.
|
||||||
|
var BaseRoles = []RoleName{
|
||||||
|
CanonicalRootRole,
|
||||||
|
CanonicalTargetsRole,
|
||||||
|
CanonicalSnapshotRole,
|
||||||
|
CanonicalTimestampRole,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regex for validating delegation names
|
||||||
|
var delegationRegexp = regexp.MustCompile("^[-a-z0-9_/]+$")
|
||||||
|
|
||||||
|
// ErrNoSuchRole indicates the roles doesn't exist
|
||||||
|
type ErrNoSuchRole struct {
|
||||||
|
Role RoleName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrNoSuchRole) Error() string {
|
||||||
|
return fmt.Sprintf("role does not exist: %s", e.Role)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrInvalidRole represents an error regarding a role. Typically
|
||||||
|
// something like a role for which sone of the public keys were
|
||||||
|
// not found in the TUF repo.
|
||||||
|
type ErrInvalidRole struct {
|
||||||
|
Role RoleName
|
||||||
|
Reason string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrInvalidRole) Error() string {
|
||||||
|
if e.Reason != "" {
|
||||||
|
return fmt.Sprintf("tuf: invalid role %s. %s", e.Role, e.Reason)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("tuf: invalid role %s.", e.Role)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidRole only determines the name is semantically
|
||||||
|
// correct. For target delegated roles, it does NOT check
|
||||||
|
// the the appropriate parent roles exist.
|
||||||
|
func ValidRole(name RoleName) bool {
|
||||||
|
if IsDelegation(name) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range BaseRoles {
|
||||||
|
if name == v {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDelegation checks if the role is a delegation or a root role
|
||||||
|
func IsDelegation(role RoleName) bool {
|
||||||
|
strRole := role.String()
|
||||||
|
targetsBase := CanonicalTargetsRole + "/"
|
||||||
|
|
||||||
|
whitelistedChars := delegationRegexp.MatchString(strRole)
|
||||||
|
|
||||||
|
// Limit size of full role string to 255 chars for db column size limit
|
||||||
|
correctLength := len(role) < 256
|
||||||
|
|
||||||
|
// Removes ., .., extra slashes, and trailing slash
|
||||||
|
isClean := path.Clean(strRole) == strRole
|
||||||
|
return strings.HasPrefix(strRole, targetsBase.String()) &&
|
||||||
|
whitelistedChars &&
|
||||||
|
correctLength &&
|
||||||
|
isClean
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsBaseRole checks if the role is a base role
|
||||||
|
func IsBaseRole(role RoleName) bool {
|
||||||
|
for _, baseRole := range BaseRoles {
|
||||||
|
if role == baseRole {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsWildDelegation determines if a role represents a valid wildcard delegation
|
||||||
|
// path, i.e. targets/*, targets/foo/*.
|
||||||
|
// The wildcard may only appear as the final part of the delegation and must
|
||||||
|
// be a whole segment, i.e. targets/foo* is not a valid wildcard delegation.
|
||||||
|
func IsWildDelegation(role RoleName) bool {
|
||||||
|
if path.Clean(role.String()) != role.String() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
base := role.Parent()
|
||||||
|
if !(IsDelegation(base) || base == CanonicalTargetsRole) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return role[len(role)-2:] == "/*"
|
||||||
|
}
|
||||||
|
|
||||||
|
// BaseRole is an internal representation of a root/targets/snapshot/timestamp role, with its public keys included
|
||||||
|
type BaseRole struct {
|
||||||
|
Keys map[string]PublicKey
|
||||||
|
Name RoleName
|
||||||
|
Threshold int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBaseRole creates a new BaseRole object with the provided parameters
|
||||||
|
func NewBaseRole(name RoleName, threshold int, keys ...PublicKey) BaseRole {
|
||||||
|
r := BaseRole{
|
||||||
|
Name: name,
|
||||||
|
Threshold: threshold,
|
||||||
|
Keys: make(map[string]PublicKey),
|
||||||
|
}
|
||||||
|
for _, k := range keys {
|
||||||
|
r.Keys[k.ID()] = k
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListKeys retrieves the public keys valid for this role
|
||||||
|
func (b BaseRole) ListKeys() KeyList {
|
||||||
|
return listKeys(b.Keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListKeyIDs retrieves the list of key IDs valid for this role
|
||||||
|
func (b BaseRole) ListKeyIDs() []string {
|
||||||
|
return listKeyIDs(b.Keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals returns whether this BaseRole equals another BaseRole
|
||||||
|
func (b BaseRole) Equals(o BaseRole) bool {
|
||||||
|
if b.Threshold != o.Threshold || b.Name != o.Name || len(b.Keys) != len(o.Keys) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for keyID, key := range b.Keys {
|
||||||
|
oKey, ok := o.Keys[keyID]
|
||||||
|
if !ok || key.ID() != oKey.ID() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelegationRole is an internal representation of a delegation role, with its public keys included
|
||||||
|
type DelegationRole struct {
|
||||||
|
BaseRole
|
||||||
|
Paths []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func listKeys(keyMap map[string]PublicKey) KeyList {
|
||||||
|
keys := KeyList{}
|
||||||
|
for _, key := range keyMap {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func listKeyIDs(keyMap map[string]PublicKey) []string {
|
||||||
|
keyIDs := []string{}
|
||||||
|
for id := range keyMap {
|
||||||
|
keyIDs = append(keyIDs, id)
|
||||||
|
}
|
||||||
|
return keyIDs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restrict restricts the paths and path hash prefixes for the passed in delegation role,
|
||||||
|
// returning a copy of the role with validated paths as if it was a direct child
|
||||||
|
func (d DelegationRole) Restrict(child DelegationRole) (DelegationRole, error) {
|
||||||
|
if !d.IsParentOf(child) {
|
||||||
|
return DelegationRole{}, fmt.Errorf("%s is not a parent of %s", d.Name, child.Name)
|
||||||
|
}
|
||||||
|
return DelegationRole{
|
||||||
|
BaseRole: BaseRole{
|
||||||
|
Keys: child.Keys,
|
||||||
|
Name: child.Name,
|
||||||
|
Threshold: child.Threshold,
|
||||||
|
},
|
||||||
|
Paths: RestrictDelegationPathPrefixes(d.Paths, child.Paths),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsParentOf returns whether the passed in delegation role is the direct child of this role,
|
||||||
|
// determined by delegation name.
|
||||||
|
// Ex: targets/a is a direct parent of targets/a/b, but targets/a is not a direct parent of targets/a/b/c
|
||||||
|
func (d DelegationRole) IsParentOf(child DelegationRole) bool {
|
||||||
|
return path.Dir(child.Name.String()) == d.Name.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckPaths checks if a given path is valid for the role
|
||||||
|
func (d DelegationRole) CheckPaths(path string) bool {
|
||||||
|
return checkPaths(path, d.Paths)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPaths(path string, permitted []string) bool {
|
||||||
|
for _, p := range permitted {
|
||||||
|
if strings.HasPrefix(path, p) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestrictDelegationPathPrefixes returns the list of valid delegationPaths that are prefixed by parentPaths
|
||||||
|
func RestrictDelegationPathPrefixes(parentPaths, delegationPaths []string) []string {
|
||||||
|
validPaths := []string{}
|
||||||
|
if len(delegationPaths) == 0 {
|
||||||
|
return validPaths
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate each individual delegation path
|
||||||
|
for _, delgPath := range delegationPaths {
|
||||||
|
isPrefixed := false
|
||||||
|
for _, parentPath := range parentPaths {
|
||||||
|
if strings.HasPrefix(delgPath, parentPath) {
|
||||||
|
isPrefixed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the delegation path did not match prefix against any parent path, it is not valid
|
||||||
|
if isPrefixed {
|
||||||
|
validPaths = append(validPaths, delgPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return validPaths
|
||||||
|
}
|
||||||
|
|
||||||
|
// RootRole is a cut down role as it appears in the root.json
|
||||||
|
// Eventually should only be used for immediately before and after serialization/deserialization
|
||||||
|
type RootRole struct {
|
||||||
|
KeyIDs []string `json:"keyids"`
|
||||||
|
Threshold int `json:"threshold"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Role is a more verbose role as they appear in targets delegations
|
||||||
|
// Eventually should only be used for immediately before and after serialization/deserialization
|
||||||
|
type Role struct {
|
||||||
|
RootRole
|
||||||
|
Name RoleName `json:"name"`
|
||||||
|
Paths []string `json:"paths,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRole creates a new Role object from the given parameters
|
||||||
|
func NewRole(name RoleName, threshold int, keyIDs, paths []string) (*Role, error) {
|
||||||
|
if IsDelegation(name) {
|
||||||
|
if len(paths) == 0 {
|
||||||
|
logrus.Debugf("role %s with no Paths will never be able to publish content until one or more are added", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if threshold < 1 {
|
||||||
|
return nil, ErrInvalidRole{Role: name}
|
||||||
|
}
|
||||||
|
if !ValidRole(name) {
|
||||||
|
return nil, ErrInvalidRole{Role: name}
|
||||||
|
}
|
||||||
|
return &Role{
|
||||||
|
RootRole: RootRole{
|
||||||
|
KeyIDs: keyIDs,
|
||||||
|
Threshold: threshold,
|
||||||
|
},
|
||||||
|
Name: name,
|
||||||
|
Paths: paths,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckPaths checks if a given path is valid for the role
|
||||||
|
func (r Role) CheckPaths(path string) bool {
|
||||||
|
return checkPaths(path, r.Paths)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddKeys merges the ids into the current list of role key ids
|
||||||
|
func (r *Role) AddKeys(ids []string) {
|
||||||
|
r.KeyIDs = mergeStrSlices(r.KeyIDs, ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPaths merges the paths into the current list of role paths
|
||||||
|
func (r *Role) AddPaths(paths []string) error {
|
||||||
|
if len(paths) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
r.Paths = mergeStrSlices(r.Paths, paths)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveKeys removes the ids from the current list of key ids
|
||||||
|
func (r *Role) RemoveKeys(ids []string) {
|
||||||
|
r.KeyIDs = subtractStrSlices(r.KeyIDs, ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemovePaths removes the paths from the current list of role paths
|
||||||
|
func (r *Role) RemovePaths(paths []string) {
|
||||||
|
r.Paths = subtractStrSlices(r.Paths, paths)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeStrSlices(orig, new []string) []string {
|
||||||
|
have := make(map[string]bool)
|
||||||
|
for _, e := range orig {
|
||||||
|
have[e] = true
|
||||||
|
}
|
||||||
|
merged := make([]string, len(orig), len(orig)+len(new))
|
||||||
|
copy(merged, orig)
|
||||||
|
for _, e := range new {
|
||||||
|
if !have[e] {
|
||||||
|
merged = append(merged, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return merged
|
||||||
|
}
|
||||||
|
|
||||||
|
func subtractStrSlices(orig, remove []string) []string {
|
||||||
|
kill := make(map[string]bool)
|
||||||
|
for _, e := range remove {
|
||||||
|
kill[e] = true
|
||||||
|
}
|
||||||
|
var keep []string
|
||||||
|
for _, e := range orig {
|
||||||
|
if !kill[e] {
|
||||||
|
keep = append(keep, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keep
|
||||||
|
}
|
171
src/vendor/github.com/docker/notary/tuf/data/root.go
generated
vendored
Normal file
171
src/vendor/github.com/docker/notary/tuf/data/root.go
generated
vendored
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/go/canonical/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SignedRoot is a fully unpacked root.json
|
||||||
|
type SignedRoot struct {
|
||||||
|
Signatures []Signature
|
||||||
|
Signed Root
|
||||||
|
Dirty bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root is the Signed component of a root.json
|
||||||
|
type Root struct {
|
||||||
|
SignedCommon
|
||||||
|
Keys Keys `json:"keys"`
|
||||||
|
Roles map[RoleName]*RootRole `json:"roles"`
|
||||||
|
ConsistentSnapshot bool `json:"consistent_snapshot"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValidRootStructure returns an error, or nil, depending on whether the content of the struct
|
||||||
|
// is valid for root metadata. This does not check signatures or expiry, just that
|
||||||
|
// the metadata content is valid.
|
||||||
|
func isValidRootStructure(r Root) error {
|
||||||
|
expectedType := TUFTypes[CanonicalRootRole]
|
||||||
|
if r.Type != expectedType {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: CanonicalRootRole, msg: fmt.Sprintf("expected type %s, not %s", expectedType, r.Type)}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Version < 1 {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: CanonicalRootRole, msg: "version cannot be less than 1"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// all the base roles MUST appear in the root.json - other roles are allowed,
|
||||||
|
// but other than the mirror role (not currently supported) are out of spec
|
||||||
|
for _, roleName := range BaseRoles {
|
||||||
|
roleObj, ok := r.Roles[roleName]
|
||||||
|
if !ok || roleObj == nil {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: CanonicalRootRole, msg: fmt.Sprintf("missing %s role specification", roleName)}
|
||||||
|
}
|
||||||
|
if err := isValidRootRoleStructure(CanonicalRootRole, roleName, *roleObj, r.Keys); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidRootRoleStructure(metaContainingRole, rootRoleName RoleName, r RootRole, validKeys Keys) error {
|
||||||
|
if r.Threshold < 1 {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: metaContainingRole,
|
||||||
|
msg: fmt.Sprintf("invalid threshold specified for %s: %v ", rootRoleName, r.Threshold),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, keyID := range r.KeyIDs {
|
||||||
|
if _, ok := validKeys[keyID]; !ok {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: metaContainingRole,
|
||||||
|
msg: fmt.Sprintf("key ID %s specified in %s without corresponding key", keyID, rootRoleName),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRoot initializes a new SignedRoot with a set of keys, roles, and the consistent flag
|
||||||
|
func NewRoot(keys map[string]PublicKey, roles map[RoleName]*RootRole, consistent bool) (*SignedRoot, error) {
|
||||||
|
signedRoot := &SignedRoot{
|
||||||
|
Signatures: make([]Signature, 0),
|
||||||
|
Signed: Root{
|
||||||
|
SignedCommon: SignedCommon{
|
||||||
|
Type: TUFTypes[CanonicalRootRole],
|
||||||
|
Version: 0,
|
||||||
|
Expires: DefaultExpires(CanonicalRootRole),
|
||||||
|
},
|
||||||
|
Keys: keys,
|
||||||
|
Roles: roles,
|
||||||
|
ConsistentSnapshot: consistent,
|
||||||
|
},
|
||||||
|
Dirty: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
return signedRoot, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildBaseRole returns a copy of a BaseRole using the information in this SignedRoot for the specified role name.
|
||||||
|
// Will error for invalid role name or key metadata within this SignedRoot
|
||||||
|
func (r SignedRoot) BuildBaseRole(roleName RoleName) (BaseRole, error) {
|
||||||
|
roleData, ok := r.Signed.Roles[roleName]
|
||||||
|
if !ok {
|
||||||
|
return BaseRole{}, ErrInvalidRole{Role: roleName, Reason: "role not found in root file"}
|
||||||
|
}
|
||||||
|
// Get all public keys for the base role from TUF metadata
|
||||||
|
keyIDs := roleData.KeyIDs
|
||||||
|
pubKeys := make(map[string]PublicKey)
|
||||||
|
for _, keyID := range keyIDs {
|
||||||
|
pubKey, ok := r.Signed.Keys[keyID]
|
||||||
|
if !ok {
|
||||||
|
return BaseRole{}, ErrInvalidRole{
|
||||||
|
Role: roleName,
|
||||||
|
Reason: fmt.Sprintf("key with ID %s was not found in root metadata", keyID),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pubKeys[keyID] = pubKey
|
||||||
|
}
|
||||||
|
|
||||||
|
return BaseRole{
|
||||||
|
Name: roleName,
|
||||||
|
Keys: pubKeys,
|
||||||
|
Threshold: roleData.Threshold,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToSigned partially serializes a SignedRoot for further signing
|
||||||
|
func (r SignedRoot) ToSigned() (*Signed, error) {
|
||||||
|
s, err := defaultSerializer.MarshalCanonical(r.Signed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// cast into a json.RawMessage
|
||||||
|
signed := json.RawMessage{}
|
||||||
|
err = signed.UnmarshalJSON(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sigs := make([]Signature, len(r.Signatures))
|
||||||
|
copy(sigs, r.Signatures)
|
||||||
|
return &Signed{
|
||||||
|
Signatures: sigs,
|
||||||
|
Signed: &signed,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON returns the serialized form of SignedRoot as bytes
|
||||||
|
func (r SignedRoot) MarshalJSON() ([]byte, error) {
|
||||||
|
signed, err := r.ToSigned()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return defaultSerializer.Marshal(signed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RootFromSigned fully unpacks a Signed object into a SignedRoot and ensures
|
||||||
|
// that it is a valid SignedRoot
|
||||||
|
func RootFromSigned(s *Signed) (*SignedRoot, error) {
|
||||||
|
r := Root{}
|
||||||
|
if s.Signed == nil {
|
||||||
|
return nil, ErrInvalidMetadata{
|
||||||
|
role: CanonicalRootRole,
|
||||||
|
msg: "root file contained an empty payload",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := defaultSerializer.Unmarshal(*s.Signed, &r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := isValidRootStructure(r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sigs := make([]Signature, len(s.Signatures))
|
||||||
|
copy(sigs, s.Signatures)
|
||||||
|
return &SignedRoot{
|
||||||
|
Signatures: sigs,
|
||||||
|
Signed: r,
|
||||||
|
}, nil
|
||||||
|
}
|
36
src/vendor/github.com/docker/notary/tuf/data/serializer.go
generated
vendored
Normal file
36
src/vendor/github.com/docker/notary/tuf/data/serializer.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import "github.com/docker/go/canonical/json"
|
||||||
|
|
||||||
|
// Serializer is an interface that can marshal and unmarshal TUF data. This
|
||||||
|
// is expected to be a canonical JSON marshaller
|
||||||
|
type serializer interface {
|
||||||
|
MarshalCanonical(from interface{}) ([]byte, error)
|
||||||
|
Marshal(from interface{}) ([]byte, error)
|
||||||
|
Unmarshal(from []byte, to interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanonicalJSON marshals to and from canonical JSON
|
||||||
|
type canonicalJSON struct{}
|
||||||
|
|
||||||
|
// MarshalCanonical returns the canonical JSON form of a thing
|
||||||
|
func (c canonicalJSON) MarshalCanonical(from interface{}) ([]byte, error) {
|
||||||
|
return json.MarshalCanonical(from)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal returns the regular non-canonical JSON form of a thing
|
||||||
|
func (c canonicalJSON) Marshal(from interface{}) ([]byte, error) {
|
||||||
|
return json.Marshal(from)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal unmarshals some JSON bytes
|
||||||
|
func (c canonicalJSON) Unmarshal(from []byte, to interface{}) error {
|
||||||
|
return json.Unmarshal(from, to)
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultSerializer is a canonical JSON serializer
|
||||||
|
var defaultSerializer serializer = canonicalJSON{}
|
||||||
|
|
||||||
|
func setDefaultSerializer(s serializer) {
|
||||||
|
defaultSerializer = s
|
||||||
|
}
|
169
src/vendor/github.com/docker/notary/tuf/data/snapshot.go
generated
vendored
Normal file
169
src/vendor/github.com/docker/notary/tuf/data/snapshot.go
generated
vendored
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/go/canonical/json"
|
||||||
|
"github.com/docker/notary"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SignedSnapshot is a fully unpacked snapshot.json
|
||||||
|
type SignedSnapshot struct {
|
||||||
|
Signatures []Signature
|
||||||
|
Signed Snapshot
|
||||||
|
Dirty bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snapshot is the Signed component of a snapshot.json
|
||||||
|
type Snapshot struct {
|
||||||
|
SignedCommon
|
||||||
|
Meta Files `json:"meta"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValidSnapshotStructure returns an error, or nil, depending on whether the content of the
|
||||||
|
// struct is valid for snapshot metadata. This does not check signatures or expiry, just that
|
||||||
|
// the metadata content is valid.
|
||||||
|
func IsValidSnapshotStructure(s Snapshot) error {
|
||||||
|
expectedType := TUFTypes[CanonicalSnapshotRole]
|
||||||
|
if s.Type != expectedType {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: CanonicalSnapshotRole, msg: fmt.Sprintf("expected type %s, not %s", expectedType, s.Type)}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Version < 1 {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: CanonicalSnapshotRole, msg: "version cannot be less than one"}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range []RoleName{CanonicalRootRole, CanonicalTargetsRole} {
|
||||||
|
// Meta is a map of FileMeta, so if the role isn't in the map it returns
|
||||||
|
// an empty FileMeta, which has an empty map, and you can check on keys
|
||||||
|
// from an empty map.
|
||||||
|
//
|
||||||
|
// For now sha256 is required and sha512 is not.
|
||||||
|
if _, ok := s.Meta[file.String()].Hashes[notary.SHA256]; !ok {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: CanonicalSnapshotRole,
|
||||||
|
msg: fmt.Sprintf("missing %s sha256 checksum information", file.String()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := CheckValidHashStructures(s.Meta[file.String()].Hashes); err != nil {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: CanonicalSnapshotRole,
|
||||||
|
msg: fmt.Sprintf("invalid %s checksum information, %v", file.String(), err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSnapshot initilizes a SignedSnapshot with a given top level root
|
||||||
|
// and targets objects
|
||||||
|
func NewSnapshot(root *Signed, targets *Signed) (*SignedSnapshot, error) {
|
||||||
|
logrus.Debug("generating new snapshot...")
|
||||||
|
targetsJSON, err := json.Marshal(targets)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debug("Error Marshalling Targets")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rootJSON, err := json.Marshal(root)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debug("Error Marshalling Root")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rootMeta, err := NewFileMeta(bytes.NewReader(rootJSON), NotaryDefaultHashes...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
targetsMeta, err := NewFileMeta(bytes.NewReader(targetsJSON), NotaryDefaultHashes...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &SignedSnapshot{
|
||||||
|
Signatures: make([]Signature, 0),
|
||||||
|
Signed: Snapshot{
|
||||||
|
SignedCommon: SignedCommon{
|
||||||
|
Type: TUFTypes[CanonicalSnapshotRole],
|
||||||
|
Version: 0,
|
||||||
|
Expires: DefaultExpires(CanonicalSnapshotRole),
|
||||||
|
},
|
||||||
|
Meta: Files{
|
||||||
|
CanonicalRootRole.String(): rootMeta,
|
||||||
|
CanonicalTargetsRole.String(): targetsMeta,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToSigned partially serializes a SignedSnapshot for further signing
|
||||||
|
func (sp *SignedSnapshot) ToSigned() (*Signed, error) {
|
||||||
|
s, err := defaultSerializer.MarshalCanonical(sp.Signed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
signed := json.RawMessage{}
|
||||||
|
err = signed.UnmarshalJSON(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sigs := make([]Signature, len(sp.Signatures))
|
||||||
|
copy(sigs, sp.Signatures)
|
||||||
|
return &Signed{
|
||||||
|
Signatures: sigs,
|
||||||
|
Signed: &signed,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddMeta updates a role in the snapshot with new meta
|
||||||
|
func (sp *SignedSnapshot) AddMeta(role RoleName, meta FileMeta) {
|
||||||
|
sp.Signed.Meta[role.String()] = meta
|
||||||
|
sp.Dirty = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMeta gets the metadata for a particular role, returning an error if it's
|
||||||
|
// not found
|
||||||
|
func (sp *SignedSnapshot) GetMeta(role RoleName) (*FileMeta, error) {
|
||||||
|
if meta, ok := sp.Signed.Meta[role.String()]; ok {
|
||||||
|
if _, ok := meta.Hashes["sha256"]; ok {
|
||||||
|
return &meta, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, ErrMissingMeta{Role: role.String()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteMeta removes a role from the snapshot. If the role doesn't
|
||||||
|
// exist in the snapshot, it's a noop.
|
||||||
|
func (sp *SignedSnapshot) DeleteMeta(role RoleName) {
|
||||||
|
if _, ok := sp.Signed.Meta[role.String()]; ok {
|
||||||
|
delete(sp.Signed.Meta, role.String())
|
||||||
|
sp.Dirty = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON returns the serialized form of SignedSnapshot as bytes
|
||||||
|
func (sp *SignedSnapshot) MarshalJSON() ([]byte, error) {
|
||||||
|
signed, err := sp.ToSigned()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return defaultSerializer.Marshal(signed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SnapshotFromSigned fully unpacks a Signed object into a SignedSnapshot
|
||||||
|
func SnapshotFromSigned(s *Signed) (*SignedSnapshot, error) {
|
||||||
|
sp := Snapshot{}
|
||||||
|
if err := defaultSerializer.Unmarshal(*s.Signed, &sp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := IsValidSnapshotStructure(sp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sigs := make([]Signature, len(s.Signatures))
|
||||||
|
copy(sigs, s.Signatures)
|
||||||
|
return &SignedSnapshot{
|
||||||
|
Signatures: sigs,
|
||||||
|
Signed: sp,
|
||||||
|
}, nil
|
||||||
|
}
|
201
src/vendor/github.com/docker/notary/tuf/data/targets.go
generated
vendored
Normal file
201
src/vendor/github.com/docker/notary/tuf/data/targets.go
generated
vendored
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/docker/go/canonical/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SignedTargets is a fully unpacked targets.json, or target delegation
|
||||||
|
// json file
|
||||||
|
type SignedTargets struct {
|
||||||
|
Signatures []Signature
|
||||||
|
Signed Targets
|
||||||
|
Dirty bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Targets is the Signed components of a targets.json or delegation json file
|
||||||
|
type Targets struct {
|
||||||
|
SignedCommon
|
||||||
|
Targets Files `json:"targets"`
|
||||||
|
Delegations Delegations `json:"delegations,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValidTargetsStructure returns an error, or nil, depending on whether the content of the struct
|
||||||
|
// is valid for targets metadata. This does not check signatures or expiry, just that
|
||||||
|
// the metadata content is valid.
|
||||||
|
func isValidTargetsStructure(t Targets, roleName RoleName) error {
|
||||||
|
if roleName != CanonicalTargetsRole && !IsDelegation(roleName) {
|
||||||
|
return ErrInvalidRole{Role: roleName}
|
||||||
|
}
|
||||||
|
|
||||||
|
// even if it's a delegated role, the metadata type is "Targets"
|
||||||
|
expectedType := TUFTypes[CanonicalTargetsRole]
|
||||||
|
if t.Type != expectedType {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: roleName, msg: fmt.Sprintf("expected type %s, not %s", expectedType, t.Type)}
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Version < 1 {
|
||||||
|
return ErrInvalidMetadata{role: roleName, msg: "version cannot be less than one"}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, roleObj := range t.Delegations.Roles {
|
||||||
|
if !IsDelegation(roleObj.Name) || path.Dir(roleObj.Name.String()) != roleName.String() {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: roleName, msg: fmt.Sprintf("delegation role %s invalid", roleObj.Name)}
|
||||||
|
}
|
||||||
|
if err := isValidRootRoleStructure(roleName, roleObj.Name, roleObj.RootRole, t.Delegations.Keys); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTargets intiializes a new empty SignedTargets object
|
||||||
|
func NewTargets() *SignedTargets {
|
||||||
|
return &SignedTargets{
|
||||||
|
Signatures: make([]Signature, 0),
|
||||||
|
Signed: Targets{
|
||||||
|
SignedCommon: SignedCommon{
|
||||||
|
Type: TUFTypes["targets"],
|
||||||
|
Version: 0,
|
||||||
|
Expires: DefaultExpires("targets"),
|
||||||
|
},
|
||||||
|
Targets: make(Files),
|
||||||
|
Delegations: *NewDelegations(),
|
||||||
|
},
|
||||||
|
Dirty: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMeta attempts to find the targets entry for the path. It
|
||||||
|
// will return nil in the case of the target not being found.
|
||||||
|
func (t SignedTargets) GetMeta(path string) *FileMeta {
|
||||||
|
for p, meta := range t.Signed.Targets {
|
||||||
|
if p == path {
|
||||||
|
return &meta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValidDelegations filters the delegation roles specified in the signed targets, and
|
||||||
|
// only returns roles that are direct children and restricts their paths
|
||||||
|
func (t SignedTargets) GetValidDelegations(parent DelegationRole) []DelegationRole {
|
||||||
|
roles := t.buildDelegationRoles()
|
||||||
|
result := []DelegationRole{}
|
||||||
|
for _, r := range roles {
|
||||||
|
validRole, err := parent.Restrict(r)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result = append(result, validRole)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildDelegationRole returns a copy of a DelegationRole using the information in this SignedTargets for the specified role name.
|
||||||
|
// Will error for invalid role name or key metadata within this SignedTargets. Path data is not validated.
|
||||||
|
func (t *SignedTargets) BuildDelegationRole(roleName RoleName) (DelegationRole, error) {
|
||||||
|
for _, role := range t.Signed.Delegations.Roles {
|
||||||
|
if role.Name == roleName {
|
||||||
|
pubKeys := make(map[string]PublicKey)
|
||||||
|
for _, keyID := range role.KeyIDs {
|
||||||
|
pubKey, ok := t.Signed.Delegations.Keys[keyID]
|
||||||
|
if !ok {
|
||||||
|
// Couldn't retrieve all keys, so stop walking and return invalid role
|
||||||
|
return DelegationRole{}, ErrInvalidRole{
|
||||||
|
Role: roleName,
|
||||||
|
Reason: "role lists unknown key " + keyID + " as a signing key",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pubKeys[keyID] = pubKey
|
||||||
|
}
|
||||||
|
return DelegationRole{
|
||||||
|
BaseRole: BaseRole{
|
||||||
|
Name: role.Name,
|
||||||
|
Keys: pubKeys,
|
||||||
|
Threshold: role.Threshold,
|
||||||
|
},
|
||||||
|
Paths: role.Paths,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return DelegationRole{}, ErrNoSuchRole{Role: roleName}
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function to create DelegationRole structures from all delegations in a SignedTargets,
|
||||||
|
// these delegations are read directly from the SignedTargets and not modified or validated
|
||||||
|
func (t SignedTargets) buildDelegationRoles() []DelegationRole {
|
||||||
|
var roles []DelegationRole
|
||||||
|
for _, roleData := range t.Signed.Delegations.Roles {
|
||||||
|
delgRole, err := t.BuildDelegationRole(roleData.Name)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
roles = append(roles, delgRole)
|
||||||
|
}
|
||||||
|
return roles
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTarget adds or updates the meta for the given path
|
||||||
|
func (t *SignedTargets) AddTarget(path string, meta FileMeta) {
|
||||||
|
t.Signed.Targets[path] = meta
|
||||||
|
t.Dirty = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddDelegation will add a new delegated role with the given keys,
|
||||||
|
// ensuring the keys either already exist, or are added to the map
|
||||||
|
// of delegation keys
|
||||||
|
func (t *SignedTargets) AddDelegation(role *Role, keys []*PublicKey) error {
|
||||||
|
return errors.New("Not Implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToSigned partially serializes a SignedTargets for further signing
|
||||||
|
func (t *SignedTargets) ToSigned() (*Signed, error) {
|
||||||
|
s, err := defaultSerializer.MarshalCanonical(t.Signed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
signed := json.RawMessage{}
|
||||||
|
err = signed.UnmarshalJSON(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sigs := make([]Signature, len(t.Signatures))
|
||||||
|
copy(sigs, t.Signatures)
|
||||||
|
return &Signed{
|
||||||
|
Signatures: sigs,
|
||||||
|
Signed: &signed,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON returns the serialized form of SignedTargets as bytes
|
||||||
|
func (t *SignedTargets) MarshalJSON() ([]byte, error) {
|
||||||
|
signed, err := t.ToSigned()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return defaultSerializer.Marshal(signed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TargetsFromSigned fully unpacks a Signed object into a SignedTargets, given
|
||||||
|
// a role name (so it can validate the SignedTargets object)
|
||||||
|
func TargetsFromSigned(s *Signed, roleName RoleName) (*SignedTargets, error) {
|
||||||
|
t := Targets{}
|
||||||
|
if err := defaultSerializer.Unmarshal(*s.Signed, &t); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := isValidTargetsStructure(t, roleName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sigs := make([]Signature, len(s.Signatures))
|
||||||
|
copy(sigs, s.Signatures)
|
||||||
|
return &SignedTargets{
|
||||||
|
Signatures: sigs,
|
||||||
|
Signed: t,
|
||||||
|
}, nil
|
||||||
|
}
|
136
src/vendor/github.com/docker/notary/tuf/data/timestamp.go
generated
vendored
Normal file
136
src/vendor/github.com/docker/notary/tuf/data/timestamp.go
generated
vendored
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/go/canonical/json"
|
||||||
|
"github.com/docker/notary"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SignedTimestamp is a fully unpacked timestamp.json
|
||||||
|
type SignedTimestamp struct {
|
||||||
|
Signatures []Signature
|
||||||
|
Signed Timestamp
|
||||||
|
Dirty bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timestamp is the Signed component of a timestamp.json
|
||||||
|
type Timestamp struct {
|
||||||
|
SignedCommon
|
||||||
|
Meta Files `json:"meta"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValidTimestampStructure returns an error, or nil, depending on whether the content of the struct
|
||||||
|
// is valid for timestamp metadata. This does not check signatures or expiry, just that
|
||||||
|
// the metadata content is valid.
|
||||||
|
func IsValidTimestampStructure(t Timestamp) error {
|
||||||
|
expectedType := TUFTypes[CanonicalTimestampRole]
|
||||||
|
if t.Type != expectedType {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: CanonicalTimestampRole, msg: fmt.Sprintf("expected type %s, not %s", expectedType, t.Type)}
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Version < 1 {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: CanonicalTimestampRole, msg: "version cannot be less than one"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meta is a map of FileMeta, so if the role isn't in the map it returns
|
||||||
|
// an empty FileMeta, which has an empty map, and you can check on keys
|
||||||
|
// from an empty map.
|
||||||
|
//
|
||||||
|
// For now sha256 is required and sha512 is not.
|
||||||
|
if _, ok := t.Meta[CanonicalSnapshotRole.String()].Hashes[notary.SHA256]; !ok {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: CanonicalTimestampRole, msg: "missing snapshot sha256 checksum information"}
|
||||||
|
}
|
||||||
|
if err := CheckValidHashStructures(t.Meta[CanonicalSnapshotRole.String()].Hashes); err != nil {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: CanonicalTimestampRole, msg: fmt.Sprintf("invalid snapshot checksum information, %v", err)}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTimestamp initializes a timestamp with an existing snapshot
|
||||||
|
func NewTimestamp(snapshot *Signed) (*SignedTimestamp, error) {
|
||||||
|
snapshotJSON, err := json.Marshal(snapshot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
snapshotMeta, err := NewFileMeta(bytes.NewReader(snapshotJSON), NotaryDefaultHashes...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &SignedTimestamp{
|
||||||
|
Signatures: make([]Signature, 0),
|
||||||
|
Signed: Timestamp{
|
||||||
|
SignedCommon: SignedCommon{
|
||||||
|
Type: TUFTypes[CanonicalTimestampRole],
|
||||||
|
Version: 0,
|
||||||
|
Expires: DefaultExpires(CanonicalTimestampRole),
|
||||||
|
},
|
||||||
|
Meta: Files{
|
||||||
|
CanonicalSnapshotRole.String(): snapshotMeta,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToSigned partially serializes a SignedTimestamp such that it can
|
||||||
|
// be signed
|
||||||
|
func (ts *SignedTimestamp) ToSigned() (*Signed, error) {
|
||||||
|
s, err := defaultSerializer.MarshalCanonical(ts.Signed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
signed := json.RawMessage{}
|
||||||
|
err = signed.UnmarshalJSON(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sigs := make([]Signature, len(ts.Signatures))
|
||||||
|
copy(sigs, ts.Signatures)
|
||||||
|
return &Signed{
|
||||||
|
Signatures: sigs,
|
||||||
|
Signed: &signed,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSnapshot gets the expected snapshot metadata hashes in the timestamp metadata,
|
||||||
|
// or nil if it doesn't exist
|
||||||
|
func (ts *SignedTimestamp) GetSnapshot() (*FileMeta, error) {
|
||||||
|
snapshotExpected, ok := ts.Signed.Meta[CanonicalSnapshotRole.String()]
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrMissingMeta{Role: CanonicalSnapshotRole.String()}
|
||||||
|
}
|
||||||
|
return &snapshotExpected, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON returns the serialized form of SignedTimestamp as bytes
|
||||||
|
func (ts *SignedTimestamp) MarshalJSON() ([]byte, error) {
|
||||||
|
signed, err := ts.ToSigned()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return defaultSerializer.Marshal(signed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimestampFromSigned parsed a Signed object into a fully unpacked
|
||||||
|
// SignedTimestamp
|
||||||
|
func TimestampFromSigned(s *Signed) (*SignedTimestamp, error) {
|
||||||
|
ts := Timestamp{}
|
||||||
|
if err := defaultSerializer.Unmarshal(*s.Signed, &ts); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := IsValidTimestampStructure(ts); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sigs := make([]Signature, len(s.Signatures))
|
||||||
|
copy(sigs, s.Signatures)
|
||||||
|
return &SignedTimestamp{
|
||||||
|
Signatures: sigs,
|
||||||
|
Signed: ts,
|
||||||
|
}, nil
|
||||||
|
}
|
388
src/vendor/github.com/docker/notary/tuf/data/types.go
generated
vendored
Normal file
388
src/vendor/github.com/docker/notary/tuf/data/types.go
generated
vendored
Normal file
@ -0,0 +1,388 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/go/canonical/json"
|
||||||
|
"github.com/docker/notary"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GUN type for specifying gun
|
||||||
|
type GUN string
|
||||||
|
|
||||||
|
func (g GUN) String() string {
|
||||||
|
return string(g)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoleName type for specifying role
|
||||||
|
type RoleName string
|
||||||
|
|
||||||
|
func (r RoleName) String() string {
|
||||||
|
return string(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parent provides the parent path role from the provided child role
|
||||||
|
func (r RoleName) Parent() RoleName {
|
||||||
|
return RoleName(path.Dir(r.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MetadataRoleMapToStringMap generates a map string of bytes from a map RoleName of bytes
|
||||||
|
func MetadataRoleMapToStringMap(roles map[RoleName][]byte) map[string][]byte {
|
||||||
|
metadata := make(map[string][]byte)
|
||||||
|
for k, v := range roles {
|
||||||
|
metadata[k.String()] = v
|
||||||
|
}
|
||||||
|
return metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRoleList generates an array of RoleName objects from a slice of strings
|
||||||
|
func NewRoleList(roles []string) []RoleName {
|
||||||
|
var roleNames []RoleName
|
||||||
|
for _, role := range roles {
|
||||||
|
roleNames = append(roleNames, RoleName(role))
|
||||||
|
}
|
||||||
|
return roleNames
|
||||||
|
}
|
||||||
|
|
||||||
|
// RolesListToStringList generates an array of string objects from a slice of roles
|
||||||
|
func RolesListToStringList(roles []RoleName) []string {
|
||||||
|
var roleNames []string
|
||||||
|
for _, role := range roles {
|
||||||
|
roleNames = append(roleNames, role.String())
|
||||||
|
}
|
||||||
|
return roleNames
|
||||||
|
}
|
||||||
|
|
||||||
|
// SigAlgorithm for types of signatures
|
||||||
|
type SigAlgorithm string
|
||||||
|
|
||||||
|
func (k SigAlgorithm) String() string {
|
||||||
|
return string(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultHashAlgorithm = "sha256"
|
||||||
|
|
||||||
|
// NotaryDefaultExpiries is the construct used to configure the default expiry times of
|
||||||
|
// the various role files.
|
||||||
|
var NotaryDefaultExpiries = map[RoleName]time.Duration{
|
||||||
|
CanonicalRootRole: notary.NotaryRootExpiry,
|
||||||
|
CanonicalTargetsRole: notary.NotaryTargetsExpiry,
|
||||||
|
CanonicalSnapshotRole: notary.NotarySnapshotExpiry,
|
||||||
|
CanonicalTimestampRole: notary.NotaryTimestampExpiry,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signature types
|
||||||
|
const (
|
||||||
|
EDDSASignature SigAlgorithm = "eddsa"
|
||||||
|
RSAPSSSignature SigAlgorithm = "rsapss"
|
||||||
|
RSAPKCS1v15Signature SigAlgorithm = "rsapkcs1v15"
|
||||||
|
ECDSASignature SigAlgorithm = "ecdsa"
|
||||||
|
PyCryptoSignature SigAlgorithm = "pycrypto-pkcs#1 pss"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Key types
|
||||||
|
const (
|
||||||
|
ED25519Key = "ed25519"
|
||||||
|
RSAKey = "rsa"
|
||||||
|
RSAx509Key = "rsa-x509"
|
||||||
|
ECDSAKey = "ecdsa"
|
||||||
|
ECDSAx509Key = "ecdsa-x509"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TUFTypes is the set of metadata types
|
||||||
|
var TUFTypes = map[RoleName]string{
|
||||||
|
CanonicalRootRole: "Root",
|
||||||
|
CanonicalTargetsRole: "Targets",
|
||||||
|
CanonicalSnapshotRole: "Snapshot",
|
||||||
|
CanonicalTimestampRole: "Timestamp",
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidTUFType checks if the given type is valid for the role
|
||||||
|
func ValidTUFType(typ string, role RoleName) bool {
|
||||||
|
if ValidRole(role) {
|
||||||
|
// All targets delegation roles must have
|
||||||
|
// the valid type is for targets.
|
||||||
|
if role == "" {
|
||||||
|
// role is unknown and does not map to
|
||||||
|
// a type
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(role.String(), CanonicalTargetsRole.String()+"/") {
|
||||||
|
role = CanonicalTargetsRole
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// most people will just use the defaults so have this optimal check
|
||||||
|
// first. Do comparison just in case there is some unknown vulnerability
|
||||||
|
// if a key and value in the map differ.
|
||||||
|
if v, ok := TUFTypes[role]; ok {
|
||||||
|
return typ == v
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signed is the high level, partially deserialized metadata object
|
||||||
|
// used to verify signatures before fully unpacking, or to add signatures
|
||||||
|
// before fully packing
|
||||||
|
type Signed struct {
|
||||||
|
Signed *json.RawMessage `json:"signed"`
|
||||||
|
Signatures []Signature `json:"signatures"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedCommon contains the fields common to the Signed component of all
|
||||||
|
// TUF metadata files
|
||||||
|
type SignedCommon struct {
|
||||||
|
Type string `json:"_type"`
|
||||||
|
Expires time.Time `json:"expires"`
|
||||||
|
Version int `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedMeta is used in server validation where we only need signatures
|
||||||
|
// and common fields
|
||||||
|
type SignedMeta struct {
|
||||||
|
Signed SignedCommon `json:"signed"`
|
||||||
|
Signatures []Signature `json:"signatures"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signature is a signature on a piece of metadata
|
||||||
|
type Signature struct {
|
||||||
|
KeyID string `json:"keyid"`
|
||||||
|
Method SigAlgorithm `json:"method"`
|
||||||
|
Signature []byte `json:"sig"`
|
||||||
|
IsValid bool `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Files is the map of paths to file meta container in targets and delegations
|
||||||
|
// metadata files
|
||||||
|
type Files map[string]FileMeta
|
||||||
|
|
||||||
|
// Hashes is the map of hash type to digest created for each metadata
|
||||||
|
// and target file
|
||||||
|
type Hashes map[string][]byte
|
||||||
|
|
||||||
|
// NotaryDefaultHashes contains the default supported hash algorithms.
|
||||||
|
var NotaryDefaultHashes = []string{notary.SHA256, notary.SHA512}
|
||||||
|
|
||||||
|
// FileMeta contains the size and hashes for a metadata or target file. Custom
|
||||||
|
// data can be optionally added.
|
||||||
|
type FileMeta struct {
|
||||||
|
Length int64 `json:"length"`
|
||||||
|
Hashes Hashes `json:"hashes"`
|
||||||
|
Custom *json.RawMessage `json:"custom,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals returns true if the other FileMeta object is equivalent to this one
|
||||||
|
func (f FileMeta) Equals(o FileMeta) bool {
|
||||||
|
if o.Length != f.Length || len(f.Hashes) != len(f.Hashes) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if f.Custom == nil && o.Custom != nil || f.Custom != nil && o.Custom == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// we don't care if these are valid hashes, just that they are equal
|
||||||
|
for key, val := range f.Hashes {
|
||||||
|
if !bytes.Equal(val, o.Hashes[key]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if f.Custom == nil && o.Custom == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
fBytes, err := f.Custom.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
oBytes, err := o.Custom.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return bytes.Equal(fBytes, oBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckHashes verifies all the checksums specified by the "hashes" of the payload.
|
||||||
|
func CheckHashes(payload []byte, name string, hashes Hashes) error {
|
||||||
|
cnt := 0
|
||||||
|
|
||||||
|
// k, v indicate the hash algorithm and the corresponding value
|
||||||
|
for k, v := range hashes {
|
||||||
|
switch k {
|
||||||
|
case notary.SHA256:
|
||||||
|
checksum := sha256.Sum256(payload)
|
||||||
|
if subtle.ConstantTimeCompare(checksum[:], v) == 0 {
|
||||||
|
return ErrMismatchedChecksum{alg: notary.SHA256, name: name, expected: hex.EncodeToString(v)}
|
||||||
|
}
|
||||||
|
cnt++
|
||||||
|
case notary.SHA512:
|
||||||
|
checksum := sha512.Sum512(payload)
|
||||||
|
if subtle.ConstantTimeCompare(checksum[:], v) == 0 {
|
||||||
|
return ErrMismatchedChecksum{alg: notary.SHA512, name: name, expected: hex.EncodeToString(v)}
|
||||||
|
}
|
||||||
|
cnt++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cnt == 0 {
|
||||||
|
return ErrMissingMeta{Role: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompareMultiHashes verifies that the two Hashes passed in can represent the same data.
|
||||||
|
// This means that both maps must have at least one key defined for which they map, and no conflicts.
|
||||||
|
// Note that we check the intersection of map keys, which adds support for non-default hash algorithms in notary
|
||||||
|
func CompareMultiHashes(hashes1, hashes2 Hashes) error {
|
||||||
|
// First check if the two hash structures are valid
|
||||||
|
if err := CheckValidHashStructures(hashes1); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := CheckValidHashStructures(hashes2); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Check if they have at least one matching hash, and no conflicts
|
||||||
|
cnt := 0
|
||||||
|
for hashAlg, hash1 := range hashes1 {
|
||||||
|
|
||||||
|
hash2, ok := hashes2[hashAlg]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if subtle.ConstantTimeCompare(hash1[:], hash2[:]) == 0 {
|
||||||
|
return fmt.Errorf("mismatched %s checksum", hashAlg)
|
||||||
|
}
|
||||||
|
// If we reached here, we had a match
|
||||||
|
cnt++
|
||||||
|
}
|
||||||
|
|
||||||
|
if cnt == 0 {
|
||||||
|
return fmt.Errorf("at least one matching hash needed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckValidHashStructures returns an error, or nil, depending on whether
|
||||||
|
// the content of the hashes is valid or not.
|
||||||
|
func CheckValidHashStructures(hashes Hashes) error {
|
||||||
|
cnt := 0
|
||||||
|
|
||||||
|
for k, v := range hashes {
|
||||||
|
switch k {
|
||||||
|
case notary.SHA256:
|
||||||
|
if len(v) != sha256.Size {
|
||||||
|
return ErrInvalidChecksum{alg: notary.SHA256}
|
||||||
|
}
|
||||||
|
cnt++
|
||||||
|
case notary.SHA512:
|
||||||
|
if len(v) != sha512.Size {
|
||||||
|
return ErrInvalidChecksum{alg: notary.SHA512}
|
||||||
|
}
|
||||||
|
cnt++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cnt == 0 {
|
||||||
|
return fmt.Errorf("at least one supported hash needed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileMeta generates a FileMeta object from the reader, using the
|
||||||
|
// hash algorithms provided
|
||||||
|
func NewFileMeta(r io.Reader, hashAlgorithms ...string) (FileMeta, error) {
|
||||||
|
if len(hashAlgorithms) == 0 {
|
||||||
|
hashAlgorithms = []string{defaultHashAlgorithm}
|
||||||
|
}
|
||||||
|
hashes := make(map[string]hash.Hash, len(hashAlgorithms))
|
||||||
|
for _, hashAlgorithm := range hashAlgorithms {
|
||||||
|
var h hash.Hash
|
||||||
|
switch hashAlgorithm {
|
||||||
|
case notary.SHA256:
|
||||||
|
h = sha256.New()
|
||||||
|
case notary.SHA512:
|
||||||
|
h = sha512.New()
|
||||||
|
default:
|
||||||
|
return FileMeta{}, fmt.Errorf("Unknown hash algorithm: %s", hashAlgorithm)
|
||||||
|
}
|
||||||
|
hashes[hashAlgorithm] = h
|
||||||
|
r = io.TeeReader(r, h)
|
||||||
|
}
|
||||||
|
n, err := io.Copy(ioutil.Discard, r)
|
||||||
|
if err != nil {
|
||||||
|
return FileMeta{}, err
|
||||||
|
}
|
||||||
|
m := FileMeta{Length: n, Hashes: make(Hashes, len(hashes))}
|
||||||
|
for hashAlgorithm, h := range hashes {
|
||||||
|
m.Hashes[hashAlgorithm] = h.Sum(nil)
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delegations holds a tier of targets delegations
|
||||||
|
type Delegations struct {
|
||||||
|
Keys Keys `json:"keys"`
|
||||||
|
Roles []*Role `json:"roles"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDelegations initializes an empty Delegations object
|
||||||
|
func NewDelegations() *Delegations {
|
||||||
|
return &Delegations{
|
||||||
|
Keys: make(map[string]PublicKey),
|
||||||
|
Roles: make([]*Role, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// These values are recommended TUF expiry times.
|
||||||
|
var defaultExpiryTimes = map[RoleName]time.Duration{
|
||||||
|
CanonicalRootRole: notary.Year,
|
||||||
|
CanonicalTargetsRole: 90 * notary.Day,
|
||||||
|
CanonicalSnapshotRole: 7 * notary.Day,
|
||||||
|
CanonicalTimestampRole: notary.Day,
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaultExpiryTimes allows one to change the default expiries.
|
||||||
|
func SetDefaultExpiryTimes(times map[RoleName]time.Duration) {
|
||||||
|
for key, value := range times {
|
||||||
|
if _, ok := defaultExpiryTimes[key]; !ok {
|
||||||
|
logrus.Errorf("Attempted to set default expiry for an unknown role: %s", key.String())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
defaultExpiryTimes[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultExpires gets the default expiry time for the given role
|
||||||
|
func DefaultExpires(role RoleName) time.Time {
|
||||||
|
if d, ok := defaultExpiryTimes[role]; ok {
|
||||||
|
return time.Now().Add(d)
|
||||||
|
}
|
||||||
|
var t time.Time
|
||||||
|
return t.UTC().Round(time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
type unmarshalledSignature Signature
|
||||||
|
|
||||||
|
// UnmarshalJSON does a custom unmarshalling of the signature JSON
|
||||||
|
func (s *Signature) UnmarshalJSON(data []byte) error {
|
||||||
|
uSignature := unmarshalledSignature{}
|
||||||
|
err := json.Unmarshal(data, &uSignature)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
uSignature.Method = SigAlgorithm(strings.ToLower(string(uSignature.Method)))
|
||||||
|
*s = Signature(uSignature)
|
||||||
|
return nil
|
||||||
|
}
|
111
src/vendor/github.com/docker/notary/tuf/signed/ed25519.go
generated
vendored
Normal file
111
src/vendor/github.com/docker/notary/tuf/signed/ed25519.go
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package signed
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/docker/notary/trustmanager"
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"github.com/docker/notary/tuf/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type edCryptoKey struct {
|
||||||
|
role data.RoleName
|
||||||
|
privKey data.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ed25519 implements a simple in memory cryptosystem for ED25519 keys
|
||||||
|
type Ed25519 struct {
|
||||||
|
keys map[string]edCryptoKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEd25519 initializes a new empty Ed25519 CryptoService that operates
|
||||||
|
// entirely in memory
|
||||||
|
func NewEd25519() *Ed25519 {
|
||||||
|
return &Ed25519{
|
||||||
|
make(map[string]edCryptoKey),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddKey allows you to add a private key
|
||||||
|
func (e *Ed25519) AddKey(role data.RoleName, gun data.GUN, k data.PrivateKey) error {
|
||||||
|
e.addKey(role, k)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addKey allows you to add a private key
|
||||||
|
func (e *Ed25519) addKey(role data.RoleName, k data.PrivateKey) {
|
||||||
|
e.keys[k.ID()] = edCryptoKey{
|
||||||
|
role: role,
|
||||||
|
privKey: k,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveKey deletes a key from the signer
|
||||||
|
func (e *Ed25519) RemoveKey(keyID string) error {
|
||||||
|
delete(e.keys, keyID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListKeys returns the list of keys IDs for the role
|
||||||
|
func (e *Ed25519) ListKeys(role data.RoleName) []string {
|
||||||
|
keyIDs := make([]string, 0, len(e.keys))
|
||||||
|
for id, edCryptoKey := range e.keys {
|
||||||
|
if edCryptoKey.role == role {
|
||||||
|
keyIDs = append(keyIDs, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keyIDs
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAllKeys returns the map of keys IDs to role
|
||||||
|
func (e *Ed25519) ListAllKeys() map[string]data.RoleName {
|
||||||
|
keys := make(map[string]data.RoleName)
|
||||||
|
for id, edKey := range e.keys {
|
||||||
|
keys[id] = edKey.role
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create generates a new key and returns the public part
|
||||||
|
func (e *Ed25519) Create(role data.RoleName, gun data.GUN, algorithm string) (data.PublicKey, error) {
|
||||||
|
if algorithm != data.ED25519Key {
|
||||||
|
return nil, errors.New("only ED25519 supported by this cryptoservice")
|
||||||
|
}
|
||||||
|
|
||||||
|
private, err := utils.GenerateED25519Key(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
e.addKey(role, private)
|
||||||
|
return data.PublicKeyFromPrivate(private), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublicKeys returns a map of public keys for the ids provided, when those IDs are found
|
||||||
|
// in the store.
|
||||||
|
func (e *Ed25519) PublicKeys(keyIDs ...string) (map[string]data.PublicKey, error) {
|
||||||
|
k := make(map[string]data.PublicKey)
|
||||||
|
for _, keyID := range keyIDs {
|
||||||
|
if edKey, ok := e.keys[keyID]; ok {
|
||||||
|
k[keyID] = data.PublicKeyFromPrivate(edKey.privKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return k, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKey returns a single public key based on the ID
|
||||||
|
func (e *Ed25519) GetKey(keyID string) data.PublicKey {
|
||||||
|
if privKey, _, err := e.GetPrivateKey(keyID); err == nil {
|
||||||
|
return data.PublicKeyFromPrivate(privKey)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPrivateKey returns a single private key and role if present, based on the ID
|
||||||
|
func (e *Ed25519) GetPrivateKey(keyID string) (data.PrivateKey, data.RoleName, error) {
|
||||||
|
if k, ok := e.keys[keyID]; ok {
|
||||||
|
return k.privKey, k.role, nil
|
||||||
|
}
|
||||||
|
return nil, "", trustmanager.ErrKeyNotFound{KeyID: keyID}
|
||||||
|
}
|
98
src/vendor/github.com/docker/notary/tuf/signed/errors.go
generated
vendored
Normal file
98
src/vendor/github.com/docker/notary/tuf/signed/errors.go
generated
vendored
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package signed
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrInsufficientSignatures - can not create enough signatures on a piece of
|
||||||
|
// metadata
|
||||||
|
type ErrInsufficientSignatures struct {
|
||||||
|
FoundKeys int
|
||||||
|
NeededKeys int
|
||||||
|
MissingKeyIDs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrInsufficientSignatures) Error() string {
|
||||||
|
candidates := ""
|
||||||
|
if len(e.MissingKeyIDs) > 0 {
|
||||||
|
candidates = fmt.Sprintf(" (%s)", strings.Join(e.MissingKeyIDs, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.FoundKeys == 0 {
|
||||||
|
return fmt.Sprintf("signing keys not available: need %d keys from %d possible keys%s",
|
||||||
|
e.NeededKeys, len(e.MissingKeyIDs), candidates)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("not enough signing keys: found %d of %d needed keys - %d other possible keys%s",
|
||||||
|
e.FoundKeys, e.NeededKeys, len(e.MissingKeyIDs), candidates)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrExpired indicates a piece of metadata has expired
|
||||||
|
type ErrExpired struct {
|
||||||
|
Role data.RoleName
|
||||||
|
Expired string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrExpired) Error() string {
|
||||||
|
return fmt.Sprintf("%s expired at %v", e.Role.String(), e.Expired)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrLowVersion indicates the piece of metadata has a version number lower than
|
||||||
|
// a version number we're already seen for this role
|
||||||
|
type ErrLowVersion struct {
|
||||||
|
Actual int
|
||||||
|
Current int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrLowVersion) Error() string {
|
||||||
|
return fmt.Sprintf("version %d is lower than current version %d", e.Actual, e.Current)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrRoleThreshold indicates we did not validate enough signatures to meet the threshold
|
||||||
|
type ErrRoleThreshold struct {
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrRoleThreshold) Error() string {
|
||||||
|
if e.Msg == "" {
|
||||||
|
return "valid signatures did not meet threshold"
|
||||||
|
}
|
||||||
|
return e.Msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrInvalidKeyType indicates the types for the key and signature it's associated with are
|
||||||
|
// mismatched. Probably a sign of malicious behaviour
|
||||||
|
type ErrInvalidKeyType struct{}
|
||||||
|
|
||||||
|
func (e ErrInvalidKeyType) Error() string {
|
||||||
|
return "key type is not valid for signature"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrInvalidKeyID indicates the specified key ID was incorrect for its associated data
|
||||||
|
type ErrInvalidKeyID struct{}
|
||||||
|
|
||||||
|
func (e ErrInvalidKeyID) Error() string {
|
||||||
|
return "key ID is not valid for key content"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrInvalidKeyLength indicates that while we may support the cipher, the provided
|
||||||
|
// key length is not specifically supported, i.e. we support RSA, but not 1024 bit keys
|
||||||
|
type ErrInvalidKeyLength struct {
|
||||||
|
msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrInvalidKeyLength) Error() string {
|
||||||
|
return fmt.Sprintf("key length is not supported: %s", e.msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrNoKeys indicates no signing keys were found when trying to sign
|
||||||
|
type ErrNoKeys struct {
|
||||||
|
KeyIDs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrNoKeys) Error() string {
|
||||||
|
return fmt.Sprintf("could not find necessary signing keys, at least one of these keys must be available: %s",
|
||||||
|
strings.Join(e.KeyIDs, ", "))
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user