// 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()} }
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 } 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]) } // If we specify the only keys left delete the role, else just delete specified keys if strings.Join(delgRole.ListKeyIDs(), ";") == strings.Join(removeTUFKeyIDs, ";") && len(td.AddKeys) == 0 { return repo.DeleteDelegation(c.Scope()) } 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()) } }
// Sign takes a data.Signed and a key, calculated and adds the signature // to the data.Signed func Sign(service CryptoService, s *data.Signed, keys ...data.PublicKey) error { logrus.Debugf("sign called with %d keys", len(keys)) signatures := make([]data.Signature, 0, len(s.Signatures)+1) keyIDMemb := make(map[string]struct{}) keyIDs := make( []idPair, 0, len(keys), ) for _, key := range keys { keyID, err := utils.CanonicalKeyID(key) if err != nil { continue } keyIDMemb[key.ID()] = struct{}{} keyIDs = append(keyIDs, idPair{ scopedKeyID: key.ID(), canonicalKeyID: keyID, }) } // we need to ask the signer to sign with the canonical key ID // (ID of the TUF key's public key bytes only), but // we need to translate back to the scoped key ID (the hash of the TUF key // with the full PEM bytes) before giving the // signature back to TUF. for _, pair := range keyIDs { newSigs, err := service.Sign([]string{pair.canonicalKeyID}, s.Signed) if err != nil { return err } // we only asked to sign with 1 key ID, so there will either be 1 // or zero signatures if len(newSigs) == 1 { newSig := newSigs[0] newSig.KeyID = pair.scopedKeyID signatures = append(signatures, newSig) } } if len(signatures) < 1 { return errors.ErrInsufficientSignatures{ Name: fmt.Sprintf("Cryptoservice failed to produce any signatures for keys with IDs: %v", keyIDs), Err: nil, } } for _, sig := range s.Signatures { if _, ok := keyIDMemb[sig.KeyID]; ok { continue } signatures = append(signatures, sig) } s.Signatures = signatures return nil }
func translateDelegationsToCanonicalIDs(delegationInfo data.Delegations) ([]*data.Role, error) { canonicalDelegations := make([]*data.Role, len(delegationInfo.Roles)) copy(canonicalDelegations, delegationInfo.Roles) delegationKeys := delegationInfo.Keys for i, delegation := range canonicalDelegations { canonicalKeyIDs := []string{} for _, keyID := range delegation.KeyIDs { pubKey, ok := delegationKeys[keyID] if !ok { return nil, fmt.Errorf("Could not translate canonical key IDs for %s", delegation.Name) } canonicalKeyID, err := utils.CanonicalKeyID(pubKey) if err != nil { return nil, 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 }
// 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 { role := tr.keysDB.GetRole(roleName) if role == nil { return data.ErrInvalidRole{Role: roleName, Reason: "does not exist"} } for _, keyID := range role.KeyIDs { k := tr.keysDB.GetKey(keyID) canonicalID, err := utils.CanonicalKeyID(k) check := []string{keyID} if 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.KeyIDs} }
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 }
// delegationAdd creates a new delegation by adding a public key from a certificate to a specific role in a GUN func (d *delegationCommander) delegationAdd(cmd *cobra.Command, args []string) error { // We must have at least the gun and role name, and at least one key or path (or the --all-paths flag) to add if len(args) < 2 || len(args) < 3 && d.paths == nil && !d.allPaths { cmd.Usage() return fmt.Errorf("must specify the Global Unique Name and the role of the delegation along with the public key certificate paths and/or a list of paths to add") } config, err := d.configGetter() if err != nil { return err } gun := args[0] role := args[1] pubKeys := []data.PublicKey{} if len(args) > 2 { pubKeyPaths := args[2:] for _, pubKeyPath := range pubKeyPaths { // Read public key bytes from PEM file pubKeyBytes, err := ioutil.ReadFile(pubKeyPath) if err != nil { return fmt.Errorf("unable to read public key from file: %s", pubKeyPath) } // Parse PEM bytes into type PublicKey pubKey, err := trustmanager.ParsePEMPublicKey(pubKeyBytes) if err != nil { return fmt.Errorf("unable to parse valid public key certificate from PEM file %s: %v", pubKeyPath, err) } pubKeys = append(pubKeys, pubKey) } } for _, path := range d.paths { if path == "" { d.allPaths = true break } } // If the user passes --all-paths (or gave the "" path in --paths), give the "" path if d.allPaths { d.paths = []string{""} } trustPin, err := getTrustPinning(config) if err != nil { return err } // no online operations are performed by add so the transport argument // should be nil nRepo, err := notaryclient.NewNotaryRepository( config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, d.retriever, trustPin) if err != nil { return err } // Add the delegation to the repository err = nRepo.AddDelegation(role, pubKeys, d.paths) if err != nil { return fmt.Errorf("failed to create delegation: %v", err) } // Make keyID slice for better CLI print pubKeyIDs := []string{} for _, pubKey := range pubKeys { pubKeyID, err := utils.CanonicalKeyID(pubKey) if err != nil { return err } pubKeyIDs = append(pubKeyIDs, pubKeyID) } cmd.Println("") addingItems := "" if len(pubKeyIDs) > 0 { addingItems = addingItems + fmt.Sprintf("with keys %s, ", pubKeyIDs) } if d.paths != nil || d.allPaths { addingItems = addingItems + fmt.Sprintf("with paths [%s], ", prettyPrintPaths(d.paths)) } cmd.Printf( "Addition of delegation role %s %sto repository \"%s\" staged for next publish.\n", role, addingItems, gun) cmd.Println("") return nil }
// Sign takes a data.Signed and a key, calculated and adds the signature // to the data.Signed func Sign(service CryptoService, s *data.Signed, keys ...data.PublicKey) error { logrus.Debugf("sign called with %d keys", len(keys)) signatures := make([]data.Signature, 0, len(s.Signatures)+1) signingKeyIDs := make(map[string]struct{}) ids := make([]string, 0, len(keys)) privKeys := make(map[string]data.PrivateKey) // Get all the private key objects related to the public keys for _, key := range keys { canonicalID, err := utils.CanonicalKeyID(key) ids = append(ids, canonicalID) if err != nil { continue } k, _, err := service.GetPrivateKey(canonicalID) if err != nil { continue } privKeys[key.ID()] = k } // Check to ensure we have at least one signing key if len(privKeys) == 0 { return ErrNoKeys{KeyIDs: ids} } // Do signing and generate list of signatures for keyID, pk := range privKeys { sig, err := pk.Sign(rand.Reader, s.Signed, nil) if err != nil { logrus.Debugf("Failed to sign with key: %s. Reason: %v", keyID, err) continue } signingKeyIDs[keyID] = struct{}{} signatures = append(signatures, data.Signature{ KeyID: keyID, Method: pk.SignatureAlgorithm(), Signature: sig[:], }) } // Check we produced at least on signature if len(signatures) < 1 { return ErrInsufficientSignatures{ Name: fmt.Sprintf( "cryptoservice failed to produce any signatures for keys with IDs: %v", ids), } } for _, sig := range s.Signatures { if _, ok := signingKeyIDs[sig.KeyID]; ok { // key is in the set of key IDs for which a signature has been created continue } signatures = append(signatures, sig) } s.Signatures = signatures return nil }
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 } r, _, err := repo.GetDelegation(c.Scope()) if _, ok := err.(data.ErrNoSuchRole); err != nil && !ok { // error that wasn't ErrNoSuchRole return err } if err == nil { // role existed, attempt to merge paths and keys if err := r.AddPaths(td.AddPaths); err != nil { return err } return repo.UpdateDelegations(r, td.AddKeys) } // create brand new role r, err = td.ToNewRole(c.Scope()) if err != nil { return err } return repo.UpdateDelegations(r, td.AddKeys) case changelist.ActionUpdate: td := changelist.TufDelegation{} err := json.Unmarshal(c.Content(), &td) if err != nil { return err } r, keys, err := repo.GetDelegation(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 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]) } // If we specify the only keys left delete the role, else just delete specified keys if strings.Join(r.KeyIDs, ";") == strings.Join(removeTUFKeyIDs, ";") && len(td.AddKeys) == 0 { r := data.Role{Name: c.Scope()} return repo.DeleteDelegation(r) } // if we aren't deleting and the role exists, merge if err := r.AddPaths(td.AddPaths); err != nil { return err } // Clear all paths if we're given the flag, else remove specified paths if td.ClearAllPaths { r.RemovePaths(r.Paths) } else { r.RemovePaths(td.RemovePaths) } r.RemoveKeys(removeTUFKeyIDs) return repo.UpdateDelegations(r, td.AddKeys) case changelist.ActionDelete: r := data.Role{Name: c.Scope()} return repo.DeleteDelegation(r) default: return fmt.Errorf("unsupported action against delegations: %s", c.Action()) } }
// PurgeDelegationKeys removes the provided canonical key IDs from all delegations // present in the subtree rooted at role. The role argument must be provided in a wildcard // format, i.e. targets/* would remove the key from all delegations in the repo func (tr *Repo) PurgeDelegationKeys(role string, removeKeys []string) error { if !data.IsWildDelegation(role) { return data.ErrInvalidRole{ Role: role, Reason: "only wildcard roles can be used in a purge", } } removeIDs := make(map[string]struct{}) for _, id := range removeKeys { removeIDs[id] = struct{}{} } start := path.Dir(role) tufIDToCanon := make(map[string]string) purgeKeys := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} { var ( deleteCandidates []string err error ) for id, key := range tgt.Signed.Delegations.Keys { var ( canonID string ok bool ) if canonID, ok = tufIDToCanon[id]; !ok { canonID, err = utils.CanonicalKeyID(key) if err != nil { return err } tufIDToCanon[id] = canonID } if _, ok := removeIDs[canonID]; ok { deleteCandidates = append(deleteCandidates, id) } } if len(deleteCandidates) == 0 { // none of the interesting keys were present. We're done with this role return nil } // now we know there are changes, check if we'll be able to sign them in if err := tr.VerifyCanSign(validRole.Name); err != nil { logrus.Warnf( "role %s contains keys being purged but you do not have the necessary keys present to sign it; keys will not be purged from %s or its immediate children", validRole.Name, validRole.Name, ) return nil } // we know we can sign in the changes, delete the keys for _, id := range deleteCandidates { delete(tgt.Signed.Delegations.Keys, id) } // delete candidate keys from all roles. for _, role := range tgt.Signed.Delegations.Roles { role.RemoveKeys(deleteCandidates) if len(role.KeyIDs) < role.Threshold { logrus.Warnf("role %s has fewer keys than its threshold of %d; it will not be usable until keys are added to it", role.Name, role.Threshold) } } tgt.Dirty = true return nil } return tr.WalkTargets("", start, purgeKeys) }
// Initialize repo and test publishing targets with delegation roles func TestClientDelegationsPublishing(t *testing.T) { setUp(t) tempDir := tempDirWithConfig(t, "{}") defer os.RemoveAll(tempDir) server := setupServer() defer server.Close() // Setup certificate for delegation role tempFile, err := ioutil.TempFile("", "pemfile") assert.NoError(t, err) privKey, err := trustmanager.GenerateRSAKey(rand.Reader, 2048) assert.NoError(t, err) privKeyBytesNoRole, err := trustmanager.KeyToPEM(privKey, "") assert.NoError(t, err) privKeyBytesWithRole, err := trustmanager.KeyToPEM(privKey, "user") assert.NoError(t, err) startTime := time.Now() endTime := startTime.AddDate(10, 0, 0) cert, err := cryptoservice.GenerateCertificate(privKey, "gun", startTime, endTime) assert.NoError(t, err) _, err = tempFile.Write(trustmanager.CertToPEM(cert)) assert.NoError(t, err) tempFile.Close() defer os.Remove(tempFile.Name()) rawPubBytes, _ := ioutil.ReadFile(tempFile.Name()) parsedPubKey, _ := trustmanager.ParsePEMPublicKey(rawPubBytes) canonicalKeyID, err := utils.CanonicalKeyID(parsedPubKey) assert.NoError(t, err) // Set up targets for publishing tempTargetFile, err := ioutil.TempFile("", "targetfile") assert.NoError(t, err) tempTargetFile.Close() defer os.Remove(tempTargetFile.Name()) var target = "sdgkadga" var output string // init repo _, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - none yet output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, "No delegations present in this repository.") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // validate that we have all keys, including snapshot assertNumKeys(t, tempDir, 1, 2, true) // rotate the snapshot key to server output, err = runCommand(t, tempDir, "-s", server.URL, "key", "rotate", "gun", "-r", "--key-type", "snapshot") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // validate that we lost the snapshot signing key _, signingKeyIDs := assertNumKeys(t, tempDir, 1, 1, true) targetKeyID := signingKeyIDs[0] // add new valid delegation with single new cert output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/releases", tempFile.Name(), "--paths", "\"\"") assert.NoError(t, err) assert.Contains(t, output, "Addition of delegation role") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see our one delegation output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.NotContains(t, output, "No delegations present in this repository.") // remove the targets key to demonstrate that delegates don't need this key keyDir := filepath.Join(tempDir, "private", "tuf_keys") assert.NoError(t, os.Remove(filepath.Join(keyDir, "gun", targetKeyID+".key"))) // Note that we need to use the canonical key ID, followed by the base of the role here err = ioutil.WriteFile(filepath.Join(keyDir, canonicalKeyID+"_releases.key"), privKeyBytesNoRole, 0700) assert.NoError(t, err) // add a target using the delegation -- will only add to targets/releases _, err = runCommand(t, tempDir, "add", "gun", target, tempTargetFile.Name(), "--roles", "targets/releases") assert.NoError(t, err) // list targets for targets/releases - we should see no targets until we publish output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases") assert.NoError(t, err) assert.Contains(t, output, "No targets") output, err = runCommand(t, tempDir, "-s", server.URL, "status", "gun") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list targets for targets/releases - we should see our target! output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases") assert.NoError(t, err) assert.Contains(t, output, "targets/releases") // remove the target for this role only _, err = runCommand(t, tempDir, "remove", "gun", target, "--roles", "targets/releases") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list targets for targets/releases - we should see no targets output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases") assert.NoError(t, err) assert.Contains(t, output, "No targets present") // Try adding a target with a different key style - private/tuf_keys/canonicalKeyID.key with "user" set as the "role" PEM header // First remove the old key and add the new style assert.NoError(t, os.Remove(filepath.Join(keyDir, canonicalKeyID+"_releases.key"))) err = ioutil.WriteFile(filepath.Join(keyDir, canonicalKeyID+".key"), privKeyBytesWithRole, 0700) assert.NoError(t, err) // add a target using the delegation -- will only add to targets/releases _, err = runCommand(t, tempDir, "add", "gun", target, tempTargetFile.Name(), "--roles", "targets/releases") assert.NoError(t, err) // list targets for targets/releases - we should see no targets until we publish output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases") assert.NoError(t, err) assert.Contains(t, output, "No targets") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list targets for targets/releases - we should see our target! output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases") assert.NoError(t, err) assert.Contains(t, output, "targets/releases") // remove the target for this role only _, err = runCommand(t, tempDir, "remove", "gun", target, "--roles", "targets/releases") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // Now remove this key, and make a new file to import the delegation's key from assert.NoError(t, os.Remove(filepath.Join(keyDir, canonicalKeyID+".key"))) tempPrivFile, err := ioutil.TempFile("/tmp", "privfile") assert.NoError(t, err) defer os.Remove(tempPrivFile.Name()) // Write the private key to a file so we can import it _, err = tempPrivFile.Write(privKeyBytesNoRole) assert.NoError(t, err) tempPrivFile.Close() // Import the private key, associating it with our delegation role _, err = runCommand(t, tempDir, "key", "import", tempPrivFile.Name(), "--role", "targets/releases") assert.NoError(t, err) // add a target using the delegation -- will only add to targets/releases _, err = runCommand(t, tempDir, "add", "gun", target, tempTargetFile.Name(), "--roles", "targets/releases") assert.NoError(t, err) // list targets for targets/releases - we should see no targets until we publish output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases") assert.NoError(t, err) assert.Contains(t, output, "No targets") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list targets for targets/releases - we should see our target! output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases") assert.NoError(t, err) assert.Contains(t, output, "targets/releases") }
// Initialize repo and test delegations commands by adding, listing, and removing delegations func TestClientDelegationsInteraction(t *testing.T) { setUp(t) tempDir := tempDirWithConfig(t, "{}") defer os.RemoveAll(tempDir) server := setupServer() defer server.Close() // Setup certificate tempFile, err := ioutil.TempFile("", "pemfile") assert.NoError(t, err) privKey, err := trustmanager.GenerateECDSAKey(rand.Reader) startTime := time.Now() endTime := startTime.AddDate(10, 0, 0) cert, err := cryptoservice.GenerateCertificate(privKey, "gun", startTime, endTime) assert.NoError(t, err) _, err = tempFile.Write(trustmanager.CertToPEM(cert)) assert.NoError(t, err) tempFile.Close() defer os.Remove(tempFile.Name()) rawPubBytes, _ := ioutil.ReadFile(tempFile.Name()) parsedPubKey, _ := trustmanager.ParsePEMPublicKey(rawPubBytes) keyID, err := utils.CanonicalKeyID(parsedPubKey) assert.NoError(t, err) var output string // -- tests -- // init repo _, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - none yet output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, "No delegations present in this repository.") // add new valid delegation with single new cert, and no path output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/delegation", tempFile.Name()) assert.NoError(t, err) assert.Contains(t, output, "Addition of delegation role") assert.NotContains(t, output, "path") // check status - see delegation output, err = runCommand(t, tempDir, "status", "gun") assert.NoError(t, err) assert.Contains(t, output, "Unpublished changes for gun") // list delegations - none yet because still unpublished output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, "No delegations present in this repository.") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // check status - no changelist output, err = runCommand(t, tempDir, "status", "gun") assert.NoError(t, err) assert.Contains(t, output, "No unpublished changes for gun") // list delegations - we should see our added delegation, with no paths output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, "targets/delegation") assert.Contains(t, output, keyID) assert.NotContains(t, output, "\"\"") // add all paths to this delegation output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/delegation", "--all-paths") assert.NoError(t, err) assert.Contains(t, output, "Addition of delegation role") assert.Contains(t, output, "\"\"") assert.Contains(t, output, "<all paths>") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see our added delegation, with no paths output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, "targets/delegation") assert.Contains(t, output, "\"\"") assert.Contains(t, output, "<all paths>") // Setup another certificate tempFile2, err := ioutil.TempFile("", "pemfile2") assert.NoError(t, err) privKey, err = trustmanager.GenerateECDSAKey(rand.Reader) startTime = time.Now() endTime = startTime.AddDate(10, 0, 0) cert, err = cryptoservice.GenerateCertificate(privKey, "gun", startTime, endTime) assert.NoError(t, err) _, err = tempFile2.Write(trustmanager.CertToPEM(cert)) assert.NoError(t, err) assert.NoError(t, err) tempFile2.Close() defer os.Remove(tempFile2.Name()) rawPubBytes2, _ := ioutil.ReadFile(tempFile2.Name()) parsedPubKey2, _ := trustmanager.ParsePEMPublicKey(rawPubBytes2) keyID2, err := utils.CanonicalKeyID(parsedPubKey2) assert.NoError(t, err) // add to the delegation by specifying the same role, this time add a scoped path output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/delegation", tempFile2.Name(), "--paths", "path") assert.NoError(t, err) assert.Contains(t, output, "Addition of delegation role") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see two keys output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, ",") assert.Contains(t, output, "path") assert.Contains(t, output, keyID) assert.Contains(t, output, keyID2) // remove the delegation's first key output, err = runCommand(t, tempDir, "delegation", "remove", "gun", "targets/delegation", keyID) assert.NoError(t, err) assert.Contains(t, output, "Removal of delegation role") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see the delegation but with only the second key output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.NotContains(t, output, keyID) assert.Contains(t, output, keyID2) // remove the delegation's second key output, err = runCommand(t, tempDir, "delegation", "remove", "gun", "targets/delegation", keyID2) assert.NoError(t, err) assert.Contains(t, output, "Removal of delegation role") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see no delegations output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, "No delegations present in this repository.") // add delegation with multiple certs and multiple paths output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/delegation", tempFile.Name(), tempFile2.Name(), "--paths", "path1,path2") assert.NoError(t, err) assert.Contains(t, output, "Addition of delegation role") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see two keys output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, ",") assert.Contains(t, output, "path1,path2") assert.Contains(t, output, keyID) assert.Contains(t, output, keyID2) // add delegation with multiple certs and multiple paths output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/delegation", "--paths", "path3") assert.NoError(t, err) assert.Contains(t, output, "Addition of delegation role") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see two keys output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, ",") assert.Contains(t, output, "path1,path2,path3") // just remove two paths from this delegation output, err = runCommand(t, tempDir, "delegation", "remove", "gun", "targets/delegation", "--paths", "path2,path3") assert.NoError(t, err) assert.Contains(t, output, "Removal of delegation role") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see the same two keys, and only path1 output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, ",") assert.Contains(t, output, "path1") assert.NotContains(t, output, "path2") assert.NotContains(t, output, "path3") assert.Contains(t, output, keyID) assert.Contains(t, output, keyID2) // remove the remaining path, should not remove the delegation entirely output, err = runCommand(t, tempDir, "delegation", "remove", "gun", "targets/delegation", "--paths", "path1") assert.NoError(t, err) assert.Contains(t, output, "Removal of delegation role") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see the same two keys, and no paths output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, ",") assert.NotContains(t, output, "path1") assert.NotContains(t, output, "path2") assert.NotContains(t, output, "path3") assert.Contains(t, output, keyID) assert.Contains(t, output, keyID2) // Add a bunch of individual paths so we can test a delegation remove --all-paths output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/delegation", "--paths", "abcdef,123456") assert.NoError(t, err) // Add more individual paths so we can test a delegation remove --all-paths output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/delegation", "--paths", "banana/split,apple/crumble/pie,orange.peel,kiwi") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see all of our paths output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, "abcdef") assert.Contains(t, output, "123456") assert.Contains(t, output, "banana/split") assert.Contains(t, output, "apple/crumble/pie") assert.Contains(t, output, "orange.peel") assert.Contains(t, output, "kiwi") // Try adding "", and check that adding it with other paths clears out the others output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/delegation", "--paths", "\"\",grapefruit,pomegranate") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see all of our old paths, and "" output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, "abcdef") assert.Contains(t, output, "123456") assert.Contains(t, output, "banana/split") assert.Contains(t, output, "apple/crumble/pie") assert.Contains(t, output, "orange.peel") assert.Contains(t, output, "kiwi") assert.Contains(t, output, "\"\"") assert.NotContains(t, output, "grapefruit") assert.NotContains(t, output, "pomegranate") // Try removing just "" output, err = runCommand(t, tempDir, "delegation", "remove", "gun", "targets/delegation", "--paths", "\"\"") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see all of our old paths without "" output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, "abcdef") assert.Contains(t, output, "123456") assert.Contains(t, output, "banana/split") assert.Contains(t, output, "apple/crumble/pie") assert.Contains(t, output, "orange.peel") assert.Contains(t, output, "kiwi") assert.NotContains(t, output, "\"\"") // Remove --all-paths to clear out all paths from this delegation output, err = runCommand(t, tempDir, "delegation", "remove", "gun", "targets/delegation", "--all-paths") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see all of our paths output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.NotContains(t, output, "abcdef") assert.NotContains(t, output, "123456") assert.NotContains(t, output, "banana/split") assert.NotContains(t, output, "apple/crumble/pie") assert.NotContains(t, output, "orange.peel") assert.NotContains(t, output, "kiwi") // Check that we ignore other --paths if we pass in --all-paths on an add output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/delegation", "--all-paths", "--paths", "grapefruit,pomegranate") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should only see "", and not the other paths specified output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, "\"\"") assert.NotContains(t, output, "grapefruit") assert.NotContains(t, output, "pomegranate") // Add those extra paths we ignored to set up the next test output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/delegation", "--paths", "grapefruit,pomegranate") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // Check that we ignore other --paths if we pass in --all-paths on a remove output, err = runCommand(t, tempDir, "delegation", "remove", "gun", "targets/delegation", "--all-paths", "--paths", "pomegranate") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see no paths output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.NotContains(t, output, "\"\"") assert.NotContains(t, output, "grapefruit") assert.NotContains(t, output, "pomegranate") // remove by force to delete the delegation entirely output, err = runCommand(t, tempDir, "delegation", "remove", "gun", "targets/delegation", "-y") assert.NoError(t, err) assert.Contains(t, output, "Forced removal (including all keys and paths) of delegation role") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see no delegations output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, "No delegations present in this repository.") }
// Sign takes a data.Signed and a cryptoservice containing private keys, // calculates and adds at least minSignature signatures using signingKeys the // data.Signed. It will also clean up any signatures that are not in produced // by either a signingKey or an otherWhitelistedKey. // Note that in most cases, otherWhitelistedKeys should probably be null. They // are for keys you don't want to sign with, but you also don't want to remove // existing signatures by those keys. For instance, if you want to call Sign // multiple times with different sets of signing keys without undoing removing // signatures produced by the previous call to Sign. func Sign(service CryptoService, s *data.Signed, signingKeys []data.PublicKey, minSignatures int, otherWhitelistedKeys []data.PublicKey) error { logrus.Debugf("sign called with %d/%d required keys", minSignatures, len(signingKeys)) signatures := make([]data.Signature, 0, len(s.Signatures)+1) signingKeyIDs := make(map[string]struct{}) tufIDs := make(map[string]data.PublicKey) privKeys := make(map[string]data.PrivateKey) // Get all the private key objects related to the public keys missingKeyIDs := []string{} for _, key := range signingKeys { canonicalID, err := utils.CanonicalKeyID(key) tufIDs[key.ID()] = key if err != nil { return err } k, _, err := service.GetPrivateKey(canonicalID) if err != nil { if _, ok := err.(trustmanager.ErrKeyNotFound); ok { missingKeyIDs = append(missingKeyIDs, canonicalID) continue } return err } privKeys[key.ID()] = k } // include the list of otherWhitelistedKeys for _, key := range otherWhitelistedKeys { if _, ok := tufIDs[key.ID()]; !ok { tufIDs[key.ID()] = key } } // Check to ensure we have enough signing keys if len(privKeys) < minSignatures { return ErrInsufficientSignatures{FoundKeys: len(privKeys), NeededKeys: minSignatures, MissingKeyIDs: missingKeyIDs} } emptyStruct := struct{}{} // Do signing and generate list of signatures for keyID, pk := range privKeys { sig, err := pk.Sign(rand.Reader, *s.Signed, nil) if err != nil { logrus.Debugf("Failed to sign with key: %s. Reason: %v", keyID, err) return err } signingKeyIDs[keyID] = emptyStruct signatures = append(signatures, data.Signature{ KeyID: keyID, Method: pk.SignatureAlgorithm(), Signature: sig[:], }) } for _, sig := range s.Signatures { if _, ok := signingKeyIDs[sig.KeyID]; ok { // key is in the set of key IDs for which a signature has been created continue } var ( k data.PublicKey ok bool ) if k, ok = tufIDs[sig.KeyID]; !ok { // key is no longer a valid signing key continue } if err := VerifySignature(*s.Signed, &sig, k); err != nil { // signature is no longer valid continue } // keep any signatures that still represent valid keys and are // themselves valid signatures = append(signatures, sig) } s.Signatures = signatures return nil }
// delegationAdd creates a new delegation by adding a public key from a certificate to a specific role in a GUN func (d *delegationCommander) delegationAdd(cmd *cobra.Command, args []string) error { // We must have at least the gun and role name, and at least one key or path (or the --all-paths flag) to add if len(args) < 2 || len(args) < 3 && d.paths == nil && !d.allPaths { cmd.Usage() return fmt.Errorf("must specify the Global Unique Name and the role of the delegation along with the public key certificate paths and/or a list of paths to add") } config, err := d.configGetter() if err != nil { return err } gun := args[0] role := args[1] pubKeys, err := ingestPublicKeys(args) if err != nil { return err } checkAllPaths(d) trustPin, err := getTrustPinning(config) if err != nil { return err } // no online operations are performed by add so the transport argument // should be nil nRepo, err := notaryclient.NewFileCachedNotaryRepository( config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, d.retriever, trustPin) if err != nil { return err } // Add the delegation to the repository err = nRepo.AddDelegation(role, pubKeys, d.paths) if err != nil { return fmt.Errorf("failed to create delegation: %v", err) } // Make keyID slice for better CLI print pubKeyIDs := []string{} for _, pubKey := range pubKeys { pubKeyID, err := utils.CanonicalKeyID(pubKey) if err != nil { return err } pubKeyIDs = append(pubKeyIDs, pubKeyID) } cmd.Println("") addingItems := "" if len(pubKeyIDs) > 0 { addingItems = addingItems + fmt.Sprintf("with keys %s, ", pubKeyIDs) } if d.paths != nil || d.allPaths { addingItems = addingItems + fmt.Sprintf( "with paths [%s], ", strings.Join(prettyPaths(d.paths), "\n"), ) } cmd.Printf( "Addition of delegation role %s %sto repository \"%s\" staged for next publish.\n", role, addingItems, gun) cmd.Println("") return maybeAutoPublish(cmd, d.autoPublish, gun, config, d.retriever) }