2017-04-17 18:08:24 -04:00
|
|
|
package client
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/docker/notary/client/changelist"
|
|
|
|
store "github.com/docker/notary/storage"
|
|
|
|
"github.com/docker/notary/tuf"
|
|
|
|
"github.com/docker/notary/tuf/data"
|
2017-08-24 18:40:24 -04:00
|
|
|
"github.com/docker/notary/tuf/signed"
|
2017-04-17 18:08:24 -04:00
|
|
|
"github.com/docker/notary/tuf/utils"
|
2017-09-11 17:07:00 -04:00
|
|
|
"github.com/sirupsen/logrus"
|
2017-04-17 18:08:24 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
// Use this to initialize remote HTTPStores from the config settings
|
2017-08-24 18:40:24 -04:00
|
|
|
func getRemoteStore(baseURL string, gun data.GUN, rt http.RoundTripper) (store.RemoteStore, error) {
|
2017-04-17 18:08:24 -04:00
|
|
|
s, err := store.NewHTTPStore(
|
2017-08-24 18:40:24 -04:00
|
|
|
baseURL+"/v2/"+gun.String()+"/_trust/tuf/",
|
2017-04-17 18:08:24 -04:00
|
|
|
"",
|
|
|
|
"json",
|
|
|
|
"key",
|
|
|
|
rt,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return store.OfflineStore{}, err
|
|
|
|
}
|
2017-08-24 18:40:24 -04:00
|
|
|
return s, nil
|
2017-04-17 18:08:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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:
|
2017-08-24 18:40:24 -04:00
|
|
|
return fmt.Errorf("scope not supported: %s", c.Scope().String())
|
2017-04-17 18:08:24 -04:00
|
|
|
}
|
|
|
|
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() {
|
2017-08-24 18:40:24 -04:00
|
|
|
case changelist.TypeBaseRole:
|
2017-04-17 18:08:24 -04:00
|
|
|
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
|
2017-08-24 18:40:24 -04:00
|
|
|
func getRemoteKey(role data.RoleName, remote store.RemoteStore) (data.PublicKey, error) {
|
2017-04-17 18:08:24 -04:00
|
|
|
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
|
2017-08-24 18:40:24 -04:00
|
|
|
func rotateRemoteKey(role data.RoleName, remote store.RemoteStore) (data.PublicKey, error) {
|
2017-04-17 18:08:24 -04:00
|
|
|
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
|
2017-08-24 18:40:24 -04:00
|
|
|
func serializeCanonicalRole(tufRepo *tuf.Repo, role data.RoleName, extraSigningKeys data.KeyList) (out []byte, err error) {
|
2017-04-17 18:08:24 -04:00
|
|
|
var s *data.Signed
|
|
|
|
switch {
|
|
|
|
case role == data.CanonicalRootRole:
|
2017-08-24 18:40:24 -04:00
|
|
|
s, err = tufRepo.SignRoot(data.DefaultExpires(role), extraSigningKeys)
|
2017-04-17 18:08:24 -04:00
|
|
|
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)
|
|
|
|
}
|
2017-08-24 18:40:24 -04:00
|
|
|
|
|
|
|
func getAllPrivKeys(rootKeyIDs []string, cryptoService signed.CryptoService) ([]data.PrivateKey, error) {
|
|
|
|
if cryptoService == nil {
|
|
|
|
return nil, fmt.Errorf("no crypto service available to get private keys from")
|
|
|
|
}
|
|
|
|
|
|
|
|
privKeys := make([]data.PrivateKey, 0, len(rootKeyIDs))
|
|
|
|
for _, keyID := range rootKeyIDs {
|
|
|
|
privKey, _, err := cryptoService.GetPrivateKey(keyID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
privKeys = append(privKeys, privKey)
|
|
|
|
}
|
|
|
|
if len(privKeys) == 0 {
|
|
|
|
var rootKeyID string
|
|
|
|
rootKeyList := cryptoService.ListKeys(data.CanonicalRootRole)
|
|
|
|
if len(rootKeyList) == 0 {
|
|
|
|
rootPublicKey, err := cryptoService.Create(data.CanonicalRootRole, "", data.ECDSAKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
rootKeyID = rootPublicKey.ID()
|
|
|
|
} else {
|
|
|
|
rootKeyID = rootKeyList[0]
|
|
|
|
}
|
|
|
|
privKey, _, err := cryptoService.GetPrivateKey(rootKeyID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
privKeys = append(privKeys, privKey)
|
|
|
|
}
|
|
|
|
|
|
|
|
return privKeys, nil
|
|
|
|
}
|