diff --git a/Dockerfile b/Dockerfile index 51b6cf08fe..951c0cf7f5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -137,7 +137,7 @@ RUN set -x \ && rm -rf "$GOPATH" # Install notary server -ENV NOTARY_COMMIT 77bced079e83d80f40c1f0a544b1a8a3b97fb052 +ENV NOTARY_COMMIT 8e8122eb5528f621afcd4e2854c47302f17392f7 RUN set -x \ && export GOPATH="$(mktemp -d)" \ && git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \ diff --git a/hack/vendor.sh b/hack/vendor.sh index 6a95f3b3fb..038f0c9d74 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -39,8 +39,8 @@ clone git github.com/hashicorp/consul v0.5.2 clone git github.com/docker/distribution 7dc8d4a26b689bd4892f2f2322dbce0b7119d686 clone git github.com/vbatts/tar-split v0.9.4 -clone git github.com/docker/notary 77bced079e83d80f40c1f0a544b1a8a3b97fb052 -clone git github.com/endophage/gotuf 374908abc8af7e953a2813c5c2b3944ab625ca68 +clone git github.com/docker/notary 8e8122eb5528f621afcd4e2854c47302f17392f7 +clone git github.com/endophage/gotuf 89ceb27829b9353dfee5ccccf7a3a9bb77008b05 clone git github.com/tent/canonical-json-go 96e4ba3a7613a1216cbd1badca4efe382adea337 clone git github.com/agl/ed25519 d2b94fd789ea21d12fac1a4443dd3a3f79cda72c diff --git a/vendor/src/github.com/docker/notary/LICENSE b/vendor/src/github.com/docker/notary/LICENSE index 5c304d1a4a..6daf85e9de 100644 --- a/vendor/src/github.com/docker/notary/LICENSE +++ b/vendor/src/github.com/docker/notary/LICENSE @@ -186,7 +186,7 @@ Apache License same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {yyyy} {name of copyright owner} + 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. diff --git a/vendor/src/github.com/docker/notary/client/changelist/change.go b/vendor/src/github.com/docker/notary/client/changelist/change.go index 77544dc661..867c230517 100644 --- a/vendor/src/github.com/docker/notary/client/changelist/change.go +++ b/vendor/src/github.com/docker/notary/client/changelist/change.go @@ -1,9 +1,19 @@ package changelist +// 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" + ScopeSnapshot = "snapshot" + ScopeTimestamp = "timestamp" +) + // 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 int `json:"action"` + Actn string `json:"action"` Role string `json:"role"` ChangeType string `json:"type"` ChangePath string `json:"path"` @@ -11,7 +21,7 @@ type TufChange struct { } // NewTufChange initializes a tufChange object -func NewTufChange(action int, role, changeType, changePath string, content []byte) *TufChange { +func NewTufChange(action string, role, changeType, changePath string, content []byte) *TufChange { return &TufChange{ Actn: action, Role: role, @@ -22,7 +32,7 @@ func NewTufChange(action int, role, changeType, changePath string, content []byt } // Action return c.Actn -func (c TufChange) Action() int { +func (c TufChange) Action() string { return c.Actn } diff --git a/vendor/src/github.com/docker/notary/client/changelist/changelist.go b/vendor/src/github.com/docker/notary/client/changelist/changelist.go index aef4970119..80cd2461f6 100644 --- a/vendor/src/github.com/docker/notary/client/changelist/changelist.go +++ b/vendor/src/github.com/docker/notary/client/changelist/changelist.go @@ -5,6 +5,11 @@ 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 diff --git a/vendor/src/github.com/docker/notary/client/changelist/files_changelist.go b/vendor/src/github.com/docker/notary/client/changelist/file_changelist.go similarity index 100% rename from vendor/src/github.com/docker/notary/client/changelist/files_changelist.go rename to vendor/src/github.com/docker/notary/client/changelist/file_changelist.go diff --git a/vendor/src/github.com/docker/notary/client/changelist/interface.go b/vendor/src/github.com/docker/notary/client/changelist/interface.go index fd24b65c54..a9b09b71fe 100644 --- a/vendor/src/github.com/docker/notary/client/changelist/interface.go +++ b/vendor/src/github.com/docker/notary/client/changelist/interface.go @@ -22,17 +22,17 @@ type Changelist interface { const ( // ActionCreate represents a Create action - ActionCreate = iota + ActionCreate = "create" // ActionUpdate represents an Update action - ActionUpdate + ActionUpdate = "update" // ActionDelete represents a Delete action - ActionDelete + ActionDelete = "delete" ) // Change is the interface for a TUF Change type Change interface { // "create","update", or "delete" - Action() int + Action() string // Where the change should be made. // For TUF this will be the role diff --git a/vendor/src/github.com/docker/notary/client/client.go b/vendor/src/github.com/docker/notary/client/client.go index 6c8e3a8aa9..6d59af720a 100644 --- a/vendor/src/github.com/docker/notary/client/client.go +++ b/vendor/src/github.com/docker/notary/client/client.go @@ -250,7 +250,7 @@ func (r *NotaryRepository) AddTarget(target *Target) error { return err } - c := changelist.NewTufChange(changelist.ActionCreate, "targets", "target", target.Name, metaJSON) + c := changelist.NewTufChange(changelist.ActionCreate, changelist.ScopeTargets, "target", target.Name, metaJSON) err = cl.Add(c) if err != nil { return err @@ -258,6 +258,22 @@ func (r *NotaryRepository) AddTarget(target *Target) error { return cl.Close() } +// RemoveTarget creates a new changelist entry to remove a target from the repository +// when the changelist gets applied at publish time +func (r *NotaryRepository) RemoveTarget(targetName string) error { + cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) + if err != nil { + return err + } + logrus.Debugf("Removing target \"%s\"", targetName) + c := changelist.NewTufChange(changelist.ActionDelete, changelist.ScopeTargets, "target", targetName, nil) + err = cl.Add(c) + if err != nil { + return err + } + return nil +} + // ListTargets lists all targets for the current repository func (r *NotaryRepository) ListTargets() ([]*Target, error) { c, err := r.bootstrapClient() diff --git a/vendor/src/github.com/docker/notary/client/helpers.go b/vendor/src/github.com/docker/notary/client/helpers.go index 003f73fa91..93040b41da 100644 --- a/vendor/src/github.com/docker/notary/client/helpers.go +++ b/vendor/src/github.com/docker/notary/client/helpers.go @@ -5,6 +5,7 @@ import ( "net/http" "time" + "github.com/Sirupsen/logrus" "github.com/docker/notary/client/changelist" "github.com/endophage/gotuf" "github.com/endophage/gotuf/data" @@ -26,13 +27,16 @@ func getRemoteStore(baseURL, gun string, rt http.RoundTripper) (store.RemoteStor func applyChangelist(repo *tuf.TufRepo, cl changelist.Changelist) error { changes := cl.List() - var err error + logrus.Debugf("applying %d changes", len(changes)) for _, c := range changes { - if c.Scope() == "targets" { - applyTargetsChange(repo, c) - } - if err != nil { - return err + switch c.Scope() { + case changelist.ScopeTargets: + err := applyTargetsChange(repo, c) + if err != nil { + return err + } + default: + logrus.Debug("scope not supported: ", c.Scope()) } } return nil @@ -40,16 +44,21 @@ func applyChangelist(repo *tuf.TufRepo, cl changelist.Changelist) error { func applyTargetsChange(repo *tuf.TufRepo, c changelist.Change) error { var err error - meta := &data.FileMeta{} - err = json.Unmarshal(c.Content(), meta) - if err != nil { - return nil - } - if c.Action() == changelist.ActionCreate { + 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} - _, err = repo.AddTargets("targets", files) - } else if c.Action() == changelist.ActionDelete { - err = repo.RemoveTargets("targets", c.Path()) + _, err = repo.AddTargets(c.Scope(), files) + case changelist.ActionDelete: + logrus.Debug("changelist remove: ", c.Path()) + err = repo.RemoveTargets(c.Scope(), c.Path()) + default: + logrus.Debug("action not yet supported: ", c.Action()) } if err != nil { return err diff --git a/vendor/src/github.com/docker/notary/keystoremanager/import_export.go b/vendor/src/github.com/docker/notary/keystoremanager/import_export.go index bd87244681..55c736cdad 100644 --- a/vendor/src/github.com/docker/notary/keystoremanager/import_export.go +++ b/vendor/src/github.com/docker/notary/keystoremanager/import_export.go @@ -42,6 +42,39 @@ func (km *KeyStoreManager) ExportRootKey(dest io.Writer, keyID string) error { return err } +// ExportRootKeyReencrypt exports the specified root key to an io.Writer in +// PEM format. The key is reencrypted with a new passphrase. +func (km *KeyStoreManager) ExportRootKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error { + privateKey, alias, err := km.rootKeyStore.GetKey(keyID) + if err != nil { + return err + } + + // Create temporary keystore to use as a staging area + tempBaseDir, err := ioutil.TempDir("", "notary-key-export-") + defer os.RemoveAll(tempBaseDir) + + privRootKeysSubdir := filepath.Join(privDir, rootKeysSubdir) + tempRootKeysPath := filepath.Join(tempBaseDir, privRootKeysSubdir) + tempRootKeyStore, err := trustmanager.NewKeyFileStore(tempRootKeysPath, newPassphraseRetriever) + if err != nil { + return err + } + + err = tempRootKeyStore.AddKey(keyID, alias, privateKey) + if err != nil { + return err + } + + pemBytes, err := tempRootKeyStore.Get(keyID + "_" + alias) + if err != nil { + return err + } + + _, err = dest.Write(pemBytes) + return err +} + // checkRootKeyIsEncrypted makes sure the root key is encrypted. We have // internal assumptions that depend on this. func checkRootKeyIsEncrypted(pemBytes []byte) error { @@ -80,13 +113,13 @@ func (km *KeyStoreManager) ImportRootKey(source io.Reader, keyID string) error { func moveKeys(oldKeyStore, newKeyStore *trustmanager.KeyFileStore) error { // List all files but no symlinks - for _, f := range oldKeyStore.ListKeys() { - pemBytes, alias, err := oldKeyStore.GetKey(f) + for f := range oldKeyStore.ListKeys() { + privateKey, alias, err := oldKeyStore.GetKey(f) if err != nil { return err } - err = newKeyStore.AddKey(f, alias, pemBytes) + err = newKeyStore.AddKey(f, alias, privateKey) if err != nil { return err @@ -247,7 +280,7 @@ func (km *KeyStoreManager) ImportKeysZip(zipReader zip.Reader) error { func moveKeysByGUN(oldKeyStore, newKeyStore *trustmanager.KeyFileStore, gun string) error { // List all files but no symlinks - for _, relKeyPath := range oldKeyStore.ListKeys() { + for relKeyPath := range oldKeyStore.ListKeys() { // Skip keys that aren't associated with this GUN if !strings.HasPrefix(relKeyPath, filepath.FromSlash(gun)) { diff --git a/vendor/src/github.com/docker/notary/pkg/passphrase/passphrase.go b/vendor/src/github.com/docker/notary/pkg/passphrase/passphrase.go index aae28170b4..e89092b891 100644 --- a/vendor/src/github.com/docker/notary/pkg/passphrase/passphrase.go +++ b/vendor/src/github.com/docker/notary/pkg/passphrase/passphrase.go @@ -22,28 +22,45 @@ import ( type Retriever func(keyName, alias string, createNew bool, attempts int) (passphrase string, giveup bool, err error) const ( - idBytesToDisplay = 5 + idBytesToDisplay = 7 tufRootAlias = "root" tufTargetsAlias = "targets" tufSnapshotAlias = "snapshot" - tufRootKeyGenerationWarning = `You are about to create a new root signing key passphrase. This passphrase will be used to protect -the most sensitive key in your signing system. Please choose a long, complex passphrase and be careful -to keep the password and the key file itself secure and backed up. It is highly recommended that you use -a password manager to generate the passphrase and keep it safe. There will be no way to recover this key. -You can find the key in your config directory.` + tufRootKeyGenerationWarning = `You are about to create a new root signing key passphrase. This passphrase +will be used to protect the most sensitive key in your signing system. Please +choose a long, complex passphrase and be careful to keep the password and the +key file itself secure and backed up. It is highly recommended that you use a +password manager to generate the passphrase and keep it safe. There will be no +way to recover this key. You can find the key in your config directory.` +) + +var ( + // ErrTooShort is returned if the passphrase entered for a new key is + // below the minimum length + ErrTooShort = errors.New("Passphrase too short") + + // ErrDontMatch is returned if the two entered passphrases don't match. + // new key is below the minimum length + ErrDontMatch = errors.New("The entered passphrases do not match") + + // ErrTooManyAttempts is returned if the maximum number of passphrase + // entry attempts is reached. + ErrTooManyAttempts = errors.New("Too many attempts") ) // PromptRetriever returns a new Retriever which will provide a prompt on stdin // and stdout to retrieve a passphrase. The passphrase will be cached such that // subsequent prompts will produce the same passphrase. func PromptRetriever() Retriever { - return PromptRetrieverWithInOut(os.Stdin, os.Stdout) + return PromptRetrieverWithInOut(os.Stdin, os.Stdout, nil) } // PromptRetrieverWithInOut returns a new Retriever which will provide a // prompt using the given in and out readers. The passphrase will be cached // such that subsequent prompts will produce the same passphrase. -func PromptRetrieverWithInOut(in io.Reader, out io.Writer) Retriever { +// aliasMap can be used to specify display names for TUF key aliases. If aliasMap +// is nil, a sensible default will be used. +func PromptRetrieverWithInOut(in io.Reader, out io.Writer, aliasMap map[string]string) Retriever { userEnteredTargetsSnapshotsPass := false targetsSnapshotsPass := "" userEnteredRootsPass := false @@ -54,14 +71,20 @@ func PromptRetrieverWithInOut(in io.Reader, out io.Writer) Retriever { fmt.Fprintln(out, tufRootKeyGenerationWarning) } if numAttempts > 0 { - if createNew { - fmt.Fprintln(out, "Passphrases do not match. Please retry.") - - } else { + if !createNew { fmt.Fprintln(out, "Passphrase incorrect. Please retry.") } } + // Figure out if we should display a different string for this alias + displayAlias := alias + if aliasMap != nil { + if val, ok := aliasMap[alias]; ok { + displayAlias = val + } + + } + // First, check if we have a password cached for this alias. if numAttempts == 0 { if userEnteredTargetsSnapshotsPass && (alias == tufSnapshotAlias || alias == tufTargetsAlias) { @@ -73,7 +96,7 @@ func PromptRetrieverWithInOut(in io.Reader, out io.Writer) Retriever { } if numAttempts > 3 && !createNew { - return "", true, errors.New("Too many attempts") + return "", true, ErrTooManyAttempts } state, err := term.SaveState(0) @@ -86,15 +109,24 @@ func PromptRetrieverWithInOut(in io.Reader, out io.Writer) Retriever { stdin := bufio.NewReader(in) indexOfLastSeparator := strings.LastIndex(keyName, string(filepath.Separator)) + if indexOfLastSeparator == -1 { + indexOfLastSeparator = 0 + } - if len(keyName) > indexOfLastSeparator+idBytesToDisplay+1 { - keyName = keyName[:indexOfLastSeparator+idBytesToDisplay+1] + if len(keyName) > indexOfLastSeparator+idBytesToDisplay { + if indexOfLastSeparator > 0 { + keyNamePrefix := keyName[:indexOfLastSeparator] + keyNameID := keyName[indexOfLastSeparator+1 : indexOfLastSeparator+idBytesToDisplay+1] + keyName = keyNamePrefix + " (" + keyNameID + ")" + } else { + keyName = keyName[indexOfLastSeparator : indexOfLastSeparator+idBytesToDisplay] + } } if createNew { - fmt.Fprintf(out, "Enter passphrase for new %s key with id %s: ", alias, keyName) + fmt.Fprintf(out, "Enter passphrase for new %s key with id %s: ", displayAlias, keyName) } else { - fmt.Fprintf(out, "Enter key passphrase for %s key with id %s: ", alias, keyName) + fmt.Fprintf(out, "Enter key passphrase for %s key with id %s: ", displayAlias, keyName) } passphrase, err := stdin.ReadBytes('\n') @@ -119,10 +151,10 @@ func PromptRetrieverWithInOut(in io.Reader, out io.Writer) Retriever { if len(retPass) < 8 { fmt.Fprintln(out, "Please use a password manager to generate and store a good random passphrase.") - return "", false, errors.New("Passphrase too short") + return "", false, ErrTooShort } - fmt.Fprintf(out, "Repeat passphrase for new %s key with id %s: ", alias, keyName) + fmt.Fprintf(out, "Repeat passphrase for new %s key with id %s: ", displayAlias, keyName) confirmation, err := stdin.ReadBytes('\n') fmt.Fprintln(out) if err != nil { @@ -131,7 +163,8 @@ func PromptRetrieverWithInOut(in io.Reader, out io.Writer) Retriever { confirmationStr := strings.TrimSpace(string(confirmation)) if retPass != confirmationStr { - return "", false, errors.New("The entered passphrases do not match") + fmt.Fprintln(out, "Passphrases do not match. Please retry.") + return "", false, ErrDontMatch } if alias == tufSnapshotAlias || alias == tufTargetsAlias { diff --git a/vendor/src/github.com/docker/notary/trustmanager/keyfilestore.go b/vendor/src/github.com/docker/notary/trustmanager/keyfilestore.go index b8d5fd175c..fc68463dbd 100644 --- a/vendor/src/github.com/docker/notary/trustmanager/keyfilestore.go +++ b/vendor/src/github.com/docker/notary/trustmanager/keyfilestore.go @@ -5,65 +5,10 @@ import ( "strings" "sync" - "fmt" - "github.com/docker/notary/pkg/passphrase" "github.com/endophage/gotuf/data" ) -const ( - keyExtension = "key" -) - -// 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) -} - -// KeyStore is a generic interface for private key storage -type KeyStore interface { - LimitedFileStore - - AddKey(name, alias string, privKey data.PrivateKey) error - GetKey(name string) (data.PrivateKey, string, error) - ListKeys() []string - RemoveKey(name string) error -} - -type cachedKey struct { - alias string - key data.PrivateKey -} - -// PassphraseRetriever 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 PassphraseRetriever func(keyId, alias string, createNew bool, attempts int) (passphrase string, giveup bool, err error) - // KeyFileStore persists and manages private keys on disk type KeyFileStore struct { sync.Mutex @@ -111,7 +56,7 @@ func (s *KeyFileStore) GetKey(name string) (data.PrivateKey, string, error) { // ListKeys returns a list of unique PublicKeys present on the KeyFileStore. // There might be symlinks associating Certificate IDs to Public Keys, so this // method only returns the IDs that aren't symlinks -func (s *KeyFileStore) ListKeys() []string { +func (s *KeyFileStore) ListKeys() map[string]string { return listKeys(s) } @@ -149,7 +94,7 @@ func (s *KeyMemoryStore) GetKey(name string) (data.PrivateKey, string, error) { // ListKeys returns a list of unique PublicKeys present on the KeyFileStore. // There might be symlinks associating Certificate IDs to Public Keys, so this // method only returns the IDs that aren't symlinks -func (s *KeyMemoryStore) ListKeys() []string { +func (s *KeyMemoryStore) ListKeys() map[string]string { return listKeys(s) } @@ -167,10 +112,10 @@ func addKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cached } attempts := 0 - passphrase := "" + chosenPassphrase := "" giveup := false for { - passphrase, giveup, err = passphraseRetriever(name, alias, true, attempts) + chosenPassphrase, giveup, err = passphraseRetriever(name, alias, true, attempts) if err != nil { attempts++ continue @@ -184,8 +129,8 @@ func addKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cached break } - if passphrase != "" { - pemPrivKey, err = EncryptPrivateKey(privKey, passphrase) + if chosenPassphrase != "" { + pemPrivKey, err = EncryptPrivateKey(privKey, chosenPassphrase) if err != nil { return err } @@ -261,18 +206,20 @@ func getKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cached return privKey, keyAlias, nil } -// ListKeys returns a list of unique PublicKeys present on the KeyFileStore. +// ListKeys returns a map of unique PublicKeys present on the KeyFileStore and +// their corresponding aliases. // There might be symlinks associating Certificate IDs to Public Keys, so this // method only returns the IDs that aren't symlinks -func listKeys(s LimitedFileStore) []string { - var keyIDList []string +func listKeys(s LimitedFileStore) map[string]string { + keyIDMap := make(map[string]string) for _, f := range s.ListFiles(false) { - keyID := strings.TrimSpace(strings.TrimSuffix(f, filepath.Ext(f))) - keyID = keyID[:strings.LastIndex(keyID, "_")] - keyIDList = append(keyIDList, keyID) + keyIDFull := strings.TrimSpace(strings.TrimSuffix(f, filepath.Ext(f))) + keyID := keyIDFull[:strings.LastIndex(keyIDFull, "_")] + keyAlias := keyIDFull[strings.LastIndex(keyIDFull, "_")+1:] + keyIDMap[keyID] = keyAlias } - return keyIDList + return keyIDMap } // RemoveKey removes the key from the keyfilestore diff --git a/vendor/src/github.com/docker/notary/trustmanager/keystore.go b/vendor/src/github.com/docker/notary/trustmanager/keystore.go new file mode 100644 index 0000000000..ba5fb1a1a4 --- /dev/null +++ b/vendor/src/github.com/docker/notary/trustmanager/keystore.go @@ -0,0 +1,52 @@ +package trustmanager + +import ( + "fmt" + + "github.com/endophage/gotuf/data" +) + +// 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) +} + +const ( + keyExtension = "key" +) + +// KeyStore is a generic interface for private key storage +type KeyStore interface { + AddKey(name, alias string, privKey data.PrivateKey) error + GetKey(name string) (data.PrivateKey, string, error) + ListKeys() map[string]string + RemoveKey(name string) error +} + +type cachedKey struct { + alias string + key data.PrivateKey +} diff --git a/vendor/src/github.com/docker/notary/trustmanager/x509utils.go b/vendor/src/github.com/docker/notary/trustmanager/x509utils.go index 396bd052e0..2661c7677c 100644 --- a/vendor/src/github.com/docker/notary/trustmanager/x509utils.go +++ b/vendor/src/github.com/docker/notary/trustmanager/x509utils.go @@ -351,7 +351,7 @@ func GenerateECDSAKey(random io.Reader) (data.PrivateKey, error) { // PrivateKey. The serialization format we use is just the public key bytes // followed by the private key bytes func GenerateED25519Key(random io.Reader) (data.PrivateKey, error) { - pub, priv, err := ed25519.GenerateKey(rand.Reader) + pub, priv, err := ed25519.GenerateKey(random) if err != nil { return nil, err } diff --git a/vendor/src/github.com/endophage/gotuf/client/client.go b/vendor/src/github.com/endophage/gotuf/client/client.go index 7d7c63a369..a321587ae1 100644 --- a/vendor/src/github.com/endophage/gotuf/client/client.go +++ b/vendor/src/github.com/endophage/gotuf/client/client.go @@ -50,15 +50,9 @@ func (c *Client) Update() error { logrus.Debug("updating TUF client") err := c.update() if err != nil { - switch err.(type) { - case signed.ErrRoleThreshold, signed.ErrExpired, tuf.ErrLocalRootExpired: - logrus.Debug("retryable error occurred. Root will be downloaded and another update attempted") - if err := c.downloadRoot(); err != nil { - logrus.Errorf("client Update (Root):", err) - return err - } - default: - logrus.Error("an unexpected error occurred while updating TUF client") + logrus.Debug("Error occurred. Root will be downloaded and another update attempted") + if err := c.downloadRoot(); err != nil { + logrus.Errorf("client Update (Root):", err) return err } // If we error again, we now have the latest root and just want to fail @@ -114,6 +108,20 @@ func (c Client) checkRoot() error { if !bytes.Equal(hash[:], hashSha256) { return fmt.Errorf("Cached root sha256 did not match snapshot root sha256") } + + if int64(len(raw)) != size { + return fmt.Errorf("Cached root size did not match snapshot size") + } + + root := &data.SignedRoot{} + err = json.Unmarshal(raw, root) + if err != nil { + return ErrCorruptedCache{file: "root.json"} + } + + if signed.IsExpired(root.Signed.Expires) { + return tuf.ErrLocalRootExpired{} + } return nil } diff --git a/vendor/src/github.com/endophage/gotuf/client/errors.go b/vendor/src/github.com/endophage/gotuf/client/errors.go index 92df3e2deb..776e6a69ea 100644 --- a/vendor/src/github.com/endophage/gotuf/client/errors.go +++ b/vendor/src/github.com/endophage/gotuf/client/errors.go @@ -104,3 +104,11 @@ type ErrInvalidURL struct { func (e ErrInvalidURL) Error() string { return fmt.Sprintf("tuf: invalid repository URL %s", e.URL) } + +type ErrCorruptedCache struct { + file string +} + +func (e ErrCorruptedCache) Error() string { + return fmt.Sprintf("cache is corrupted: %s", e.file) +} diff --git a/vendor/src/github.com/endophage/gotuf/data/roles.go b/vendor/src/github.com/endophage/gotuf/data/roles.go index d77529bb04..d3047d7844 100644 --- a/vendor/src/github.com/endophage/gotuf/data/roles.go +++ b/vendor/src/github.com/endophage/gotuf/data/roles.go @@ -7,16 +7,27 @@ import ( "github.com/endophage/gotuf/errors" ) +// Canonical base role names +const ( + CanonicalRootRole = "root" + CanonicalTargetsRole = "targets" + CanonicalSnapshotRole = "snapshot" + CanonicalTimestampRole = "timestamp" +) + var ValidRoles = map[string]string{ - "root": "root", - "targets": "targets", - "snapshot": "snapshot", - "timestamp": "timestamp", + CanonicalRootRole: CanonicalRootRole, + CanonicalTargetsRole: CanonicalTargetsRole, + CanonicalSnapshotRole: CanonicalSnapshotRole, + CanonicalTimestampRole: CanonicalTimestampRole, } func SetValidRoles(rs map[string]string) { - for k, v := range rs { - ValidRoles[strings.ToLower(k)] = strings.ToLower(v) + // iterate ValidRoles + for k, _ := range ValidRoles { + if v, ok := rs[k]; ok { + ValidRoles[k] = v + } } } @@ -27,6 +38,27 @@ func RoleName(role string) string { return role } +func CanonicalRole(role string) string { + name := strings.ToLower(role) + if _, ok := ValidRoles[name]; ok { + // The canonical version is always lower case + // se ensure we return name, not role + return name + } + targetsBase := fmt.Sprintf("%s/", ValidRoles[CanonicalTargetsRole]) + if strings.HasPrefix(name, targetsBase) { + role = strings.TrimPrefix(role, targetsBase) + role = fmt.Sprintf("%s/%s", CanonicalTargetsRole, role) + return role + } + for r, v := range ValidRoles { + if role == v { + return r + } + } + return "" +} + // ValidRole only determines the name is semantically // correct. For target delegated roles, it does NOT check // the the appropriate parent roles exist. @@ -35,7 +67,7 @@ func ValidRole(name string) bool { if v, ok := ValidRoles[name]; ok { return name == v } - targetsBase := fmt.Sprintf("%s/", ValidRoles["targets"]) + targetsBase := fmt.Sprintf("%s/", ValidRoles[CanonicalTargetsRole]) if strings.HasPrefix(name, targetsBase) { return true } @@ -112,6 +144,6 @@ func (r Role) CheckPrefixes(hash string) bool { } func (r Role) IsDelegation() bool { - targetsBase := fmt.Sprintf("%s/", ValidRoles["targets"]) + targetsBase := fmt.Sprintf("%s/", ValidRoles[CanonicalTargetsRole]) return strings.HasPrefix(r.Name, targetsBase) } diff --git a/vendor/src/github.com/endophage/gotuf/data/types.go b/vendor/src/github.com/endophage/gotuf/data/types.go index 9d4667165c..98d55f32be 100644 --- a/vendor/src/github.com/endophage/gotuf/data/types.go +++ b/vendor/src/github.com/endophage/gotuf/data/types.go @@ -43,10 +43,10 @@ const ( ) var TUFTypes = map[string]string{ - "targets": "Targets", - "root": "Root", - "snapshot": "Snapshot", - "timestamp": "Timestamp", + CanonicalRootRole: "Root", + CanonicalTargetsRole: "Targets", + CanonicalSnapshotRole: "Snapshot", + CanonicalTimestampRole: "Timestamp", } // SetTUFTypes allows one to override some or all of the default @@ -57,19 +57,25 @@ func SetTUFTypes(ts map[string]string) { } } -// Checks if type is correct. -func ValidTUFType(t string) bool { +func ValidTUFType(typ, role string) bool { + if ValidRole(role) { + // All targets delegation roles must have + // the valid type is for targets. + role = CanonicalRole(role) + if role == "" { + // role is unknown and does not map to + // a type + return false + } + if strings.HasPrefix(role, CanonicalTargetsRole+"/") { + 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[t]; ok { - return t == v - } - // For people that feel the need to change the default type names. - for _, v := range TUFTypes { - if t == v { - return true - } + if v, ok := TUFTypes[role]; ok { + return typ == v } return false } @@ -138,10 +144,10 @@ func NewDelegations() *Delegations { // defines number of days in which something should expire var defaultExpiryTimes = map[string]int{ - "root": 365, - "targets": 90, - "snapshot": 7, - "timestamp": 1, + CanonicalRootRole: 365, + CanonicalTargetsRole: 90, + CanonicalSnapshotRole: 7, + CanonicalTimestampRole: 1, } // SetDefaultExpiryTimes allows one to change the default expiries. diff --git a/vendor/src/github.com/endophage/gotuf/signed/errors.go b/vendor/src/github.com/endophage/gotuf/signed/errors.go index 7aec7c7232..09ecc9a719 100644 --- a/vendor/src/github.com/endophage/gotuf/signed/errors.go +++ b/vendor/src/github.com/endophage/gotuf/signed/errors.go @@ -27,3 +27,17 @@ type ErrRoleThreshold struct{} func (e ErrRoleThreshold) Error() string { return "valid signatures did not meet threshold" } + +type ErrInvalidKeyType struct{} + +func (e ErrInvalidKeyType) Error() string { + return "key type is not valid for signature" +} + +type ErrInvalidKeyLength struct { + msg string +} + +func (e ErrInvalidKeyLength) Error() string { + return fmt.Sprintf("key length is not supported: %s", e.msg) +} diff --git a/vendor/src/github.com/endophage/gotuf/signed/verifiers.go b/vendor/src/github.com/endophage/gotuf/signed/verifiers.go index fd919035dc..e11eb4ad64 100644 --- a/vendor/src/github.com/endophage/gotuf/signed/verifiers.go +++ b/vendor/src/github.com/endophage/gotuf/signed/verifiers.go @@ -7,6 +7,7 @@ import ( "crypto/sha256" "crypto/x509" "encoding/pem" + "fmt" "math/big" "reflect" @@ -15,6 +16,11 @@ import ( "github.com/endophage/gotuf/data" ) +const ( + minRSAKeySizeBit = 2048 // 2048 bits = 256 bytes + minRSAKeySizeByte = minRSAKeySizeBit / 8 +) + // Verifiers serves as a map of all verifiers available on the system and // can be injected into a verificationService. For testing and configuration // purposes, it will not be used by default. @@ -47,15 +53,27 @@ func RegisterVerifier(algorithm data.SigAlgorithm, v Verifier) { type Ed25519Verifier struct{} func (v Ed25519Verifier) Verify(key data.PublicKey, sig []byte, msg []byte) error { + if key.Algorithm() != data.ED25519Key { + return ErrInvalidKeyType{} + } var sigBytes [ed25519.SignatureSize]byte - if len(sig) != len(sigBytes) { + if len(sig) != ed25519.SignatureSize { logrus.Infof("signature length is incorrect, must be %d, was %d.", ed25519.SignatureSize, len(sig)) return ErrInvalid } copy(sigBytes[:], sig) var keyBytes [ed25519.PublicKeySize]byte - copy(keyBytes[:], key.Public()) + pub := key.Public() + if len(pub) != ed25519.PublicKeySize { + logrus.Errorf("public key is incorrect size, must be %d, was %d.", ed25519.PublicKeySize, len(pub)) + return ErrInvalidKeyLength{msg: fmt.Sprintf("ed25519 public key must be %d bytes.", ed25519.PublicKeySize)} + } + n := copy(keyBytes[:], key.Public()) + if n < ed25519.PublicKeySize { + logrus.Errorf("failed to copy the key, must have %d bytes, copied %d bytes.", ed25519.PublicKeySize, n) + return ErrInvalid + } if !ed25519.Verify(&keyBytes, msg, &sigBytes) { logrus.Infof("failed ed25519 verification") @@ -71,6 +89,16 @@ func verifyPSS(key interface{}, digest, sig []byte) error { return ErrInvalid } + if rsaPub.N.BitLen() < minRSAKeySizeBit { + logrus.Infof("RSA keys less than 2048 bits are not acceptable, provided key has length %d.", rsaPub.N.BitLen()) + return ErrInvalidKeyLength{msg: fmt.Sprintf("RSA key must be at least %d bits.", minRSAKeySizeBit)} + } + + if len(sig) < minRSAKeySizeByte { + logrus.Infof("RSA keys less than 2048 bits are not acceptable, provided signature has length %d.", len(sig)) + return ErrInvalid + } + opts := rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256} if err := rsa.VerifyPSS(rsaPub, crypto.SHA256, digest[:], sig, &opts); err != nil { logrus.Infof("failed RSAPSS verification: %s", err) @@ -104,8 +132,9 @@ func getRSAPubKey(key data.PublicKey) (crypto.PublicKey, error) { return nil, ErrInvalid } default: + // only accept RSA keys logrus.Infof("invalid key type for RSAPSS verifier: %s", algorithm) - return nil, ErrInvalid + return nil, ErrInvalidKeyType{} } return pubKey, nil @@ -116,6 +145,7 @@ type RSAPSSVerifier struct{} // Verify does the actual check. func (v RSAPSSVerifier) Verify(key data.PublicKey, sig []byte, msg []byte) error { + // will return err if keytype is not a recognized RSA type pubKey, err := getRSAPubKey(key) if err != nil { return err @@ -130,6 +160,7 @@ func (v RSAPSSVerifier) Verify(key data.PublicKey, sig []byte, msg []byte) error type RSAPKCS1v15Verifier struct{} func (v RSAPKCS1v15Verifier) Verify(key data.PublicKey, sig []byte, msg []byte) error { + // will return err if keytype is not a recognized RSA type pubKey, err := getRSAPubKey(key) if err != nil { return err @@ -142,6 +173,16 @@ func (v RSAPKCS1v15Verifier) Verify(key data.PublicKey, sig []byte, msg []byte) return ErrInvalid } + if rsaPub.N.BitLen() < minRSAKeySizeBit { + logrus.Infof("RSA keys less than 2048 bits are not acceptable, provided key has length %d.", rsaPub.N.BitLen()) + return ErrInvalidKeyLength{msg: fmt.Sprintf("RSA key must be at least %d bits.", minRSAKeySizeBit)} + } + + if len(sig) < minRSAKeySizeByte { + logrus.Infof("RSA keys less than 2048 bits are not acceptable, provided signature has length %d.", len(sig)) + return ErrInvalid + } + if err = rsa.VerifyPKCS1v15(rsaPub, crypto.SHA256, digest[:], sig); err != nil { logrus.Errorf("Failed verification: %s", err.Error()) return ErrInvalid @@ -157,6 +198,9 @@ type RSAPyCryptoVerifier struct{} // with PyCrypto. func (v RSAPyCryptoVerifier) Verify(key data.PublicKey, sig []byte, msg []byte) error { digest := sha256.Sum256(msg) + if key.Algorithm() != data.RSAKey { + return ErrInvalidKeyType{} + } k, _ := pem.Decode([]byte(key.Public())) if k == nil { @@ -203,8 +247,9 @@ func (v ECDSAVerifier) Verify(key data.PublicKey, sig []byte, msg []byte) error return ErrInvalid } default: + // only accept ECDSA keys. logrus.Infof("invalid key type for ECDSA verifier: %s", algorithm) - return ErrInvalid + return ErrInvalidKeyType{} } ecdsaPubKey, ok := pubKey.(*ecdsa.PublicKey) diff --git a/vendor/src/github.com/endophage/gotuf/signed/verify.go b/vendor/src/github.com/endophage/gotuf/signed/verify.go index f6b6d91677..fe79563f32 100644 --- a/vendor/src/github.com/endophage/gotuf/signed/verify.go +++ b/vendor/src/github.com/endophage/gotuf/signed/verify.go @@ -22,9 +22,9 @@ var ( ) type signedMeta struct { - Type string `json:"_type"` - Expires string `json:"expires"` - Version int `json:"version"` + Type string `json:"_type"` + Expires time.Time `json:"expires"` + Version int `json:"version"` } // VerifyRoot checks if a given root file is valid against a known set of keys. @@ -80,12 +80,12 @@ func verifyMeta(s *data.Signed, role string, minVersion int) error { if err := json.Unmarshal(s.Signed, sm); err != nil { return err } - if !data.ValidTUFType(sm.Type) { + if !data.ValidTUFType(sm.Type, role) { return ErrWrongType } if IsExpired(sm.Expires) { logrus.Errorf("Metadata for %s expired", role) - return ErrExpired{Role: role, Expired: sm.Expires} + return ErrExpired{Role: role, Expired: sm.Expires.Format("Mon Jan 2 15:04:05 MST 2006")} } if sm.Version < minVersion { return ErrLowVersion{sm.Version, minVersion} @@ -94,15 +94,8 @@ func verifyMeta(s *data.Signed, role string, minVersion int) error { return nil } -var IsExpired = func(t string) bool { - ts, err := time.Parse(time.RFC3339, t) - if err != nil { - ts, err = time.Parse("2006-01-02 15:04:05 MST", t) - if err != nil { - return false - } - } - return ts.Sub(time.Now()) <= 0 +var IsExpired = func(t time.Time) bool { + return t.Before(time.Now()) } func VerifySignatures(s *data.Signed, role string, db *keys.KeyDB) error {