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