1package data 2 3import ( 4 "bytes" 5 "crypto/sha256" 6 "crypto/sha512" 7 "crypto/subtle" 8 "encoding/hex" 9 "fmt" 10 "hash" 11 "io" 12 "io/ioutil" 13 "path" 14 "strings" 15 "time" 16 17 "github.com/docker/go/canonical/json" 18 "github.com/sirupsen/logrus" 19 "github.com/theupdateframework/notary" 20) 21 22// GUN is a Globally Unique Name. It is used to identify trust collections. 23// An example usage of this is for container image repositories. 24// For example: myregistry.io/myuser/myimage 25type GUN string 26 27func (g GUN) String() string { 28 return string(g) 29} 30 31// RoleName type for specifying role 32type RoleName string 33 34func (r RoleName) String() string { 35 return string(r) 36} 37 38// Parent provides the parent path role from the provided child role 39func (r RoleName) Parent() RoleName { 40 return RoleName(path.Dir(r.String())) 41} 42 43// MetadataRoleMapToStringMap generates a map string of bytes from a map RoleName of bytes 44func MetadataRoleMapToStringMap(roles map[RoleName][]byte) map[string][]byte { 45 metadata := make(map[string][]byte) 46 for k, v := range roles { 47 metadata[k.String()] = v 48 } 49 return metadata 50} 51 52// NewRoleList generates an array of RoleName objects from a slice of strings 53func NewRoleList(roles []string) []RoleName { 54 var roleNames []RoleName 55 for _, role := range roles { 56 roleNames = append(roleNames, RoleName(role)) 57 } 58 return roleNames 59} 60 61// RolesListToStringList generates an array of string objects from a slice of roles 62func RolesListToStringList(roles []RoleName) []string { 63 var roleNames []string 64 for _, role := range roles { 65 roleNames = append(roleNames, role.String()) 66 } 67 return roleNames 68} 69 70// SigAlgorithm for types of signatures 71type SigAlgorithm string 72 73func (k SigAlgorithm) String() string { 74 return string(k) 75} 76 77const defaultHashAlgorithm = "sha256" 78 79// NotaryDefaultExpiries is the construct used to configure the default expiry times of 80// the various role files. 81var NotaryDefaultExpiries = map[RoleName]time.Duration{ 82 CanonicalRootRole: notary.NotaryRootExpiry, 83 CanonicalTargetsRole: notary.NotaryTargetsExpiry, 84 CanonicalSnapshotRole: notary.NotarySnapshotExpiry, 85 CanonicalTimestampRole: notary.NotaryTimestampExpiry, 86} 87 88// Signature types 89const ( 90 EDDSASignature SigAlgorithm = "eddsa" 91 RSAPSSSignature SigAlgorithm = "rsapss" 92 RSAPKCS1v15Signature SigAlgorithm = "rsapkcs1v15" 93 ECDSASignature SigAlgorithm = "ecdsa" 94 PyCryptoSignature SigAlgorithm = "pycrypto-pkcs#1 pss" 95) 96 97// Key types 98const ( 99 ED25519Key = "ed25519" 100 RSAKey = "rsa" 101 RSAx509Key = "rsa-x509" 102 ECDSAKey = "ecdsa" 103 ECDSAx509Key = "ecdsa-x509" 104) 105 106// TUFTypes is the set of metadata types 107var TUFTypes = map[RoleName]string{ 108 CanonicalRootRole: "Root", 109 CanonicalTargetsRole: "Targets", 110 CanonicalSnapshotRole: "Snapshot", 111 CanonicalTimestampRole: "Timestamp", 112} 113 114// ValidTUFType checks if the given type is valid for the role 115func ValidTUFType(typ string, role RoleName) bool { 116 if ValidRole(role) { 117 // All targets delegation roles must have 118 // the valid type is for targets. 119 if role == "" { 120 // role is unknown and does not map to 121 // a type 122 return false 123 } 124 if strings.HasPrefix(role.String(), CanonicalTargetsRole.String()+"/") { 125 role = CanonicalTargetsRole 126 } 127 } 128 // most people will just use the defaults so have this optimal check 129 // first. Do comparison just in case there is some unknown vulnerability 130 // if a key and value in the map differ. 131 if v, ok := TUFTypes[role]; ok { 132 return typ == v 133 } 134 return false 135} 136 137// Signed is the high level, partially deserialized metadata object 138// used to verify signatures before fully unpacking, or to add signatures 139// before fully packing 140type Signed struct { 141 Signed *json.RawMessage `json:"signed"` 142 Signatures []Signature `json:"signatures"` 143} 144 145// SignedCommon contains the fields common to the Signed component of all 146// TUF metadata files 147type SignedCommon struct { 148 Type string `json:"_type"` 149 Expires time.Time `json:"expires"` 150 Version int `json:"version"` 151} 152 153// SignedMeta is used in server validation where we only need signatures 154// and common fields 155type SignedMeta struct { 156 Signed SignedCommon `json:"signed"` 157 Signatures []Signature `json:"signatures"` 158} 159 160// Signature is a signature on a piece of metadata 161type Signature struct { 162 KeyID string `json:"keyid"` 163 Method SigAlgorithm `json:"method"` 164 Signature []byte `json:"sig"` 165 IsValid bool `json:"-"` 166} 167 168// Files is the map of paths to file meta container in targets and delegations 169// metadata files 170type Files map[string]FileMeta 171 172// Hashes is the map of hash type to digest created for each metadata 173// and target file 174type Hashes map[string][]byte 175 176// NotaryDefaultHashes contains the default supported hash algorithms. 177var NotaryDefaultHashes = []string{notary.SHA256, notary.SHA512} 178 179// FileMeta contains the size and hashes for a metadata or target file. Custom 180// data can be optionally added. 181type FileMeta struct { 182 Length int64 `json:"length"` 183 Hashes Hashes `json:"hashes"` 184 Custom *json.RawMessage `json:"custom,omitempty"` 185} 186 187// Equals returns true if the other FileMeta object is equivalent to this one 188func (f FileMeta) Equals(o FileMeta) bool { 189 if o.Length != f.Length || len(f.Hashes) != len(f.Hashes) { 190 return false 191 } 192 if f.Custom == nil && o.Custom != nil || f.Custom != nil && o.Custom == nil { 193 return false 194 } 195 // we don't care if these are valid hashes, just that they are equal 196 for key, val := range f.Hashes { 197 if !bytes.Equal(val, o.Hashes[key]) { 198 return false 199 } 200 } 201 if f.Custom == nil && o.Custom == nil { 202 return true 203 } 204 fBytes, err := f.Custom.MarshalJSON() 205 if err != nil { 206 return false 207 } 208 oBytes, err := o.Custom.MarshalJSON() 209 if err != nil { 210 return false 211 } 212 return bytes.Equal(fBytes, oBytes) 213} 214 215// CheckHashes verifies all the checksums specified by the "hashes" of the payload. 216func CheckHashes(payload []byte, name string, hashes Hashes) error { 217 cnt := 0 218 219 // k, v indicate the hash algorithm and the corresponding value 220 for k, v := range hashes { 221 switch k { 222 case notary.SHA256: 223 checksum := sha256.Sum256(payload) 224 if subtle.ConstantTimeCompare(checksum[:], v) == 0 { 225 return ErrMismatchedChecksum{alg: notary.SHA256, name: name, expected: hex.EncodeToString(v)} 226 } 227 cnt++ 228 case notary.SHA512: 229 checksum := sha512.Sum512(payload) 230 if subtle.ConstantTimeCompare(checksum[:], v) == 0 { 231 return ErrMismatchedChecksum{alg: notary.SHA512, name: name, expected: hex.EncodeToString(v)} 232 } 233 cnt++ 234 } 235 } 236 237 if cnt == 0 { 238 return ErrMissingMeta{Role: name} 239 } 240 241 return nil 242} 243 244// CompareMultiHashes verifies that the two Hashes passed in can represent the same data. 245// This means that both maps must have at least one key defined for which they map, and no conflicts. 246// Note that we check the intersection of map keys, which adds support for non-default hash algorithms in notary 247func CompareMultiHashes(hashes1, hashes2 Hashes) error { 248 // First check if the two hash structures are valid 249 if err := CheckValidHashStructures(hashes1); err != nil { 250 return err 251 } 252 if err := CheckValidHashStructures(hashes2); err != nil { 253 return err 254 } 255 // Check if they have at least one matching hash, and no conflicts 256 cnt := 0 257 for hashAlg, hash1 := range hashes1 { 258 259 hash2, ok := hashes2[hashAlg] 260 if !ok { 261 continue 262 } 263 264 if subtle.ConstantTimeCompare(hash1[:], hash2[:]) == 0 { 265 return fmt.Errorf("mismatched %s checksum", hashAlg) 266 } 267 // If we reached here, we had a match 268 cnt++ 269 } 270 271 if cnt == 0 { 272 return fmt.Errorf("at least one matching hash needed") 273 } 274 275 return nil 276} 277 278// CheckValidHashStructures returns an error, or nil, depending on whether 279// the content of the hashes is valid or not. 280func CheckValidHashStructures(hashes Hashes) error { 281 cnt := 0 282 283 for k, v := range hashes { 284 switch k { 285 case notary.SHA256: 286 if len(v) != sha256.Size { 287 return ErrInvalidChecksum{alg: notary.SHA256} 288 } 289 cnt++ 290 case notary.SHA512: 291 if len(v) != sha512.Size { 292 return ErrInvalidChecksum{alg: notary.SHA512} 293 } 294 cnt++ 295 } 296 } 297 298 if cnt == 0 { 299 return fmt.Errorf("at least one supported hash needed") 300 } 301 302 return nil 303} 304 305// NewFileMeta generates a FileMeta object from the reader, using the 306// hash algorithms provided 307func NewFileMeta(r io.Reader, hashAlgorithms ...string) (FileMeta, error) { 308 if len(hashAlgorithms) == 0 { 309 hashAlgorithms = []string{defaultHashAlgorithm} 310 } 311 hashes := make(map[string]hash.Hash, len(hashAlgorithms)) 312 for _, hashAlgorithm := range hashAlgorithms { 313 var h hash.Hash 314 switch hashAlgorithm { 315 case notary.SHA256: 316 h = sha256.New() 317 case notary.SHA512: 318 h = sha512.New() 319 default: 320 return FileMeta{}, fmt.Errorf("Unknown hash algorithm: %s", hashAlgorithm) 321 } 322 hashes[hashAlgorithm] = h 323 r = io.TeeReader(r, h) 324 } 325 n, err := io.Copy(ioutil.Discard, r) 326 if err != nil { 327 return FileMeta{}, err 328 } 329 m := FileMeta{Length: n, Hashes: make(Hashes, len(hashes))} 330 for hashAlgorithm, h := range hashes { 331 m.Hashes[hashAlgorithm] = h.Sum(nil) 332 } 333 return m, nil 334} 335 336// Delegations holds a tier of targets delegations 337type Delegations struct { 338 Keys Keys `json:"keys"` 339 Roles []*Role `json:"roles"` 340} 341 342// NewDelegations initializes an empty Delegations object 343func NewDelegations() *Delegations { 344 return &Delegations{ 345 Keys: make(map[string]PublicKey), 346 Roles: make([]*Role, 0), 347 } 348} 349 350// These values are recommended TUF expiry times. 351var defaultExpiryTimes = map[RoleName]time.Duration{ 352 CanonicalRootRole: notary.Year, 353 CanonicalTargetsRole: 90 * notary.Day, 354 CanonicalSnapshotRole: 7 * notary.Day, 355 CanonicalTimestampRole: notary.Day, 356} 357 358// SetDefaultExpiryTimes allows one to change the default expiries. 359func SetDefaultExpiryTimes(times map[RoleName]time.Duration) { 360 for key, value := range times { 361 if _, ok := defaultExpiryTimes[key]; !ok { 362 logrus.Errorf("Attempted to set default expiry for an unknown role: %s", key.String()) 363 continue 364 } 365 defaultExpiryTimes[key] = value 366 } 367} 368 369// DefaultExpires gets the default expiry time for the given role 370func DefaultExpires(role RoleName) time.Time { 371 if d, ok := defaultExpiryTimes[role]; ok { 372 return time.Now().Add(d) 373 } 374 var t time.Time 375 return t.UTC().Round(time.Second) 376} 377 378type unmarshalledSignature Signature 379 380// UnmarshalJSON does a custom unmarshalling of the signature JSON 381func (s *Signature) UnmarshalJSON(data []byte) error { 382 uSignature := unmarshalledSignature{} 383 err := json.Unmarshal(data, &uSignature) 384 if err != nil { 385 return err 386 } 387 uSignature.Method = SigAlgorithm(strings.ToLower(string(uSignature.Method))) 388 *s = Signature(uSignature) 389 return nil 390} 391