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} err = doWithRoleFallback(c.Scope(), func(role string) error { _, e := repo.AddTargets(role, files) return e }) if err != nil { logrus.Errorf("couldn't add target to %s: %s", c.Scope(), err.Error()) } case changelist.ActionDelete: logrus.Debug("changelist remove: ", c.Path()) err = doWithRoleFallback(c.Scope(), func(role string) error { return repo.RemoveTargets(role, c.Path()) }) if err != nil { logrus.Errorf("couldn't remove target from %s: %s", c.Scope(), err.Error()) } default: logrus.Debug("action not yet supported: ", c.Action()) } return err }
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: logrus.Debug("action not yet supported: ", c.Action()) } return err }
func validateSnapshot(role string, oldSnap *data.SignedSnapshot, snapUpdate storage.MetaUpdate, roles map[string]storage.MetaUpdate, repo *tuf.Repo) error { s := &data.Signed{} err := json.Unmarshal(snapUpdate.Data, s) if err != nil { return errors.New("could not parse snapshot") } // version specifically gets validated when writing to store to // better handle race conditions there. snapshotRole, err := repo.GetBaseRole(role) if err != nil { return err } if err := signed.Verify(s, snapshotRole, 0); err != nil { return err } snap, err := data.SnapshotFromSigned(s) if err != nil { return errors.New("could not parse snapshot") } if !data.ValidTUFType(snap.Signed.Type, data.CanonicalSnapshotRole) { return errors.New("snapshot has wrong type") } err = checkSnapshotEntries(role, oldSnap, snap, roles) if err != nil { return err } return nil }
func witnessTargets(repo *tuf.Repo, invalid *tuf.Repo, role string) error { if r, ok := repo.Targets[role]; ok { // role is already valid, mark for re-signing/updating r.Dirty = true return nil } if roleObj, err := repo.GetDelegationRole(role); err == nil && invalid != nil { // A role with a threshold > len(keys) is technically invalid, but we let it build in the builder because // we want to be able to download the role (which may still have targets on it), add more keys, and then // witness the role, thus bringing it back to valid. However, if no keys have been added before witnessing, // then it is still an invalid role, and can't be witnessed because nothing can bring it back to valid. if roleObj.Threshold > len(roleObj.Keys) { return data.ErrInvalidRole{ Role: role, Reason: "role does not specify enough valid signing keys to meet its required threshold", } } if r, ok := invalid.Targets[role]; ok { // role is recognized but invalid, move to valid data and mark for re-signing repo.Targets[role] = r r.Dirty = true return nil } } // role isn't recognized, even as invalid return data.ErrInvalidRole{ Role: role, Reason: "this role is not known", } }
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 return data.ErrInvalidRole{ Role: c.Scope(), Reason: "cannot create a role that already exists", } } // role doesn't exist, create brand new 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, err := repo.GetDelegation(c.Scope()) if err != nil { return err } // role exists, merge if err := r.AddPaths(td.AddPaths); err != nil { return err } if err := r.AddPathHashPrefixes(td.AddPathHashPrefixes); err != nil { return err } r.RemoveKeys(td.RemoveKeys) r.RemovePaths(td.RemovePaths) r.RemovePathHashPrefixes(td.RemovePathHashPrefixes) 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()) } }
func loadTargetsFromStore(gun, role string, repo *tuf.Repo, store storage.MetaStore) error { tgtJSON, err := store.GetCurrent(gun, role) if err != nil { return err } t := &data.SignedTargets{} err = json.Unmarshal(tgtJSON, t) if err != nil { return err } return repo.SetTargets(role, t) }
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 }
// AddTarget generates a fake target and adds it to a repo. func AddTarget(role string, r *tuf.Repo) (name string, meta data.FileMeta, content []byte, err error) { randness := fuzz.Continue{} content = RandomByteSlice(1024) name = randness.RandString() t := data.FileMeta{ Length: int64(len(content)), Hashes: data.Hashes{ "sha256": utils.DoHash("sha256", content), "sha512": utils.DoHash("sha512", content), }, } files := data.Files{name: t} _, err = r.AddTargets(role, files) return }
func copyTimestampKey(t *testing.T, fromRepo *tuf.Repo, toStore storage.MetaStore, gun string) { role, err := fromRepo.GetBaseRole(data.CanonicalTimestampRole) assert.NoError(t, err) assert.NotNil(t, role, "No timestamp role in the root file") assert.Len(t, role.ListKeyIDs(), 1, fmt.Sprintf( "Expected 1 timestamp key in timestamp role, got %d", len(role.ListKeyIDs()))) pubTimestampKey := role.ListKeys()[0] err = toStore.SetKey(gun, data.CanonicalTimestampRole, pubTimestampKey.Algorithm(), pubTimestampKey.Public()) assert.NoError(t, err) }
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: logrus.Debug("action not yet supported for root: ", c.Action()) } return nil }
// SignAndSerialize calls Sign and then Serialize to get the repo metadata out func SignAndSerialize(tufRepo *tuf.Repo) (map[string][]byte, error) { meta := make(map[string][]byte) for delgName := range tufRepo.Targets { // we'll sign targets later if delgName == data.CanonicalTargetsRole { continue } signedThing, err := tufRepo.SignTargets(delgName, data.DefaultExpires("targets")) if err != nil { return nil, err } metaBytes, err := json.MarshalCanonical(signedThing) if err != nil { return nil, err } meta[delgName] = metaBytes } // these need to be generated after the delegations are created and signed so // the snapshot will have the delegation metadata rs, tgs, ss, ts, err := Sign(tufRepo) if err != nil { return nil, err } rf, tgf, sf, tf, err := Serialize(rs, tgs, ss, ts) if err != nil { return nil, err } meta[data.CanonicalRootRole] = rf meta[data.CanonicalSnapshotRole] = sf meta[data.CanonicalTargetsRole] = tgf meta[data.CanonicalTimestampRole] = tf return meta, nil }
func applyTargetsChange(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} _, 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 } return nil }
func validateTargets(role string, roles map[string]storage.MetaUpdate, repo *tuf.Repo) (*data.SignedTargets, error) { // TODO: when delegations are being validated, validate parent // role exists for any delegation s := &data.Signed{} err := json.Unmarshal(roles[role].Data, s) if err != nil { return nil, fmt.Errorf("could not parse %s", role) } // version specifically gets validated when writing to store to // better handle race conditions there. var targetOrDelgRole data.BaseRole if role == data.CanonicalTargetsRole { targetOrDelgRole, err = repo.GetBaseRole(role) if err != nil { logrus.Debugf("no %s role loaded", role) return nil, err } } else { delgRole, err := repo.GetDelegationRole(role) if err != nil { logrus.Debugf("no %s delegation role loaded", role) return nil, err } targetOrDelgRole = delgRole.BaseRole } if err := signed.Verify(s, targetOrDelgRole, 0); err != nil { return nil, err } t, err := data.TargetsFromSigned(s) if err != nil { return nil, err } if !data.ValidTUFType(t.Signed.Type, data.CanonicalTargetsRole) { return nil, fmt.Errorf("%s has wrong type", role) } return t, nil }
// Sign signs all top level roles in a repo in the appropriate order func Sign(repo *tuf.Repo) (root, targets, snapshot, timestamp *data.Signed, err error) { root, err = repo.SignRoot(data.DefaultExpires("root")) if err != nil { return nil, nil, nil, nil, err } targets, err = repo.SignTargets("targets", data.DefaultExpires("targets")) if err != nil { return nil, nil, nil, nil, err } snapshot, err = repo.SignSnapshot(data.DefaultExpires("snapshot")) if err != nil { return nil, nil, nil, nil, err } timestamp, err = repo.SignTimestamp(data.DefaultExpires("timestamp")) if err != nil { return nil, nil, nil, nil, err } return }
// Sign signs all top level roles in a repo in the appropriate order func Sign(repo *tuf.Repo) (root, targets, snapshot, timestamp *data.Signed, err error) { root, err = repo.SignRoot(data.DefaultExpires("root")) if _, ok := err.(data.ErrInvalidRole); err != nil && !ok { return nil, nil, nil, nil, err } targets, err = repo.SignTargets("targets", data.DefaultExpires("targets")) if _, ok := err.(data.ErrInvalidRole); err != nil && !ok { return nil, nil, nil, nil, err } snapshot, err = repo.SignSnapshot(data.DefaultExpires("snapshot")) if _, ok := err.(data.ErrInvalidRole); err != nil && !ok { return nil, nil, nil, nil, err } timestamp, err = repo.SignTimestamp(data.DefaultExpires("timestamp")) if _, ok := err.(data.ErrInvalidRole); err != nil && !ok { return nil, nil, nil, nil, err } return }
// signs and serializes the metadata for a canonical role in a tuf repo to JSON func serializeCanonicalRole(tufRepo *tuf.Repo, role string) (out []byte, err error) { var s *data.Signed switch { case role == data.CanonicalRootRole: s, err = tufRepo.SignRoot(data.DefaultExpires(role)) 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) }
func generateSnapshot(gun string, repo *tuf.Repo, store storage.MetaStore) (*storage.MetaUpdate, error) { role, err := repo.GetBaseRole(data.CanonicalSnapshotRole) if err != nil { return nil, validation.ErrBadRoot{Msg: "root did not include snapshot role"} } algo, keyBytes, err := store.GetKey(gun, data.CanonicalSnapshotRole) if err != nil { return nil, validation.ErrBadHierarchy{Msg: "could not retrieve snapshot key. client must provide snapshot"} } foundK := data.NewPublicKey(algo, keyBytes) validKey := false for _, id := range role.ListKeyIDs() { if id == foundK.ID() { validKey = true break } } if !validKey { return nil, validation.ErrBadHierarchy{ Missing: data.CanonicalSnapshotRole, Msg: "no snapshot was included in update and server does not hold current snapshot key for repository"} } currentJSON, err := store.GetCurrent(gun, data.CanonicalSnapshotRole) if err != nil { if _, ok := err.(storage.ErrNotFound); !ok { return nil, validation.ErrValidation{Msg: err.Error()} } } var sn *data.SignedSnapshot if currentJSON != nil { sn = new(data.SignedSnapshot) err := json.Unmarshal(currentJSON, sn) if err != nil { return nil, validation.ErrValidation{Msg: err.Error()} } err = repo.SetSnapshot(sn) if err != nil { return nil, validation.ErrValidation{Msg: err.Error()} } } else { // this will only occurr if no snapshot has ever been created for the repository err := repo.InitSnapshot() if err != nil { return nil, validation.ErrBadSnapshot{Msg: err.Error()} } } sgnd, err := repo.SignSnapshot(data.DefaultExpires(data.CanonicalSnapshotRole)) if err != nil { return nil, validation.ErrBadSnapshot{Msg: err.Error()} } sgndJSON, err := json.Marshal(sgnd) if err != nil { return nil, validation.ErrBadSnapshot{Msg: err.Error()} } return &storage.MetaUpdate{ Role: data.CanonicalSnapshotRole, Version: repo.Snapshot.Signed.Version, Data: sgndJSON, }, 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 } // 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()) } }
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()) } }
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, err := repo.GetDelegation(c.Scope()) if err != nil { return err } // If we specify the only keys left delete the role, else just delete specified keys if strings.Join(r.KeyIDs, ";") == strings.Join(td.RemoveKeys, ";") && 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 } if err := r.AddPathHashPrefixes(td.AddPathHashPrefixes); err != nil { return err } r.RemoveKeys(td.RemoveKeys) r.RemovePaths(td.RemovePaths) r.RemovePathHashPrefixes(td.RemovePathHashPrefixes) 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()) } }