func delegationAddInput(d *delegationCommander, cmd *cobra.Command, args []string) ( config *viper.Viper, gun string, role string, keyIDs []string, error error) { if len(args) < 2 { cmd.Usage() return nil, "", "", nil, fmt.Errorf("must specify the Global Unique Name and the role of the delegation along with optional keyIDs and/or a list of paths to remove") } config, err := d.configGetter() if err != nil { return nil, "", "", nil, err } gun = args[0] role = args[1] // Check if role is valid delegation name before requiring any user input if !data.IsDelegation(role) { return nil, "", "", nil, fmt.Errorf("invalid delegation name %s", role) } // If we're only given the gun and the role, attempt to remove all data for this delegation if len(args) == 2 && d.paths == nil && !d.allPaths { d.removeAll = true } if len(args) > 2 { keyIDs = args[2:] } // If the user passes --all-paths, don't use any of the passed in --paths if d.allPaths { d.paths = nil } return config, gun, role, keyIDs, 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 string, delegationKeys []data.PublicKey) error { if !data.IsDelegation(name) { return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} } cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { return err } defer cl.Close() 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(cl, template, name) }
// UpdateDelegationKeys updates the appropriate delegations, either adding // a new delegation or updating an existing one. If keys are // provided, the IDs will be added to the role (if they do not exist // there already), and the keys will be added to the targets file. func (tr *Repo) UpdateDelegationKeys(roleName string, addKeys data.KeyList, removeKeys []string, newThreshold int) error { if !data.IsDelegation(roleName) { return data.ErrInvalidRole{Role: roleName, Reason: "not a valid delegated role"} } parent := path.Dir(roleName) if err := tr.VerifyCanSign(parent); err != nil { return err } // check the parent role's metadata _, ok := tr.Targets[parent] if !ok { // the parent targetfile may not exist yet - if not, then create it var err error _, err = tr.InitTargets(parent) if err != nil { return err } } // Walk to the parent of this delegation, since that is where its role metadata exists // We do not have to verify that the walker reached its desired role in this scenario // since we've already done another walk to the parent role in VerifyCanSign, and potentially made a targets file err := tr.WalkTargets("", parent, delegationUpdateVisitor(roleName, addKeys, removeKeys, []string{}, []string{}, false, newThreshold)) if err != nil { return err } return nil }
// VerifyCanSign returns nil if the role exists and we have at least one // signing key for the role, false otherwise. This does not check that we have // enough signing keys to meet the threshold, since we want to support the use // case of multiple signers for a role. It returns an error if the role doesn't // exist or if there are no signing keys. func (tr *Repo) VerifyCanSign(roleName string) error { var ( role data.BaseRole err error ) // we only need the BaseRole part of a delegation because we're just // checking KeyIDs if data.IsDelegation(roleName) { r, err := tr.GetDelegationRole(roleName) if err != nil { return err } role = r.BaseRole } else { role, err = tr.GetBaseRole(roleName) } if err != nil { return data.ErrInvalidRole{Role: roleName, Reason: "does not exist"} } for keyID, k := range role.Keys { check := []string{keyID} if canonicalID, err := utils.CanonicalKeyID(k); err == nil { check = append(check, canonicalID) } for _, id := range check { p, _, err := tr.cryptoService.GetPrivateKey(id) if err == nil && p != nil { return nil } } } return signed.ErrNoKeys{KeyIDs: role.ListKeyIDs()} }
// adds a TUF Change template to the given roles func addChange(cl *changelist.FileChangelist, c changelist.Change, roles ...string) error { if len(roles) == 0 { roles = []string{data.CanonicalTargetsRole} } var changes []changelist.Change for _, role := range roles { role = strings.ToLower(role) // Ensure we can only add targets to the CanonicalTargetsRole, // or a Delegation role (which is <CanonicalTargetsRole>/something else) if role != data.CanonicalTargetsRole && !data.IsDelegation(role) { return data.ErrInvalidRole{ Role: role, Reason: "cannot add targets to this role", } } changes = append(changes, changelist.NewTufChange( c.Action(), role, c.Type(), c.Path(), c.Content(), )) } for _, c := range changes { if err := cl.Add(c); err != nil { return err } } return nil }
// AddDelegation creates a new changelist entry to add a delegation to the repository // when the changelist gets applied at publish time. This does not do any validation // other than checking the name of the delegation to add - all that will happen // at publish time. func (r *NotaryRepository) AddDelegation(name string, threshold int, delegationKeys []data.PublicKey, paths []string) error { if !data.IsDelegation(name) { return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} } cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { return err } defer cl.Close() logrus.Debugf(`Adding delegation "%s" with threshold %d, and %d keys\n`, name, threshold, len(delegationKeys)) tdJSON, err := json.Marshal(&changelist.TufDelegation{ NewThreshold: threshold, AddKeys: data.KeyList(delegationKeys), AddPaths: paths, }) if err != nil { return err } template := changelist.NewTufChange( changelist.ActionCreate, name, changelist.TypeTargetsDelegation, "", // no path tdJSON, ) return addChange(cl, template, name) }
func applyChangelist(repo *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()) switch { case c.Scope() == changelist.ScopeTargets || isDel: err = applyTargetsChange(repo, c) case c.Scope() == changelist.ScopeRoot: err = applyRootChange(repo, c) default: logrus.Debug("scope not supported: ", c.Scope()) } index++ if err != nil { return err } } logrus.Debugf("applied %d change(s)", index) return nil }
// GetDelegation finds the role entry representing the provided // role name along with its associated public keys, or ErrInvalidRole func (tr *Repo) GetDelegation(role string) (*data.Role, data.Keys, error) { if !data.IsDelegation(role) { return nil, nil, data.ErrInvalidRole{Role: role, Reason: "not a valid delegated role"} } parent := path.Dir(role) // check the parent role if _, err := tr.GetDelegationRole(parent); parent != data.CanonicalTargetsRole && err != nil { return nil, nil, data.ErrInvalidRole{Role: role, Reason: "parent role not found"} } // check the parent role's metadata p, ok := tr.Targets[parent] if !ok { // the parent targetfile may not exist yet, so it can't be in the list return nil, nil, data.ErrNoSuchRole{Role: role} } foundAt := utils.FindRoleIndex(p.Signed.Delegations.Roles, role) if foundAt < 0 { return nil, nil, data.ErrNoSuchRole{Role: role} } delegationRole := p.Signed.Delegations.Roles[foundAt] keys := make(data.Keys) for _, keyID := range delegationRole.KeyIDs { keys[keyID] = p.Signed.Delegations.Keys[keyID] } return delegationRole, keys, nil }
// UpdateDelegationPaths updates the appropriate delegation's paths. // It is not allowed to create a new delegation. func (tr *Repo) UpdateDelegationPaths(roleName string, addPaths, removePaths []string, clearPaths bool) error { if !data.IsDelegation(roleName) { return data.ErrInvalidRole{Role: roleName, Reason: "not a valid delegated role"} } parent := path.Dir(roleName) if err := tr.VerifyCanSign(parent); err != nil { return err } // check the parent role's metadata _, ok := tr.Targets[parent] if !ok { // the parent targetfile may not exist yet // if not, this is an error because a delegation must exist to edit only paths return data.ErrInvalidRole{Role: roleName, Reason: "no valid delegated role exists"} } // Walk to the parent of this delegation, since that is where its role metadata exists // We do not have to verify that the walker reached its desired role in this scenario // since we've already done another walk to the parent role in VerifyCanSign err := tr.WalkTargets("", parent, delegationUpdateVisitor(roleName, data.KeyList{}, []string{}, addPaths, removePaths, clearPaths, notary.MinThreshold)) if err != nil { return err } return 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()) } 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 }
// RotateKey rotates the key for a role - this can invalidate that role's metadata // if it is not signed by that key. Particularly if the key being rotated is the // root key, because it is not signed by the new key, only the old key. func (m *MetadataSwizzler) RotateKey(role string, key data.PublicKey) error { roleSpecifier := data.CanonicalRootRole if data.IsDelegation(role) { roleSpecifier = path.Dir(role) } b, err := m.MetadataCache.GetSized(roleSpecifier, store.NoSizeLimit) if err != nil { return err } signedThing := &data.Signed{} if err := json.Unmarshal(b, signedThing); err != nil { return err } // get keys before the keys are rotated pubKeys, err := getPubKeys(m.CryptoService, signedThing, roleSpecifier) if err != nil { return err } if roleSpecifier == data.CanonicalRootRole { signedRoot, err := data.RootFromSigned(signedThing) if err != nil { return err } signedRoot.Signed.Roles[role].KeyIDs = []string{key.ID()} signedRoot.Signed.Keys[key.ID()] = key if signedThing, err = signedRoot.ToSigned(); err != nil { return err } } else { signedTargets, err := data.TargetsFromSigned(signedThing, roleSpecifier) if err != nil { return err } for _, roleObject := range signedTargets.Signed.Delegations.Roles { if roleObject.Name == role { roleObject.KeyIDs = []string{key.ID()} break } } signedTargets.Signed.Delegations.Keys[key.ID()] = key if signedThing, err = signedTargets.ToSigned(); err != nil { return err } } metaBytes, err := serializeMetadata(m.CryptoService, signedThing, roleSpecifier, pubKeys...) if err != nil { return err } return m.MetadataCache.Set(roleSpecifier, metaBytes) }
// InitTargets initializes an empty targets, and returns the new empty target func (tr *Repo) InitTargets(role string) (*data.SignedTargets, error) { if !data.IsDelegation(role) && role != data.CanonicalTargetsRole { return nil, data.ErrInvalidRole{ Role: role, Reason: fmt.Sprintf("role is not a valid targets role name: %s", role), } } targets := data.NewTargets() tr.Targets[role] = targets return targets, nil }
// UpdateDelegations updates the appropriate delegations, either adding // a new delegation or updating an existing one. If keys are // provided, the IDs will be added to the role (if they do not exist // there already), and the keys will be added to the targets file. func (tr *Repo) UpdateDelegations(role *data.Role, keys []data.PublicKey) error { if !data.IsDelegation(role.Name) { return data.ErrInvalidRole{Role: role.Name, Reason: "not a valid delegated role"} } parent := path.Dir(role.Name) if err := tr.VerifyCanSign(parent); err != nil { return err } // check the parent role's metadata p, ok := tr.Targets[parent] if !ok { // the parent targetfile may not exist yet - if not, then create it var err error p, err = tr.InitTargets(parent) if err != nil { return err } } for _, k := range keys { if !utils.StrSliceContains(role.KeyIDs, k.ID()) { role.KeyIDs = append(role.KeyIDs, k.ID()) } p.Signed.Delegations.Keys[k.ID()] = k } // if the role has fewer keys than the threshold, it // will never be able to create a valid targets file // and should be considered invalid. if len(role.KeyIDs) < role.Threshold { return data.ErrInvalidRole{Role: role.Name, Reason: "insufficient keys to meet threshold"} } foundAt := utils.FindRoleIndex(p.Signed.Delegations.Roles, role.Name) if foundAt >= 0 { p.Signed.Delegations.Roles[foundAt] = role } else { p.Signed.Delegations.Roles = append(p.Signed.Delegations.Roles, role) } // We've made a change to parent. Set it to dirty p.Dirty = true // We don't actually want to create the new delegation metadata yet. // When we add a delegation, it may only be signable by a key we don't have // (hence we are delegating signing). utils.RemoveUnusedKeys(p) return nil }
// RotateKey removes all existing keys associated with the role, and either // creates and adds one new key or delegates managing the key to the server. // These changes are staged in a changelist until publish is called. func (r *NotaryRepository) RotateKey(role string, serverManagesKey bool) error { // We currently support remotely managing timestamp and snapshot keys canBeRemoteKey := role == data.CanonicalTimestampRole || role == data.CanonicalSnapshotRole // And locally managing root, targets, and snapshot keys canBeLocalKey := (role == data.CanonicalSnapshotRole || role == data.CanonicalTargetsRole || role == data.CanonicalRootRole) switch { case !data.ValidRole(role) || data.IsDelegation(role): return fmt.Errorf("notary does not currently permit rotating the %s key", role) case serverManagesKey && !canBeRemoteKey: return ErrInvalidRemoteRole{Role: role} case !serverManagesKey && !canBeLocalKey: return ErrInvalidLocalRole{Role: role} } var ( pubKey data.PublicKey err error errFmtMsg string ) switch serverManagesKey { case true: pubKey, err = getRemoteKey(r.baseURL, r.gun, role, r.roundTrip) errFmtMsg = "unable to rotate remote key: %s" default: pubKey, err = r.CryptoService.Create(role, r.gun, data.ECDSAKey) errFmtMsg = "unable to generate key: %s" } if err != nil { return fmt.Errorf(errFmtMsg, err) } // if this is a root role, generate a root cert for the public key if role == data.CanonicalRootRole { privKey, _, err := r.CryptoService.GetPrivateKey(pubKey.ID()) if err != nil { return err } pubKey, err = rootCertKey(r.gun, privKey) if err != nil { return err } } cl := changelist.NewMemChangelist() if err := r.rootFileKeyChange(cl, role, changelist.ActionCreate, pubKey); err != nil { return err } return r.publish(cl) }
// SetThreshold sets a threshold for a metadata role - can invalidate metadata for which // the threshold is increased, if there aren't enough signatures or can be invalid because // the threshold is 0 func (m *MetadataSwizzler) SetThreshold(role string, newThreshold int) error { roleSpecifier := data.CanonicalRootRole if data.IsDelegation(role) { roleSpecifier = path.Dir(role) } b, err := m.MetadataCache.GetSized(roleSpecifier, store.NoSizeLimit) if err != nil { return err } signedThing := &data.Signed{} if err := json.Unmarshal(b, signedThing); err != nil { return err } if roleSpecifier == data.CanonicalRootRole { signedRoot, err := data.RootFromSigned(signedThing) if err != nil { return err } signedRoot.Signed.Roles[role].Threshold = newThreshold if signedThing, err = signedRoot.ToSigned(); err != nil { return err } } else { signedTargets, err := data.TargetsFromSigned(signedThing, roleSpecifier) if err != nil { return err } for _, roleObject := range signedTargets.Signed.Delegations.Roles { if roleObject.Name == role { roleObject.Threshold = newThreshold break } } if signedThing, err = signedTargets.ToSigned(); err != nil { return err } } var metaBytes []byte pubKeys, err := getPubKeys(m.CryptoService, signedThing, roleSpecifier) if err == nil { metaBytes, err = serializeMetadata(m.CryptoService, signedThing, roleSpecifier, pubKeys...) } if err != nil { return err } return m.MetadataCache.Set(roleSpecifier, metaBytes) }
// applies a function repeatedly, falling back on the parent role, until it no // longer can func doWithRoleFallback(role string, doFunc func(string) error) error { for role == data.CanonicalTargetsRole || data.IsDelegation(role) { err := doFunc(role) if err == nil { return nil } if _, ok := err.(data.ErrInvalidRole); !ok { return err } role = path.Dir(role) } return data.ErrInvalidRole{Role: role} }
// AddKey stores the contents of a PEM-encoded private key as a PEM block func (s *KeyMemoryStore) AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error { s.Lock() defer s.Unlock() if keyInfo.Role == data.CanonicalRootRole || data.IsDelegation(keyInfo.Role) || !data.ValidRole(keyInfo.Role) { keyInfo.Gun = "" } err := addKey(s, s.PassRetriever, s.cachedKeys, filepath.Join(keyInfo.Gun, privKey.ID()), keyInfo.Role, privKey) if err != nil { return err } s.keyInfoMap[privKey.ID()] = keyInfo return nil }
// GetDelegationRole gets a delegation role from this repo's metadata, walking from the targets role down to the delegation itself func (tr *Repo) GetDelegationRole(name string) (data.DelegationRole, error) { if !data.IsDelegation(name) { return data.DelegationRole{}, data.ErrInvalidRole{Role: name, Reason: "invalid delegation name"} } if tr.Root == nil { return data.DelegationRole{}, ErrNotLoaded{data.CanonicalRootRole} } _, ok := tr.Root.Signed.Roles[data.CanonicalTargetsRole] if !ok { return data.DelegationRole{}, ErrNotLoaded{data.CanonicalTargetsRole} } // Traverse target metadata, down to delegation itself // Get all public keys for the base role from TUF metadata _, ok = tr.Targets[data.CanonicalTargetsRole] if !ok { return data.DelegationRole{}, ErrNotLoaded{data.CanonicalTargetsRole} } // Start with top level roles in targets. Walk the chain of ancestors // until finding the desired role, or we run out of targets files to search. var foundRole *data.DelegationRole buildDelegationRoleVisitor := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} { // Try to find the delegation and build a DelegationRole structure for _, role := range tgt.Signed.Delegations.Roles { if role.Name == name { delgRole, err := tgt.BuildDelegationRole(name) if err != nil { return err } foundRole = &delgRole return StopWalk{} } } return nil } // Walk to the parent of this delegation, since that is where its role metadata exists err := tr.WalkTargets("", path.Dir(name), buildDelegationRoleVisitor) if err != nil { return data.DelegationRole{}, err } // We never found the delegation. In the context of this repo it is considered // invalid. N.B. it may be that it existed at one point but an ancestor has since // been modified/removed. if foundRole == nil { return data.DelegationRole{}, data.ErrInvalidRole{Role: name, Reason: "delegation does not exist"} } return *foundRole, nil }
func loadAndValidateTargets(gun string, repo *tuf.Repo, roles map[string]storage.MetaUpdate, store storage.MetaStore) ([]storage.MetaUpdate, error) { targetsRoles := make(utils.RoleList, 0) for role := range roles { if role == data.CanonicalTargetsRole || data.IsDelegation(role) { targetsRoles = append(targetsRoles, role) } } // N.B. RoleList sorts paths with fewer segments first. // By sorting, we'll always process shallower targets updates before deeper // ones (i.e. we'll load and validate targets before targets/foo). This // helps ensure we only load from storage when necessary in a cleaner way. sort.Sort(targetsRoles) updatesToApply := make([]storage.MetaUpdate, 0, len(targetsRoles)) for _, role := range targetsRoles { // don't load parent if current role is "targets", // we must load all ancestor roles for delegations to validate the full parent chain ancestorRole := role for ancestorRole != data.CanonicalTargetsRole { ancestorRole = path.Dir(ancestorRole) if _, ok := repo.Targets[ancestorRole]; !ok { err := loadTargetsFromStore(gun, ancestorRole, repo, store) if err != nil { return nil, err } } } var ( t *data.SignedTargets err error ) if t, err = validateTargets(role, roles, repo); err != nil { if _, ok := err.(data.ErrInvalidRole); ok { // role wasn't found in its parent. It has been removed // or never existed. Drop this role from the update // (by not adding it to updatesToApply) continue } logrus.Error("ErrBadTargets: ", err.Error()) return nil, validation.ErrBadTargets{Msg: err.Error()} } // this will load keys and roles into the kdb err = repo.SetTargets(role, t) if err != nil { return nil, err } updatesToApply = append(updatesToApply, roles[role]) } return updatesToApply, nil }
func loadAndValidateTargets(gun string, builder tuf.RepoBuilder, roles map[string]storage.MetaUpdate, store storage.MetaStore) ([]storage.MetaUpdate, error) { targetsRoles := make(utils.RoleList, 0) for role := range roles { if role == data.CanonicalTargetsRole || data.IsDelegation(role) { targetsRoles = append(targetsRoles, role) } } // N.B. RoleList sorts paths with fewer segments first. // By sorting, we'll always process shallower targets updates before deeper // ones (i.e. we'll load and validate targets before targets/foo). This // helps ensure we only load from storage when necessary in a cleaner way. sort.Sort(targetsRoles) updatesToApply := make([]storage.MetaUpdate, 0, len(targetsRoles)) for _, roleName := range targetsRoles { // don't load parent if current role is "targets", // we must load all ancestor roles, starting from `targets` and working down, // for delegations to validate the full parent chain var parentsToLoad []string ancestorRole := roleName for ancestorRole != data.CanonicalTargetsRole { ancestorRole = path.Dir(ancestorRole) if !builder.IsLoaded(ancestorRole) { parentsToLoad = append(parentsToLoad, ancestorRole) } } for i := len(parentsToLoad) - 1; i >= 0; i-- { if err := loadFromStore(gun, parentsToLoad[i], builder, store); err != nil { // if the parent doesn't exist, just keep going - loading the role will eventually fail // due to it being an invalid role if _, ok := err.(storage.ErrNotFound); !ok { return nil, err } } } if err := builder.Load(roleName, roles[roleName].Data, 1, false); err != nil { logrus.Error("ErrBadTargets: ", err.Error()) return nil, validation.ErrBadTargets{Msg: err.Error()} } updatesToApply = append(updatesToApply, roles[roleName]) } return updatesToApply, nil }
func checkRotationInput(role string, serverManaged bool) error { // We currently support remotely managing timestamp and snapshot keys canBeRemoteKey := role == data.CanonicalTimestampRole || role == data.CanonicalSnapshotRole // And locally managing root, targets, and snapshot keys canBeLocalKey := role == data.CanonicalSnapshotRole || role == data.CanonicalTargetsRole || role == data.CanonicalRootRole switch { case !data.ValidRole(role) || data.IsDelegation(role): return fmt.Errorf("notary does not currently permit rotating the %s key", role) case serverManaged && !canBeRemoteKey: return ErrInvalidRemoteRole{Role: role} case !serverManaged && !canBeLocalKey: return ErrInvalidLocalRole{Role: role} } 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 string) error { if !data.IsDelegation(name) { return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} } cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { return err } defer cl.Close() logrus.Debugf(`Removing delegation "%s"\n`, name) template := newDeleteDelegationChange(name, nil) return addChange(cl, template, name) }
// RemoveDelegation creates a new changelist entry to remove a delegation from // the repository when the changelist gets applied at publish time. // This does not validate that the delegation exists, since one might exist // after applying all changes. func (r *NotaryRepository) RemoveDelegation(name string, keyIDs, paths []string, removeAll bool) error { if !data.IsDelegation(name) { return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} } cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { return err } defer cl.Close() logrus.Debugf(`Removing delegation "%s"\n`, name) var template *changelist.TufChange // We use the Delete action only for force removal, Update is used for removing individual keys and paths if removeAll { template = changelist.NewTufChange( changelist.ActionDelete, name, changelist.TypeTargetsDelegation, "", // no path nil, // deleting role, no data needed ) } else { tdJSON, err := json.Marshal(&changelist.TufDelegation{ RemoveKeys: keyIDs, RemovePaths: paths, }) if err != nil { return err } template = changelist.NewTufChange( changelist.ActionUpdate, name, changelist.TypeTargetsDelegation, "", // no path tdJSON, ) } return addChange(cl, template, name) }
// DeleteDelegation removes a delegated targets role from its parent // targets object. It also deletes the delegation from the snapshot. // DeleteDelegation will only make use of the role Name field. func (tr *Repo) DeleteDelegation(role data.Role) error { if !data.IsDelegation(role.Name) { return data.ErrInvalidRole{Role: role.Name, Reason: "not a valid delegated role"} } // the role variable must not be used past this assignment for safety name := role.Name parent := path.Dir(name) if err := tr.VerifyCanSign(parent); err != nil { return err } // delete delegated data from Targets map and Snapshot - if they don't // exist, these are no-op delete(tr.Targets, name) tr.Snapshot.DeleteMeta(name) p, ok := tr.Targets[parent] if !ok { // if there is no parent metadata (the role exists though), then this // is as good as done. return nil } foundAt := utils.FindRoleIndex(p.Signed.Delegations.Roles, name) if foundAt >= 0 { var roles []*data.Role // slice out deleted role roles = append(roles, p.Signed.Delegations.Roles[:foundAt]...) if foundAt+1 < len(p.Signed.Delegations.Roles) { roles = append(roles, p.Signed.Delegations.Roles[foundAt+1:]...) } p.Signed.Delegations.Roles = roles utils.RemoveUnusedKeys(p) p.Dirty = true } // if the role wasn't found, it's a good as deleted return nil }
// 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, 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{alias: keyInfo.Role, key: privKey} err = s.store.Set(keyID, pemPrivKey) if err != nil { return err } s.keyInfoMap[privKey.ID()] = keyInfo return nil }
// ListRoles returns a list of RoleWithSignatures objects for this repo // This represents the latest metadata for each role in this repo func (r *NotaryRepository) ListRoles() ([]RoleWithSignatures, error) { // Update to latest repo state _, err := r.Update(false) if err != nil { return nil, err } // Get all role info from our updated keysDB, can be empty roles := r.tufRepo.GetAllLoadedRoles() var roleWithSigs []RoleWithSignatures // Populate RoleWithSignatures with Role from keysDB and signatures from TUF metadata for _, role := range roles { roleWithSig := RoleWithSignatures{Role: *role, Signatures: nil} switch role.Name { case data.CanonicalRootRole: roleWithSig.Signatures = r.tufRepo.Root.Signatures case data.CanonicalTargetsRole: roleWithSig.Signatures = r.tufRepo.Targets[data.CanonicalTargetsRole].Signatures case data.CanonicalSnapshotRole: roleWithSig.Signatures = r.tufRepo.Snapshot.Signatures case data.CanonicalTimestampRole: roleWithSig.Signatures = r.tufRepo.Timestamp.Signatures default: // If the role isn't a delegation, we should error -- this is only possible if we have invalid state if !data.IsDelegation(role.Name) { return nil, data.ErrInvalidRole{Role: role.Name, Reason: "invalid role name"} } if _, ok := r.tufRepo.Targets[role.Name]; ok { // We'll only find a signature if we've published any targets with this delegation roleWithSig.Signatures = r.tufRepo.Targets[role.Name].Signatures } } roleWithSigs = append(roleWithSigs, roleWithSig) } return roleWithSigs, nil }
func testAddKeyWithRole(t *testing.T, role, expectedSubdir string) { gun := "docker.com/notary" testExt := "key" // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") require.NoError(t, err, "failed to create a temporary directory") defer os.RemoveAll(tempBaseDir) // Create our store store, err := NewKeyFileStore(tempBaseDir, passphraseRetriever) require.NoError(t, err, "failed to create new key filestore") privKey, err := GenerateECDSAKey(rand.Reader) require.NoError(t, err, "could not generate private key") // Since we're generating this manually we need to add the extension '.' expectedFilePath := filepath.Join(tempBaseDir, notary.PrivDir, expectedSubdir, privKey.ID()+"."+testExt) // Call the AddKey function err = store.AddKey(KeyInfo{Role: role, Gun: gun}, privKey) require.NoError(t, err, "failed to add key to store") // Check to see if file exists b, err := ioutil.ReadFile(expectedFilePath) require.NoError(t, err, "expected file not found") require.Contains(t, string(b), "-----BEGIN EC PRIVATE KEY-----") // Check that we have the role and gun info for this key's ID keyInfo, ok := store.keyInfoMap[privKey.ID()] require.True(t, ok) require.Equal(t, role, keyInfo.Role) if role == data.CanonicalRootRole || data.IsDelegation(role) || !data.ValidRole(role) { require.Empty(t, keyInfo.Gun) } else { require.Equal(t, gun, keyInfo.Gun) } }
// ClearDelegationPaths creates a changelist entry to remove all paths from an existing delegation. func (r *NotaryRepository) ClearDelegationPaths(name string) error { if !data.IsDelegation(name) { return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} } cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { return err } defer cl.Close() 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(cl, 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 string, paths []string) error { if !data.IsDelegation(name) { return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} } cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { return err } defer cl.Close() 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(cl, 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 string, keyIDs []string) error { if !data.IsDelegation(name) && !data.IsWildDelegation(name) { return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} } cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { return err } defer cl.Close() 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(cl, template, name) }