1package data
2
3import (
4	"fmt"
5	"path"
6	"regexp"
7	"strings"
8
9	"github.com/sirupsen/logrus"
10)
11
12// Canonical base role names
13var (
14	CanonicalRootRole      RoleName = "root"
15	CanonicalTargetsRole   RoleName = "targets"
16	CanonicalSnapshotRole  RoleName = "snapshot"
17	CanonicalTimestampRole RoleName = "timestamp"
18)
19
20// BaseRoles is an easy to iterate list of the top level
21// roles.
22var BaseRoles = []RoleName{
23	CanonicalRootRole,
24	CanonicalTargetsRole,
25	CanonicalSnapshotRole,
26	CanonicalTimestampRole,
27}
28
29// Regex for validating delegation names
30var delegationRegexp = regexp.MustCompile("^[-a-z0-9_/]+$")
31
32// ErrNoSuchRole indicates the roles doesn't exist
33type ErrNoSuchRole struct {
34	Role RoleName
35}
36
37func (e ErrNoSuchRole) Error() string {
38	return fmt.Sprintf("role does not exist: %s", e.Role)
39}
40
41// ErrInvalidRole represents an error regarding a role. Typically
42// something like a role for which sone of the public keys were
43// not found in the TUF repo.
44type ErrInvalidRole struct {
45	Role   RoleName
46	Reason string
47}
48
49func (e ErrInvalidRole) Error() string {
50	if e.Reason != "" {
51		return fmt.Sprintf("tuf: invalid role %s. %s", e.Role, e.Reason)
52	}
53	return fmt.Sprintf("tuf: invalid role %s.", e.Role)
54}
55
56// ValidRole only determines the name is semantically
57// correct. For target delegated roles, it does NOT check
58// the the appropriate parent roles exist.
59func ValidRole(name RoleName) bool {
60	if IsDelegation(name) {
61		return true
62	}
63
64	for _, v := range BaseRoles {
65		if name == v {
66			return true
67		}
68	}
69	return false
70}
71
72// IsDelegation checks if the role is a delegation or a root role
73func IsDelegation(role RoleName) bool {
74	strRole := role.String()
75	targetsBase := CanonicalTargetsRole + "/"
76
77	whitelistedChars := delegationRegexp.MatchString(strRole)
78
79	// Limit size of full role string to 255 chars for db column size limit
80	correctLength := len(role) < 256
81
82	// Removes ., .., extra slashes, and trailing slash
83	isClean := path.Clean(strRole) == strRole
84	return strings.HasPrefix(strRole, targetsBase.String()) &&
85		whitelistedChars &&
86		correctLength &&
87		isClean
88}
89
90// IsBaseRole checks if the role is a base role
91func IsBaseRole(role RoleName) bool {
92	for _, baseRole := range BaseRoles {
93		if role == baseRole {
94			return true
95		}
96	}
97	return false
98}
99
100// IsWildDelegation determines if a role represents a valid wildcard delegation
101// path, i.e. targets/*, targets/foo/*.
102// The wildcard may only appear as the final part of the delegation and must
103// be a whole segment, i.e. targets/foo* is not a valid wildcard delegation.
104func IsWildDelegation(role RoleName) bool {
105	if path.Clean(role.String()) != role.String() {
106		return false
107	}
108	base := role.Parent()
109	if !(IsDelegation(base) || base == CanonicalTargetsRole) {
110		return false
111	}
112	return role[len(role)-2:] == "/*"
113}
114
115// BaseRole is an internal representation of a root/targets/snapshot/timestamp role, with its public keys included
116type BaseRole struct {
117	Keys      map[string]PublicKey
118	Name      RoleName
119	Threshold int
120}
121
122// NewBaseRole creates a new BaseRole object with the provided parameters
123func NewBaseRole(name RoleName, threshold int, keys ...PublicKey) BaseRole {
124	r := BaseRole{
125		Name:      name,
126		Threshold: threshold,
127		Keys:      make(map[string]PublicKey),
128	}
129	for _, k := range keys {
130		r.Keys[k.ID()] = k
131	}
132	return r
133}
134
135// ListKeys retrieves the public keys valid for this role
136func (b BaseRole) ListKeys() KeyList {
137	return listKeys(b.Keys)
138}
139
140// ListKeyIDs retrieves the list of key IDs valid for this role
141func (b BaseRole) ListKeyIDs() []string {
142	return listKeyIDs(b.Keys)
143}
144
145// Equals returns whether this BaseRole equals another BaseRole
146func (b BaseRole) Equals(o BaseRole) bool {
147	if b.Threshold != o.Threshold || b.Name != o.Name || len(b.Keys) != len(o.Keys) {
148		return false
149	}
150
151	for keyID, key := range b.Keys {
152		oKey, ok := o.Keys[keyID]
153		if !ok || key.ID() != oKey.ID() {
154			return false
155		}
156	}
157
158	return true
159}
160
161// DelegationRole is an internal representation of a delegation role, with its public keys included
162type DelegationRole struct {
163	BaseRole
164	Paths []string
165}
166
167func listKeys(keyMap map[string]PublicKey) KeyList {
168	keys := KeyList{}
169	for _, key := range keyMap {
170		keys = append(keys, key)
171	}
172	return keys
173}
174
175func listKeyIDs(keyMap map[string]PublicKey) []string {
176	keyIDs := []string{}
177	for id := range keyMap {
178		keyIDs = append(keyIDs, id)
179	}
180	return keyIDs
181}
182
183// Restrict restricts the paths and path hash prefixes for the passed in delegation role,
184// returning a copy of the role with validated paths as if it was a direct child
185func (d DelegationRole) Restrict(child DelegationRole) (DelegationRole, error) {
186	if !d.IsParentOf(child) {
187		return DelegationRole{}, fmt.Errorf("%s is not a parent of %s", d.Name, child.Name)
188	}
189	return DelegationRole{
190		BaseRole: BaseRole{
191			Keys:      child.Keys,
192			Name:      child.Name,
193			Threshold: child.Threshold,
194		},
195		Paths: RestrictDelegationPathPrefixes(d.Paths, child.Paths),
196	}, nil
197}
198
199// IsParentOf returns whether the passed in delegation role is the direct child of this role,
200// determined by delegation name.
201// Ex: targets/a is a direct parent of targets/a/b, but targets/a is not a direct parent of targets/a/b/c
202func (d DelegationRole) IsParentOf(child DelegationRole) bool {
203	return path.Dir(child.Name.String()) == d.Name.String()
204}
205
206// CheckPaths checks if a given path is valid for the role
207func (d DelegationRole) CheckPaths(path string) bool {
208	return checkPaths(path, d.Paths)
209}
210
211func checkPaths(path string, permitted []string) bool {
212	for _, p := range permitted {
213		if strings.HasPrefix(path, p) {
214			return true
215		}
216	}
217	return false
218}
219
220// RestrictDelegationPathPrefixes returns the list of valid delegationPaths that are prefixed by parentPaths
221func RestrictDelegationPathPrefixes(parentPaths, delegationPaths []string) []string {
222	validPaths := []string{}
223	if len(delegationPaths) == 0 {
224		return validPaths
225	}
226
227	// Validate each individual delegation path
228	for _, delgPath := range delegationPaths {
229		isPrefixed := false
230		for _, parentPath := range parentPaths {
231			if strings.HasPrefix(delgPath, parentPath) {
232				isPrefixed = true
233				break
234			}
235		}
236		// If the delegation path did not match prefix against any parent path, it is not valid
237		if isPrefixed {
238			validPaths = append(validPaths, delgPath)
239		}
240	}
241	return validPaths
242}
243
244// RootRole is a cut down role as it appears in the root.json
245// Eventually should only be used for immediately before and after serialization/deserialization
246type RootRole struct {
247	KeyIDs    []string `json:"keyids"`
248	Threshold int      `json:"threshold"`
249}
250
251// Role is a more verbose role as they appear in targets delegations
252// Eventually should only be used for immediately before and after serialization/deserialization
253type Role struct {
254	RootRole
255	Name  RoleName `json:"name"`
256	Paths []string `json:"paths,omitempty"`
257}
258
259// NewRole creates a new Role object from the given parameters
260func NewRole(name RoleName, threshold int, keyIDs, paths []string) (*Role, error) {
261	if IsDelegation(name) {
262		if len(paths) == 0 {
263			logrus.Debugf("role %s with no Paths will never be able to publish content until one or more are added", name)
264		}
265	}
266	if threshold < 1 {
267		return nil, ErrInvalidRole{Role: name}
268	}
269	if !ValidRole(name) {
270		return nil, ErrInvalidRole{Role: name}
271	}
272	return &Role{
273		RootRole: RootRole{
274			KeyIDs:    keyIDs,
275			Threshold: threshold,
276		},
277		Name:  name,
278		Paths: paths,
279	}, nil
280
281}
282
283// CheckPaths checks if a given path is valid for the role
284func (r Role) CheckPaths(path string) bool {
285	return checkPaths(path, r.Paths)
286}
287
288// AddKeys merges the ids into the current list of role key ids
289func (r *Role) AddKeys(ids []string) {
290	r.KeyIDs = mergeStrSlices(r.KeyIDs, ids)
291}
292
293// AddPaths merges the paths into the current list of role paths
294func (r *Role) AddPaths(paths []string) error {
295	if len(paths) == 0 {
296		return nil
297	}
298	r.Paths = mergeStrSlices(r.Paths, paths)
299	return nil
300}
301
302// RemoveKeys removes the ids from the current list of key ids
303func (r *Role) RemoveKeys(ids []string) {
304	r.KeyIDs = subtractStrSlices(r.KeyIDs, ids)
305}
306
307// RemovePaths removes the paths from the current list of role paths
308func (r *Role) RemovePaths(paths []string) {
309	r.Paths = subtractStrSlices(r.Paths, paths)
310}
311
312func mergeStrSlices(orig, new []string) []string {
313	have := make(map[string]bool)
314	for _, e := range orig {
315		have[e] = true
316	}
317	merged := make([]string, len(orig), len(orig)+len(new))
318	copy(merged, orig)
319	for _, e := range new {
320		if !have[e] {
321			merged = append(merged, e)
322		}
323	}
324	return merged
325}
326
327func subtractStrSlices(orig, remove []string) []string {
328	kill := make(map[string]bool)
329	for _, e := range remove {
330		kill[e] = true
331	}
332	var keep []string
333	for _, e := range orig {
334		if !kill[e] {
335			keep = append(keep, e)
336		}
337	}
338	return keep
339}
340