github.com/in-toto/in-toto-golang@v0.9.1-0.20240517212500-990269f763cf/in_toto/model.go (about) 1 package in_toto 2 3 import ( 4 "context" 5 "crypto/ecdsa" 6 "crypto/rsa" 7 "crypto/x509" 8 "encoding/hex" 9 "encoding/json" 10 "errors" 11 "fmt" 12 "os" 13 "reflect" 14 "regexp" 15 "strconv" 16 "strings" 17 "time" 18 19 "github.com/secure-systems-lab/go-securesystemslib/cjson" 20 "github.com/secure-systems-lab/go-securesystemslib/dsse" 21 ) 22 23 type HashObj = map[string]string 24 25 /* 26 KeyVal contains the actual values of a key, as opposed to key metadata such as 27 a key identifier or key type. For RSA keys, the key value is a pair of public 28 and private keys in PEM format stored as strings. For public keys the Private 29 field may be an empty string. 30 */ 31 type KeyVal struct { 32 Private string `json:"private,omitempty"` 33 Public string `json:"public"` 34 Certificate string `json:"certificate,omitempty"` 35 } 36 37 /* 38 Key represents a generic in-toto key that contains key metadata, such as an 39 identifier, supported hash algorithms to create the identifier, the key type 40 and the supported signature scheme, and the actual key value. 41 */ 42 type Key struct { 43 KeyID string `json:"keyid"` 44 KeyIDHashAlgorithms []string `json:"keyid_hash_algorithms"` 45 KeyType string `json:"keytype"` 46 KeyVal KeyVal `json:"keyval"` 47 Scheme string `json:"scheme"` 48 } 49 50 // ErrEmptyKeyField will be thrown if a field in our Key struct is empty. 51 var ErrEmptyKeyField = errors.New("empty field in key") 52 53 // ErrInvalidHexString will be thrown, if a string doesn't match a hex string. 54 var ErrInvalidHexString = errors.New("invalid hex string") 55 56 // ErrSchemeKeyTypeMismatch will be thrown, if the given scheme and key type are not supported together. 57 var ErrSchemeKeyTypeMismatch = errors.New("the scheme and key type are not supported together") 58 59 // ErrUnsupportedKeyIDHashAlgorithms will be thrown, if the specified KeyIDHashAlgorithms is not supported. 60 var ErrUnsupportedKeyIDHashAlgorithms = errors.New("the given keyID hash algorithm is not supported") 61 62 // ErrKeyKeyTypeMismatch will be thrown, if the specified keyType does not match the key 63 var ErrKeyKeyTypeMismatch = errors.New("the given key does not match its key type") 64 65 // ErrNoPublicKey gets returned when the private key value is not empty. 66 var ErrNoPublicKey = errors.New("the given key is not a public key") 67 68 // ErrCurveSizeSchemeMismatch gets returned, when the scheme and curve size are incompatible 69 // for example: curve size = "521" and scheme = "ecdsa-sha2-nistp224" 70 var ErrCurveSizeSchemeMismatch = errors.New("the scheme does not match the curve size") 71 72 /* 73 matchEcdsaScheme checks if the scheme suffix, matches the ecdsa key 74 curve size. We do not need a full regex match here, because 75 our validateKey functions are already checking for a valid scheme string. 76 */ 77 func matchEcdsaScheme(curveSize int, scheme string) error { 78 if !strings.HasSuffix(scheme, strconv.Itoa(curveSize)) { 79 return ErrCurveSizeSchemeMismatch 80 } 81 return nil 82 } 83 84 /* 85 validateHexString is used to validate that a string passed to it contains 86 only valid hexadecimal characters. 87 */ 88 func validateHexString(str string) error { 89 formatCheck, _ := regexp.MatchString("^[a-fA-F0-9]+$", str) 90 if !formatCheck { 91 return fmt.Errorf("%w: %s", ErrInvalidHexString, str) 92 } 93 return nil 94 } 95 96 /* 97 validateKeyVal validates the KeyVal struct. In case of an ed25519 key, 98 it will check for a hex string for private and public key. In any other 99 case, validateKeyVal will try to decode the PEM block. If this succeeds, 100 we have a valid PEM block in our KeyVal struct. On success it will return nil 101 on failure it will return the corresponding error. This can be either 102 an ErrInvalidHexString, an ErrNoPEMBlock or an ErrUnsupportedKeyType 103 if the KeyType is unknown. 104 */ 105 func validateKeyVal(key Key) error { 106 switch key.KeyType { 107 case ed25519KeyType: 108 // We cannot use matchPublicKeyKeyType or matchPrivateKeyKeyType here, 109 // because we retrieve the key not from PEM. Hence we are dealing with 110 // plain ed25519 key bytes. These bytes can't be typechecked like in the 111 // matchKeyKeytype functions. 112 err := validateHexString(key.KeyVal.Public) 113 if err != nil { 114 return err 115 } 116 if key.KeyVal.Private != "" { 117 err := validateHexString(key.KeyVal.Private) 118 if err != nil { 119 return err 120 } 121 } 122 case rsaKeyType, ecdsaKeyType: 123 // We do not need the pemData here, so we can throw it away via '_' 124 _, parsedKey, err := decodeAndParse([]byte(key.KeyVal.Public)) 125 if err != nil { 126 return err 127 } 128 err = matchPublicKeyKeyType(parsedKey, key.KeyType) 129 if err != nil { 130 return err 131 } 132 if key.KeyVal.Private != "" { 133 // We do not need the pemData here, so we can throw it away via '_' 134 _, parsedKey, err := decodeAndParse([]byte(key.KeyVal.Private)) 135 if err != nil { 136 return err 137 } 138 err = matchPrivateKeyKeyType(parsedKey, key.KeyType) 139 if err != nil { 140 return err 141 } 142 } 143 default: 144 return ErrUnsupportedKeyType 145 } 146 return nil 147 } 148 149 /* 150 matchPublicKeyKeyType validates an interface if it can be asserted to a 151 the RSA or ECDSA public key type. We can only check RSA and ECDSA this way, 152 because we are storing them in PEM format. Ed25519 keys are stored as plain 153 ed25519 keys encoded as hex strings, thus we have no metadata for them. 154 This function will return nil on success. If the key type does not match 155 it will return an ErrKeyKeyTypeMismatch. 156 */ 157 func matchPublicKeyKeyType(key interface{}, keyType string) error { 158 switch key.(type) { 159 case *rsa.PublicKey: 160 if keyType != rsaKeyType { 161 return ErrKeyKeyTypeMismatch 162 } 163 case *ecdsa.PublicKey: 164 if keyType != ecdsaKeyType { 165 return ErrKeyKeyTypeMismatch 166 } 167 default: 168 return ErrInvalidKey 169 } 170 return nil 171 } 172 173 /* 174 matchPrivateKeyKeyType validates an interface if it can be asserted to a 175 the RSA or ECDSA private key type. We can only check RSA and ECDSA this way, 176 because we are storing them in PEM format. Ed25519 keys are stored as plain 177 ed25519 keys encoded as hex strings, thus we have no metadata for them. 178 This function will return nil on success. If the key type does not match 179 it will return an ErrKeyKeyTypeMismatch. 180 */ 181 func matchPrivateKeyKeyType(key interface{}, keyType string) error { 182 // we can only check RSA and ECDSA this way, because we are storing them in PEM 183 // format. ed25519 keys are stored as plain ed25519 keys encoded as hex strings 184 // so we have no metadata for them. 185 switch key.(type) { 186 case *rsa.PrivateKey: 187 if keyType != rsaKeyType { 188 return ErrKeyKeyTypeMismatch 189 } 190 case *ecdsa.PrivateKey: 191 if keyType != ecdsaKeyType { 192 return ErrKeyKeyTypeMismatch 193 } 194 default: 195 return ErrInvalidKey 196 } 197 return nil 198 } 199 200 /* 201 matchKeyTypeScheme checks if the specified scheme matches our specified 202 keyType. If the keyType is not supported it will return an 203 ErrUnsupportedKeyType. If the keyType and scheme do not match it will return 204 an ErrSchemeKeyTypeMismatch. If the specified keyType and scheme are 205 compatible matchKeyTypeScheme will return nil. 206 */ 207 func matchKeyTypeScheme(key Key) error { 208 switch key.KeyType { 209 case rsaKeyType: 210 for _, scheme := range getSupportedRSASchemes() { 211 if key.Scheme == scheme { 212 return nil 213 } 214 } 215 case ed25519KeyType: 216 for _, scheme := range getSupportedEd25519Schemes() { 217 if key.Scheme == scheme { 218 return nil 219 } 220 } 221 case ecdsaKeyType: 222 for _, scheme := range getSupportedEcdsaSchemes() { 223 if key.Scheme == scheme { 224 return nil 225 } 226 } 227 default: 228 return fmt.Errorf("%w: %s", ErrUnsupportedKeyType, key.KeyType) 229 } 230 return ErrSchemeKeyTypeMismatch 231 } 232 233 /* 234 validateKey checks the outer key object (everything, except the KeyVal struct). 235 It verifies the keyID for being a hex string and checks for empty fields. 236 On success it will return nil, on error it will return the corresponding error. 237 Either: ErrEmptyKeyField or ErrInvalidHexString. 238 */ 239 func validateKey(key Key) error { 240 err := validateHexString(key.KeyID) 241 if err != nil { 242 return err 243 } 244 // This probably can be done more elegant with reflection 245 // but we care about performance, do we?! 246 if key.KeyType == "" { 247 return fmt.Errorf("%w: keytype", ErrEmptyKeyField) 248 } 249 if key.KeyVal.Public == "" && key.KeyVal.Certificate == "" { 250 return fmt.Errorf("%w: keyval.public and keyval.certificate cannot both be blank", ErrEmptyKeyField) 251 } 252 if key.Scheme == "" { 253 return fmt.Errorf("%w: scheme", ErrEmptyKeyField) 254 } 255 err = matchKeyTypeScheme(key) 256 if err != nil { 257 return err 258 } 259 // only check for supported KeyIDHashAlgorithms, if the variable has been set 260 if key.KeyIDHashAlgorithms != nil { 261 supportedKeyIDHashAlgorithms := getSupportedKeyIDHashAlgorithms() 262 if !supportedKeyIDHashAlgorithms.IsSubSet(NewSet(key.KeyIDHashAlgorithms...)) { 263 return fmt.Errorf("%w: %#v, supported are: %#v", ErrUnsupportedKeyIDHashAlgorithms, key.KeyIDHashAlgorithms, getSupportedKeyIDHashAlgorithms()) 264 } 265 } 266 return nil 267 } 268 269 /* 270 validatePublicKey is a wrapper around validateKey. It test if the private key 271 value in the key is empty and then validates the key via calling validateKey. 272 On success it will return nil, on error it will return an ErrNoPublicKey error. 273 */ 274 func validatePublicKey(key Key) error { 275 if key.KeyVal.Private != "" { 276 return ErrNoPublicKey 277 } 278 err := validateKey(key) 279 if err != nil { 280 return err 281 } 282 return nil 283 } 284 285 /* 286 Signature represents a generic in-toto signature that contains the identifier 287 of the Key, which was used to create the signature and the signature data. The 288 used signature scheme is found in the corresponding Key. 289 */ 290 type Signature struct { 291 KeyID string `json:"keyid"` 292 Sig string `json:"sig"` 293 Certificate string `json:"cert,omitempty"` 294 } 295 296 // GetCertificate returns the parsed x509 certificate attached to the signature, 297 // if it exists. 298 func (sig Signature) GetCertificate() (Key, error) { 299 key := Key{} 300 if len(sig.Certificate) == 0 { 301 return key, errors.New("Signature has empty Certificate") 302 } 303 304 err := key.LoadKeyReaderDefaults(strings.NewReader(sig.Certificate)) 305 return key, err 306 } 307 308 /* 309 validateSignature is a function used to check if a passed signature is valid, 310 by inspecting the key ID and the signature itself. 311 */ 312 func validateSignature(signature Signature) error { 313 if err := validateHexString(signature.KeyID); err != nil { 314 return err 315 } 316 if err := validateHexString(signature.Sig); err != nil { 317 return err 318 } 319 return nil 320 } 321 322 /* 323 validateSliceOfSignatures is a helper function used to validate multiple 324 signatures stored in a slice. 325 */ 326 func validateSliceOfSignatures(slice []Signature) error { 327 for _, signature := range slice { 328 if err := validateSignature(signature); err != nil { 329 return err 330 } 331 } 332 return nil 333 } 334 335 /* 336 Link represents the evidence of a supply chain step performed by a functionary. 337 It should be contained in a generic Metablock object, which provides 338 functionality for signing and signature verification, and reading from and 339 writing to disk. 340 */ 341 type Link struct { 342 Type string `json:"_type"` 343 Name string `json:"name"` 344 Materials map[string]HashObj `json:"materials"` 345 Products map[string]HashObj `json:"products"` 346 ByProducts map[string]interface{} `json:"byproducts"` 347 Command []string `json:"command"` 348 Environment map[string]interface{} `json:"environment"` 349 } 350 351 /* 352 validateArtifacts is a general function used to validate products and materials. 353 */ 354 func validateArtifacts(artifacts map[string]HashObj) error { 355 for artifactName, artifact := range artifacts { 356 artifactValue := reflect.ValueOf(artifact).MapRange() 357 for artifactValue.Next() { 358 value := artifactValue.Value().Interface().(string) 359 hashType := artifactValue.Key().Interface().(string) 360 if err := validateHexString(value); err != nil { 361 return fmt.Errorf("in artifact '%s', %s hash value: %s", 362 artifactName, hashType, err.Error()) 363 } 364 } 365 } 366 return nil 367 } 368 369 /* 370 validateLink is a function used to ensure that a passed item of type Link 371 matches the necessary format. 372 */ 373 func validateLink(link Link) error { 374 if link.Type != "link" { 375 return fmt.Errorf("invalid type for link '%s': should be 'link'", 376 link.Name) 377 } 378 379 if err := validateArtifacts(link.Materials); err != nil { 380 return fmt.Errorf("in materials of link '%s': %s", link.Name, 381 err.Error()) 382 } 383 384 if err := validateArtifacts(link.Products); err != nil { 385 return fmt.Errorf("in products of link '%s': %s", link.Name, 386 err.Error()) 387 } 388 389 return nil 390 } 391 392 /* 393 LinkNameFormat represents a format string used to create the filename for a 394 signed Link (wrapped in a Metablock). It consists of the name of the link and 395 the first 8 characters of the signing key id. E.g.: 396 397 fmt.Sprintf(LinkNameFormat, "package", 398 "2f89b9272acfc8f4a0a0f094d789fdb0ba798b0fe41f2f5f417c12f0085ff498") 399 // returns "package.2f89b9272.link" 400 */ 401 const LinkNameFormat = "%s.%.8s.link" 402 const PreliminaryLinkNameFormat = ".%s.%.8s.link-unfinished" 403 404 /* 405 LinkNameFormatShort is for links that are not signed, e.g.: 406 407 fmt.Sprintf(LinkNameFormatShort, "unsigned") 408 // returns "unsigned.link" 409 */ 410 const LinkNameFormatShort = "%s.link" 411 const LinkGlobFormat = "%s.????????.link" 412 413 /* 414 SublayoutLinkDirFormat represents the format of the name of the directory for 415 sublayout links during the verification workflow. 416 */ 417 const SublayoutLinkDirFormat = "%s.%.8s" 418 419 /* 420 SupplyChainItem summarizes common fields of the two available supply chain 421 item types, Inspection and Step. 422 */ 423 type SupplyChainItem struct { 424 Name string `json:"name"` 425 ExpectedMaterials [][]string `json:"expected_materials"` 426 ExpectedProducts [][]string `json:"expected_products"` 427 } 428 429 /* 430 validateArtifactRule calls UnpackRule to validate that the passed rule conforms 431 with any of the available rule formats. 432 */ 433 func validateArtifactRule(rule []string) error { 434 if _, err := UnpackRule(rule); err != nil { 435 return err 436 } 437 return nil 438 } 439 440 /* 441 validateSliceOfArtifactRules iterates over passed rules to validate them. 442 */ 443 func validateSliceOfArtifactRules(rules [][]string) error { 444 for _, rule := range rules { 445 if err := validateArtifactRule(rule); err != nil { 446 return err 447 } 448 } 449 return nil 450 } 451 452 /* 453 validateSupplyChainItem is used to validate the common elements found in both 454 steps and inspections. Here, the function primarily ensures that the name of 455 a supply chain item isn't empty. 456 */ 457 func validateSupplyChainItem(item SupplyChainItem) error { 458 if item.Name == "" { 459 return fmt.Errorf("name cannot be empty") 460 } 461 462 if err := validateSliceOfArtifactRules(item.ExpectedMaterials); err != nil { 463 return fmt.Errorf("invalid material rule: %s", err) 464 } 465 if err := validateSliceOfArtifactRules(item.ExpectedProducts); err != nil { 466 return fmt.Errorf("invalid product rule: %s", err) 467 } 468 return nil 469 } 470 471 /* 472 Inspection represents an in-toto supply chain inspection, whose command in the 473 Run field is executed during final product verification, generating unsigned 474 link metadata. Materials and products used/produced by the inspection are 475 constrained by the artifact rules in the inspection's ExpectedMaterials and 476 ExpectedProducts fields. 477 */ 478 type Inspection struct { 479 Type string `json:"_type"` 480 Run []string `json:"run"` 481 SupplyChainItem 482 } 483 484 /* 485 validateInspection ensures that a passed inspection is valid and matches the 486 necessary format of an inspection. 487 */ 488 func validateInspection(inspection Inspection) error { 489 if err := validateSupplyChainItem(inspection.SupplyChainItem); err != nil { 490 return fmt.Errorf("inspection %s", err.Error()) 491 } 492 if inspection.Type != "inspection" { 493 return fmt.Errorf("invalid Type value for inspection '%s': should be "+ 494 "'inspection'", inspection.SupplyChainItem.Name) 495 } 496 return nil 497 } 498 499 /* 500 Step represents an in-toto step of the supply chain performed by a functionary. 501 During final product verification in-toto looks for corresponding Link 502 metadata, which is used as signed evidence that the step was performed 503 according to the supply chain definition. Materials and products used/produced 504 by the step are constrained by the artifact rules in the step's 505 ExpectedMaterials and ExpectedProducts fields. 506 */ 507 type Step struct { 508 Type string `json:"_type"` 509 PubKeys []string `json:"pubkeys"` 510 CertificateConstraints []CertificateConstraint `json:"cert_constraints,omitempty"` 511 ExpectedCommand []string `json:"expected_command"` 512 Threshold int `json:"threshold"` 513 SupplyChainItem 514 } 515 516 // CheckCertConstraints returns true if the provided certificate matches at least one 517 // of the constraints for this step. 518 func (s Step) CheckCertConstraints(key Key, rootCAIDs []string, rootCertPool, intermediateCertPool *x509.CertPool) error { 519 if len(s.CertificateConstraints) == 0 { 520 return fmt.Errorf("no constraints found") 521 } 522 523 _, possibleCert, err := decodeAndParse([]byte(key.KeyVal.Certificate)) 524 if err != nil { 525 return err 526 } 527 528 cert, ok := possibleCert.(*x509.Certificate) 529 if !ok { 530 return fmt.Errorf("not a valid certificate") 531 } 532 533 for _, constraint := range s.CertificateConstraints { 534 err = constraint.Check(cert, rootCAIDs, rootCertPool, intermediateCertPool) 535 if err == nil { 536 return nil 537 } 538 } 539 if err != nil { 540 return err 541 } 542 543 // this should not be reachable since there is at least one constraint, and the for loop only saw err != nil 544 return fmt.Errorf("unknown certificate constraint error") 545 } 546 547 /* 548 validateStep ensures that a passed step is valid and matches the 549 necessary format of an step. 550 */ 551 func validateStep(step Step) error { 552 if err := validateSupplyChainItem(step.SupplyChainItem); err != nil { 553 return fmt.Errorf("step %s", err.Error()) 554 } 555 if step.Type != "step" { 556 return fmt.Errorf("invalid Type value for step '%s': should be 'step'", 557 step.SupplyChainItem.Name) 558 } 559 for _, keyID := range step.PubKeys { 560 if err := validateHexString(keyID); err != nil { 561 return err 562 } 563 } 564 return nil 565 } 566 567 /* 568 ISO8601DateSchema defines the format string of a timestamp following the 569 ISO 8601 standard. 570 */ 571 const ISO8601DateSchema = "2006-01-02T15:04:05Z" 572 573 /* 574 Layout represents the definition of a software supply chain. It lists the 575 sequence of steps required in the software supply chain and the functionaries 576 authorized to perform these steps. Functionaries are identified by their 577 public keys. In addition, the layout may list a sequence of inspections that 578 are executed during in-toto supply chain verification. A layout should be 579 contained in a generic Metablock object, which provides functionality for 580 signing and signature verification, and reading from and writing to disk. 581 */ 582 type Layout struct { 583 Type string `json:"_type"` 584 Steps []Step `json:"steps"` 585 Inspect []Inspection `json:"inspect"` 586 Keys map[string]Key `json:"keys"` 587 RootCas map[string]Key `json:"rootcas,omitempty"` 588 IntermediateCas map[string]Key `json:"intermediatecas,omitempty"` 589 Expires string `json:"expires"` 590 Readme string `json:"readme"` 591 } 592 593 // Go does not allow to pass `[]T` (slice with certain type) to a function 594 // that accepts `[]interface{}` (slice with generic type) 595 // We have to manually create the interface slice first, see 596 // https://golang.org/doc/faq#convert_slice_of_interface 597 // TODO: Is there a better way to do polymorphism for steps and inspections? 598 func (l *Layout) stepsAsInterfaceSlice() []interface{} { 599 stepsI := make([]interface{}, len(l.Steps)) 600 for i, v := range l.Steps { 601 stepsI[i] = v 602 } 603 return stepsI 604 } 605 func (l *Layout) inspectAsInterfaceSlice() []interface{} { 606 inspectionsI := make([]interface{}, len(l.Inspect)) 607 for i, v := range l.Inspect { 608 inspectionsI[i] = v 609 } 610 return inspectionsI 611 } 612 613 // RootCAIDs returns a slice of all of the Root CA IDs 614 func (l *Layout) RootCAIDs() []string { 615 rootCAIDs := make([]string, 0, len(l.RootCas)) 616 for rootCAID := range l.RootCas { 617 rootCAIDs = append(rootCAIDs, rootCAID) 618 } 619 return rootCAIDs 620 } 621 622 func validateLayoutKeys(keys map[string]Key) error { 623 for keyID, key := range keys { 624 if key.KeyID != keyID { 625 return fmt.Errorf("invalid key found") 626 } 627 err := validatePublicKey(key) 628 if err != nil { 629 return err 630 } 631 } 632 633 return nil 634 } 635 636 /* 637 validateLayout is a function used to ensure that a passed item of type Layout 638 matches the necessary format. 639 */ 640 func validateLayout(layout Layout) error { 641 if layout.Type != "layout" { 642 return fmt.Errorf("invalid Type value for layout: should be 'layout'") 643 } 644 645 if _, err := time.Parse(ISO8601DateSchema, layout.Expires); err != nil { 646 return fmt.Errorf("expiry time parsed incorrectly - date either" + 647 " invalid or of incorrect format") 648 } 649 650 if err := validateLayoutKeys(layout.Keys); err != nil { 651 return err 652 } 653 654 if err := validateLayoutKeys(layout.RootCas); err != nil { 655 return err 656 } 657 658 if err := validateLayoutKeys(layout.IntermediateCas); err != nil { 659 return err 660 } 661 662 var namesSeen = make(map[string]bool) 663 for _, step := range layout.Steps { 664 if namesSeen[step.Name] { 665 return fmt.Errorf("non unique step or inspection name found") 666 } 667 668 namesSeen[step.Name] = true 669 670 if err := validateStep(step); err != nil { 671 return err 672 } 673 } 674 for _, inspection := range layout.Inspect { 675 if namesSeen[inspection.Name] { 676 return fmt.Errorf("non unique step or inspection name found") 677 } 678 679 namesSeen[inspection.Name] = true 680 } 681 return nil 682 } 683 684 type Metadata interface { 685 Sign(Key) error 686 VerifySignature(Key) error 687 GetPayload() any 688 Sigs() []Signature 689 GetSignatureForKeyID(string) (Signature, error) 690 Dump(string) error 691 } 692 693 func LoadMetadata(path string) (Metadata, error) { 694 jsonBytes, err := os.ReadFile(path) 695 if err != nil { 696 return nil, err 697 } 698 699 var rawData map[string]*json.RawMessage 700 if err := json.Unmarshal(jsonBytes, &rawData); err != nil { 701 return nil, err 702 } 703 704 if _, ok := rawData["payloadType"]; ok { 705 dsseEnv := &dsse.Envelope{} 706 if rawData["payload"] == nil || rawData["signatures"] == nil { 707 return nil, fmt.Errorf("in-toto metadata envelope requires 'payload' and 'signatures' parts") 708 } 709 710 if err := json.Unmarshal(jsonBytes, dsseEnv); err != nil { 711 return nil, err 712 } 713 714 if dsseEnv.PayloadType != PayloadType { 715 return nil, ErrInvalidPayloadType 716 } 717 718 return loadEnvelope(dsseEnv) 719 } 720 721 mb := &Metablock{} 722 723 // Error out on missing `signed` or `signatures` field or if 724 // one of them has a `null` value, which would lead to a nil pointer 725 // dereference in Unmarshal below. 726 if rawData["signed"] == nil || rawData["signatures"] == nil { 727 return nil, fmt.Errorf("in-toto metadata requires 'signed' and 'signatures' parts") 728 } 729 730 // Fully unmarshal signatures part 731 if err := json.Unmarshal(*rawData["signatures"], &mb.Signatures); err != nil { 732 return nil, err 733 } 734 735 payload, err := loadPayload(*rawData["signed"]) 736 if err != nil { 737 return nil, err 738 } 739 740 mb.Signed = payload 741 742 return mb, nil 743 } 744 745 /* 746 Metablock is a generic container for signable in-toto objects such as Layout 747 or Link. It has two fields, one that contains the signable object and one that 748 contains corresponding signatures. Metablock also provides functionality for 749 signing and signature verification, and reading from and writing to disk. 750 */ 751 type Metablock struct { 752 // NOTE: Whenever we want to access an attribute of `Signed` we have to 753 // perform type assertion, e.g. `metablock.Signed.(Layout).Keys` 754 // Maybe there is a better way to store either Layouts or Links in `Signed`? 755 // The notary folks seem to have separate container structs: 756 // https://github.com/theupdateframework/notary/blob/master/tuf/data/root.go#L10-L14 757 // https://github.com/theupdateframework/notary/blob/master/tuf/data/targets.go#L13-L17 758 // I implemented it this way, because there will be several functions that 759 // receive or return a Metablock, where the type of Signed has to be inferred 760 // on runtime, e.g. when iterating over links for a layout, and a link can 761 // turn out to be a layout (sublayout) 762 Signed interface{} `json:"signed"` 763 Signatures []Signature `json:"signatures"` 764 } 765 766 type jsonField struct { 767 name string 768 omitempty bool 769 } 770 771 /* 772 checkRequiredJSONFields checks that the passed map (obj) has keys for each of 773 the json tags in the passed struct type (typ), and returns an error otherwise. 774 Any json tags that contain the "omitempty" option be allowed to be optional. 775 */ 776 func checkRequiredJSONFields(obj map[string]interface{}, 777 typ reflect.Type) error { 778 779 // Create list of json tags, e.g. `json:"_type"` 780 attributeCount := typ.NumField() 781 allFields := make([]jsonField, 0) 782 for i := 0; i < attributeCount; i++ { 783 fieldStr := typ.Field(i).Tag.Get("json") 784 field := jsonField{ 785 name: fieldStr, 786 omitempty: false, 787 } 788 789 if idx := strings.Index(fieldStr, ","); idx != -1 { 790 field.name = fieldStr[:idx] 791 field.omitempty = strings.Contains(fieldStr[idx+1:], "omitempty") 792 } 793 794 allFields = append(allFields, field) 795 } 796 797 // Assert that there's a key in the passed map for each tag 798 for _, field := range allFields { 799 if _, ok := obj[field.name]; !ok && !field.omitempty { 800 return fmt.Errorf("required field %s missing", field.name) 801 } 802 } 803 return nil 804 } 805 806 /* 807 Load parses JSON formatted metadata at the passed path into the Metablock 808 object on which it was called. It returns an error if it cannot parse 809 a valid JSON formatted Metablock that contains a Link or Layout. 810 811 Deprecated: Use LoadMetadata for a signature wrapper agnostic way to load an 812 envelope. 813 */ 814 func (mb *Metablock) Load(path string) error { 815 // Read entire file 816 jsonBytes, err := os.ReadFile(path) 817 if err != nil { 818 return err 819 } 820 821 // Unmarshal JSON into a map of raw messages (signed and signatures) 822 // We can't fully unmarshal immediately, because we need to inspect the 823 // type (link or layout) to decide which data structure to use 824 var rawMb map[string]*json.RawMessage 825 if err := json.Unmarshal(jsonBytes, &rawMb); err != nil { 826 return err 827 } 828 829 // Error out on missing `signed` or `signatures` field or if 830 // one of them has a `null` value, which would lead to a nil pointer 831 // dereference in Unmarshal below. 832 if rawMb["signed"] == nil || rawMb["signatures"] == nil { 833 return fmt.Errorf("in-toto metadata requires 'signed' and" + 834 " 'signatures' parts") 835 } 836 837 // Fully unmarshal signatures part 838 if err := json.Unmarshal(*rawMb["signatures"], &mb.Signatures); err != nil { 839 return err 840 } 841 842 payload, err := loadPayload(*rawMb["signed"]) 843 if err != nil { 844 return err 845 } 846 847 mb.Signed = payload 848 849 return nil 850 } 851 852 /* 853 Dump JSON serializes and writes the Metablock on which it was called to the 854 passed path. It returns an error if JSON serialization or writing fails. 855 */ 856 func (mb *Metablock) Dump(path string) error { 857 // JSON encode Metablock formatted with newlines and indentation 858 // TODO: parametrize format 859 jsonBytes, err := json.MarshalIndent(mb, "", " ") 860 if err != nil { 861 return err 862 } 863 864 // Write JSON bytes to the passed path with permissions (-rw-r--r--) 865 err = os.WriteFile(path, jsonBytes, 0644) 866 if err != nil { 867 return err 868 } 869 870 return nil 871 } 872 873 /* 874 GetSignableRepresentation returns the canonical JSON representation of the 875 Signed field of the Metablock on which it was called. If canonicalization 876 fails the first return value is nil and the second return value is the error. 877 */ 878 func (mb *Metablock) GetSignableRepresentation() ([]byte, error) { 879 return cjson.EncodeCanonical(mb.Signed) 880 } 881 882 func (mb *Metablock) GetPayload() any { 883 return mb.Signed 884 } 885 886 func (mb *Metablock) Sigs() []Signature { 887 return mb.Signatures 888 } 889 890 /* 891 VerifySignature verifies the first signature, corresponding to the passed Key, 892 that it finds in the Signatures field of the Metablock on which it was called. 893 It returns an error if Signatures does not contain a Signature corresponding to 894 the passed Key, the object in Signed cannot be canonicalized, or the Signature 895 is invalid. 896 */ 897 func (mb *Metablock) VerifySignature(key Key) error { 898 sig, err := mb.GetSignatureForKeyID(key.KeyID) 899 if err != nil { 900 return err 901 } 902 903 verifier, err := getSignerVerifierFromKey(key) 904 if err != nil { 905 return err 906 } 907 908 payload, err := mb.GetSignableRepresentation() 909 if err != nil { 910 return err 911 } 912 913 sigBytes, err := hex.DecodeString(sig.Sig) 914 if err != nil { 915 return err 916 } 917 918 err = verifier.Verify(context.Background(), payload, sigBytes) 919 if err != nil { 920 return err 921 } 922 923 return nil 924 } 925 926 // GetSignatureForKeyID returns the signature that was created by the provided keyID, if it exists. 927 func (mb *Metablock) GetSignatureForKeyID(keyID string) (Signature, error) { 928 for _, s := range mb.Signatures { 929 if s.KeyID == keyID { 930 return s, nil 931 } 932 } 933 934 return Signature{}, fmt.Errorf("no signature found for key '%s'", keyID) 935 } 936 937 /* 938 ValidateMetablock ensures that a passed Metablock object is valid. It indirectly 939 validates the Link or Layout that the Metablock object contains. 940 */ 941 func ValidateMetablock(mb Metablock) error { 942 switch mbSignedType := mb.Signed.(type) { 943 case Layout: 944 if err := validateLayout(mb.Signed.(Layout)); err != nil { 945 return err 946 } 947 case Link: 948 if err := validateLink(mb.Signed.(Link)); err != nil { 949 return err 950 } 951 default: 952 return fmt.Errorf("unknown type '%s', should be 'layout' or 'link'", 953 mbSignedType) 954 } 955 956 if err := validateSliceOfSignatures(mb.Signatures); err != nil { 957 return err 958 } 959 960 return nil 961 } 962 963 /* 964 Sign creates a signature over the signed portion of the metablock using the Key 965 object provided. It then appends the resulting signature to the signatures 966 field as provided. It returns an error if the Signed object cannot be 967 canonicalized, or if the key is invalid or not supported. 968 */ 969 func (mb *Metablock) Sign(key Key) error { 970 signer, err := getSignerVerifierFromKey(key) 971 if err != nil { 972 return err 973 } 974 975 payload, err := mb.GetSignableRepresentation() 976 if err != nil { 977 return err 978 } 979 980 signature, err := signer.Sign(context.Background(), payload) 981 if err != nil { 982 return err 983 } 984 985 mb.Signatures = append(mb.Signatures, Signature{ 986 KeyID: key.KeyID, 987 Sig: hex.EncodeToString(signature), 988 Certificate: key.KeyVal.Certificate, 989 }) 990 991 return nil 992 }