1package client
2
3import (
4	"encoding/json"
5	"fmt"
6	"net/http"
7	"time"
8
9	"github.com/sirupsen/logrus"
10	"github.com/theupdateframework/notary/client/changelist"
11	store "github.com/theupdateframework/notary/storage"
12	"github.com/theupdateframework/notary/tuf"
13	"github.com/theupdateframework/notary/tuf/data"
14	"github.com/theupdateframework/notary/tuf/signed"
15	"github.com/theupdateframework/notary/tuf/utils"
16)
17
18// Use this to initialize remote HTTPStores from the config settings
19func getRemoteStore(baseURL string, gun data.GUN, rt http.RoundTripper) (store.RemoteStore, error) {
20	s, err := store.NewHTTPStore(
21		baseURL+"/v2/"+gun.String()+"/_trust/tuf/",
22		"",
23		"json",
24		"key",
25		rt,
26	)
27	if err != nil {
28		return store.OfflineStore{}, err
29	}
30	return s, nil
31}
32
33func applyChangelist(repo *tuf.Repo, invalid *tuf.Repo, cl changelist.Changelist) error {
34	it, err := cl.NewIterator()
35	if err != nil {
36		return err
37	}
38	index := 0
39	for it.HasNext() {
40		c, err := it.Next()
41		if err != nil {
42			return err
43		}
44		isDel := data.IsDelegation(c.Scope()) || data.IsWildDelegation(c.Scope())
45		switch {
46		case c.Scope() == changelist.ScopeTargets || isDel:
47			err = applyTargetsChange(repo, invalid, c)
48		case c.Scope() == changelist.ScopeRoot:
49			err = applyRootChange(repo, c)
50		default:
51			return fmt.Errorf("scope not supported: %s", c.Scope().String())
52		}
53		if err != nil {
54			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())
55			return err
56		}
57		index++
58	}
59	logrus.Debugf("applied %d change(s)", index)
60	return nil
61}
62
63func applyTargetsChange(repo *tuf.Repo, invalid *tuf.Repo, c changelist.Change) error {
64	switch c.Type() {
65	case changelist.TypeTargetsTarget:
66		return changeTargetMeta(repo, c)
67	case changelist.TypeTargetsDelegation:
68		return changeTargetsDelegation(repo, c)
69	case changelist.TypeWitness:
70		return witnessTargets(repo, invalid, c.Scope())
71	default:
72		return fmt.Errorf("only target meta and delegations changes supported")
73	}
74}
75
76func changeTargetsDelegation(repo *tuf.Repo, c changelist.Change) error {
77	switch c.Action() {
78	case changelist.ActionCreate:
79		td := changelist.TUFDelegation{}
80		err := json.Unmarshal(c.Content(), &td)
81		if err != nil {
82			return err
83		}
84
85		// Try to create brand new role or update one
86		// First add the keys, then the paths.  We can only add keys and paths in this scenario
87		err = repo.UpdateDelegationKeys(c.Scope(), td.AddKeys, []string{}, td.NewThreshold)
88		if err != nil {
89			return err
90		}
91		return repo.UpdateDelegationPaths(c.Scope(), td.AddPaths, []string{}, false)
92	case changelist.ActionUpdate:
93		td := changelist.TUFDelegation{}
94		err := json.Unmarshal(c.Content(), &td)
95		if err != nil {
96			return err
97		}
98		if data.IsWildDelegation(c.Scope()) {
99			return repo.PurgeDelegationKeys(c.Scope(), td.RemoveKeys)
100		}
101
102		delgRole, err := repo.GetDelegationRole(c.Scope())
103		if err != nil {
104			return err
105		}
106
107		// We need to translate the keys from canonical ID to TUF ID for compatibility
108		canonicalToTUFID := make(map[string]string)
109		for tufID, pubKey := range delgRole.Keys {
110			canonicalID, err := utils.CanonicalKeyID(pubKey)
111			if err != nil {
112				return err
113			}
114			canonicalToTUFID[canonicalID] = tufID
115		}
116
117		removeTUFKeyIDs := []string{}
118		for _, canonID := range td.RemoveKeys {
119			removeTUFKeyIDs = append(removeTUFKeyIDs, canonicalToTUFID[canonID])
120		}
121
122		err = repo.UpdateDelegationKeys(c.Scope(), td.AddKeys, removeTUFKeyIDs, td.NewThreshold)
123		if err != nil {
124			return err
125		}
126		return repo.UpdateDelegationPaths(c.Scope(), td.AddPaths, td.RemovePaths, td.ClearAllPaths)
127	case changelist.ActionDelete:
128		return repo.DeleteDelegation(c.Scope())
129	default:
130		return fmt.Errorf("unsupported action against delegations: %s", c.Action())
131	}
132
133}
134
135func changeTargetMeta(repo *tuf.Repo, c changelist.Change) error {
136	var err error
137	switch c.Action() {
138	case changelist.ActionCreate:
139		logrus.Debug("changelist add: ", c.Path())
140		meta := &data.FileMeta{}
141		err = json.Unmarshal(c.Content(), meta)
142		if err != nil {
143			return err
144		}
145		files := data.Files{c.Path(): *meta}
146
147		// Attempt to add the target to this role
148		if _, err = repo.AddTargets(c.Scope(), files); err != nil {
149			logrus.Errorf("couldn't add target to %s: %s", c.Scope(), err.Error())
150		}
151
152	case changelist.ActionDelete:
153		logrus.Debug("changelist remove: ", c.Path())
154
155		// Attempt to remove the target from this role
156		if err = repo.RemoveTargets(c.Scope(), c.Path()); err != nil {
157			logrus.Errorf("couldn't remove target from %s: %s", c.Scope(), err.Error())
158		}
159
160	default:
161		err = fmt.Errorf("action not yet supported: %s", c.Action())
162	}
163	return err
164}
165
166func applyRootChange(repo *tuf.Repo, c changelist.Change) error {
167	var err error
168	switch c.Type() {
169	case changelist.TypeBaseRole:
170		err = applyRootRoleChange(repo, c)
171	default:
172		err = fmt.Errorf("type of root change not yet supported: %s", c.Type())
173	}
174	return err // might be nil
175}
176
177func applyRootRoleChange(repo *tuf.Repo, c changelist.Change) error {
178	switch c.Action() {
179	case changelist.ActionCreate:
180		// replaces all keys for a role
181		d := &changelist.TUFRootData{}
182		err := json.Unmarshal(c.Content(), d)
183		if err != nil {
184			return err
185		}
186		err = repo.ReplaceBaseKeys(d.RoleName, d.Keys...)
187		if err != nil {
188			return err
189		}
190	default:
191		return fmt.Errorf("action not yet supported for root: %s", c.Action())
192	}
193	return nil
194}
195
196func nearExpiry(r data.SignedCommon) bool {
197	plus6mo := time.Now().AddDate(0, 6, 0)
198	return r.Expires.Before(plus6mo)
199}
200
201func warnRolesNearExpiry(r *tuf.Repo) {
202	//get every role and its respective signed common and call nearExpiry on it
203	//Root check
204	if nearExpiry(r.Root.Signed.SignedCommon) {
205		logrus.Warn("root is nearing expiry, you should re-sign the role metadata")
206	}
207	//Targets and delegations check
208	for role, signedTOrD := range r.Targets {
209		//signedTOrD is of type *data.SignedTargets
210		if nearExpiry(signedTOrD.Signed.SignedCommon) {
211			logrus.Warn(role, " metadata is nearing expiry, you should re-sign the role metadata")
212		}
213	}
214	//Snapshot check
215	if nearExpiry(r.Snapshot.Signed.SignedCommon) {
216		logrus.Warn("snapshot is nearing expiry, you should re-sign the role metadata")
217	}
218	//do not need to worry about Timestamp, notary signer will re-sign with the timestamp key
219}
220
221// Fetches a public key from a remote store, given a gun and role
222func getRemoteKey(role data.RoleName, remote store.RemoteStore) (data.PublicKey, error) {
223	rawPubKey, err := remote.GetKey(role)
224	if err != nil {
225		return nil, err
226	}
227
228	pubKey, err := data.UnmarshalPublicKey(rawPubKey)
229	if err != nil {
230		return nil, err
231	}
232
233	return pubKey, nil
234}
235
236// Rotates a private key in a remote store and returns the public key component
237func rotateRemoteKey(role data.RoleName, remote store.RemoteStore) (data.PublicKey, error) {
238	rawPubKey, err := remote.RotateKey(role)
239	if err != nil {
240		return nil, err
241	}
242
243	pubKey, err := data.UnmarshalPublicKey(rawPubKey)
244	if err != nil {
245		return nil, err
246	}
247
248	return pubKey, nil
249}
250
251// signs and serializes the metadata for a canonical role in a TUF repo to JSON
252func serializeCanonicalRole(tufRepo *tuf.Repo, role data.RoleName, extraSigningKeys data.KeyList) (out []byte, err error) {
253	var s *data.Signed
254	switch {
255	case role == data.CanonicalRootRole:
256		s, err = tufRepo.SignRoot(data.DefaultExpires(role), extraSigningKeys)
257	case role == data.CanonicalSnapshotRole:
258		s, err = tufRepo.SignSnapshot(data.DefaultExpires(role))
259	case tufRepo.Targets[role] != nil:
260		s, err = tufRepo.SignTargets(
261			role, data.DefaultExpires(data.CanonicalTargetsRole))
262	default:
263		err = fmt.Errorf("%s not supported role to sign on the client", role)
264	}
265
266	if err != nil {
267		return
268	}
269
270	return json.Marshal(s)
271}
272
273func getAllPrivKeys(rootKeyIDs []string, cryptoService signed.CryptoService) ([]data.PrivateKey, error) {
274	if cryptoService == nil {
275		return nil, fmt.Errorf("no crypto service available to get private keys from")
276	}
277
278	privKeys := make([]data.PrivateKey, 0, len(rootKeyIDs))
279	for _, keyID := range rootKeyIDs {
280		privKey, _, err := cryptoService.GetPrivateKey(keyID)
281		if err != nil {
282			return nil, err
283		}
284		privKeys = append(privKeys, privKey)
285	}
286	if len(privKeys) == 0 {
287		var rootKeyID string
288		rootKeyList := cryptoService.ListKeys(data.CanonicalRootRole)
289		if len(rootKeyList) == 0 {
290			rootPublicKey, err := cryptoService.Create(data.CanonicalRootRole, "", data.ECDSAKey)
291			if err != nil {
292				return nil, err
293			}
294			rootKeyID = rootPublicKey.ID()
295		} else {
296			rootKeyID = rootKeyList[0]
297		}
298		privKey, _, err := cryptoService.GetPrivateKey(rootKeyID)
299		if err != nil {
300			return nil, err
301		}
302		privKeys = append(privKeys, privKey)
303	}
304
305	return privKeys, nil
306}
307