github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/asserts/snap_asserts.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015-2020 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package asserts 21 22 import ( 23 "bytes" 24 "crypto" 25 "fmt" 26 "time" 27 28 _ "golang.org/x/crypto/sha3" // expected for digests 29 30 "github.com/snapcore/snapd/osutil" 31 "github.com/snapcore/snapd/release" 32 "github.com/snapcore/snapd/snap/naming" 33 ) 34 35 // SnapDeclaration holds a snap-declaration assertion, declaring a 36 // snap binding its identifying snap-id to a name, asserting its 37 // publisher and its other properties. 38 type SnapDeclaration struct { 39 assertionBase 40 refreshControl []string 41 plugRules map[string]*PlugRule 42 slotRules map[string]*SlotRule 43 autoAliases []string 44 aliases map[string]string 45 timestamp time.Time 46 } 47 48 // Series returns the series for which the snap is being declared. 49 func (snapdcl *SnapDeclaration) Series() string { 50 return snapdcl.HeaderString("series") 51 } 52 53 // SnapID returns the snap id of the declared snap. 54 func (snapdcl *SnapDeclaration) SnapID() string { 55 return snapdcl.HeaderString("snap-id") 56 } 57 58 // SnapName returns the declared snap name. 59 func (snapdcl *SnapDeclaration) SnapName() string { 60 return snapdcl.HeaderString("snap-name") 61 } 62 63 // PublisherID returns the identifier of the publisher of the declared snap. 64 func (snapdcl *SnapDeclaration) PublisherID() string { 65 return snapdcl.HeaderString("publisher-id") 66 } 67 68 // Timestamp returns the time when the snap-declaration was issued. 69 func (snapdcl *SnapDeclaration) Timestamp() time.Time { 70 return snapdcl.timestamp 71 } 72 73 // RefreshControl returns the ids of snaps whose updates are controlled by this declaration. 74 func (snapdcl *SnapDeclaration) RefreshControl() []string { 75 return snapdcl.refreshControl 76 } 77 78 // PlugRule returns the plug-side rule about the given interface if one was included in the plugs stanza of the declaration, otherwise it returns nil. 79 func (snapdcl *SnapDeclaration) PlugRule(interfaceName string) *PlugRule { 80 return snapdcl.plugRules[interfaceName] 81 } 82 83 // SlotRule returns the slot-side rule about the given interface if one was included in the slots stanza of the declaration, otherwise it returns nil. 84 func (snapdcl *SnapDeclaration) SlotRule(interfaceName string) *SlotRule { 85 return snapdcl.slotRules[interfaceName] 86 } 87 88 // AutoAliases returns the optional auto-aliases granted to this snap. 89 // XXX: deprecated, will go away 90 func (snapdcl *SnapDeclaration) AutoAliases() []string { 91 return snapdcl.autoAliases 92 } 93 94 // Aliases returns the optional explicit aliases granted to this snap. 95 func (snapdcl *SnapDeclaration) Aliases() map[string]string { 96 return snapdcl.aliases 97 } 98 99 // Implement further consistency checks. 100 func (snapdcl *SnapDeclaration) checkConsistency(db RODatabase, acck *AccountKey) error { 101 if !db.IsTrustedAccount(snapdcl.AuthorityID()) { 102 return fmt.Errorf("snap-declaration assertion for %q (id %q) is not signed by a directly trusted authority: %s", snapdcl.SnapName(), snapdcl.SnapID(), snapdcl.AuthorityID()) 103 } 104 _, err := db.Find(AccountType, map[string]string{ 105 "account-id": snapdcl.PublisherID(), 106 }) 107 if IsNotFound(err) { 108 return fmt.Errorf("snap-declaration assertion for %q (id %q) does not have a matching account assertion for the publisher %q", snapdcl.SnapName(), snapdcl.SnapID(), snapdcl.PublisherID()) 109 } 110 if err != nil { 111 return err 112 } 113 114 return nil 115 } 116 117 // sanity 118 var _ consistencyChecker = (*SnapDeclaration)(nil) 119 120 // Prerequisites returns references to this snap-declaration's prerequisite assertions. 121 func (snapdcl *SnapDeclaration) Prerequisites() []*Ref { 122 return []*Ref{ 123 {Type: AccountType, PrimaryKey: []string{snapdcl.PublisherID()}}, 124 } 125 } 126 127 func compilePlugRules(plugs map[string]interface{}, compiled func(iface string, plugRule *PlugRule)) error { 128 for iface, rule := range plugs { 129 plugRule, err := compilePlugRule(iface, rule) 130 if err != nil { 131 return err 132 } 133 compiled(iface, plugRule) 134 } 135 return nil 136 } 137 138 func compileSlotRules(slots map[string]interface{}, compiled func(iface string, slotRule *SlotRule)) error { 139 for iface, rule := range slots { 140 slotRule, err := compileSlotRule(iface, rule) 141 if err != nil { 142 return err 143 } 144 compiled(iface, slotRule) 145 } 146 return nil 147 } 148 149 func snapDeclarationFormatAnalyze(headers map[string]interface{}, body []byte) (formatnum int, err error) { 150 _, plugsOk := headers["plugs"] 151 _, slotsOk := headers["slots"] 152 if !(plugsOk || slotsOk) { 153 return 0, nil 154 } 155 156 formatnum = 1 157 setFormatNum := func(num int) { 158 if num > formatnum { 159 formatnum = num 160 } 161 } 162 163 plugs, err := checkMap(headers, "plugs") 164 if err != nil { 165 return 0, err 166 } 167 err = compilePlugRules(plugs, func(_ string, rule *PlugRule) { 168 if rule.feature(dollarAttrConstraintsFeature) { 169 setFormatNum(2) 170 } 171 if rule.feature(deviceScopeConstraintsFeature) { 172 setFormatNum(3) 173 } 174 if rule.feature(nameConstraintsFeature) { 175 setFormatNum(4) 176 } 177 }) 178 if err != nil { 179 return 0, err 180 } 181 182 slots, err := checkMap(headers, "slots") 183 if err != nil { 184 return 0, err 185 } 186 err = compileSlotRules(slots, func(_ string, rule *SlotRule) { 187 if rule.feature(dollarAttrConstraintsFeature) { 188 setFormatNum(2) 189 } 190 if rule.feature(deviceScopeConstraintsFeature) { 191 setFormatNum(3) 192 } 193 if rule.feature(nameConstraintsFeature) { 194 setFormatNum(4) 195 } 196 }) 197 if err != nil { 198 return 0, err 199 } 200 201 return formatnum, nil 202 } 203 204 func checkAliases(headers map[string]interface{}) (map[string]string, error) { 205 value, ok := headers["aliases"] 206 if !ok { 207 return nil, nil 208 } 209 aliasList, ok := value.([]interface{}) 210 if !ok { 211 return nil, fmt.Errorf(`"aliases" header must be a list of alias maps`) 212 } 213 if len(aliasList) == 0 { 214 return nil, nil 215 } 216 217 aliasMap := make(map[string]string, len(aliasList)) 218 for i, item := range aliasList { 219 aliasItem, ok := item.(map[string]interface{}) 220 if !ok { 221 return nil, fmt.Errorf(`"aliases" header must be a list of alias maps`) 222 } 223 224 what := fmt.Sprintf(`in "aliases" item %d`, i+1) 225 name, err := checkStringMatchesWhat(aliasItem, "name", what, naming.ValidAlias) 226 if err != nil { 227 return nil, err 228 } 229 230 what = fmt.Sprintf(`for alias %q`, name) 231 target, err := checkStringMatchesWhat(aliasItem, "target", what, naming.ValidApp) 232 if err != nil { 233 return nil, err 234 } 235 236 if _, ok := aliasMap[name]; ok { 237 return nil, fmt.Errorf(`duplicated definition in "aliases" for alias %q`, name) 238 } 239 240 aliasMap[name] = target 241 } 242 243 return aliasMap, nil 244 } 245 246 func assembleSnapDeclaration(assert assertionBase) (Assertion, error) { 247 _, err := checkExistsString(assert.headers, "snap-name") 248 if err != nil { 249 return nil, err 250 } 251 252 _, err = checkNotEmptyString(assert.headers, "publisher-id") 253 if err != nil { 254 return nil, err 255 } 256 257 timestamp, err := checkRFC3339Date(assert.headers, "timestamp") 258 if err != nil { 259 return nil, err 260 } 261 262 var refControl []string 263 var plugRules map[string]*PlugRule 264 var slotRules map[string]*SlotRule 265 266 refControl, err = checkStringList(assert.headers, "refresh-control") 267 if err != nil { 268 return nil, err 269 } 270 271 plugs, err := checkMap(assert.headers, "plugs") 272 if err != nil { 273 return nil, err 274 } 275 if plugs != nil { 276 plugRules = make(map[string]*PlugRule, len(plugs)) 277 err := compilePlugRules(plugs, func(iface string, rule *PlugRule) { 278 plugRules[iface] = rule 279 }) 280 if err != nil { 281 return nil, err 282 } 283 } 284 285 slots, err := checkMap(assert.headers, "slots") 286 if err != nil { 287 return nil, err 288 } 289 if slots != nil { 290 slotRules = make(map[string]*SlotRule, len(slots)) 291 err := compileSlotRules(slots, func(iface string, rule *SlotRule) { 292 slotRules[iface] = rule 293 }) 294 if err != nil { 295 return nil, err 296 } 297 } 298 299 // XXX: depracated, will go away later 300 autoAliases, err := checkStringListMatches(assert.headers, "auto-aliases", naming.ValidAlias) 301 if err != nil { 302 return nil, err 303 } 304 305 aliases, err := checkAliases(assert.headers) 306 if err != nil { 307 return nil, err 308 } 309 310 return &SnapDeclaration{ 311 assertionBase: assert, 312 refreshControl: refControl, 313 plugRules: plugRules, 314 slotRules: slotRules, 315 autoAliases: autoAliases, 316 aliases: aliases, 317 timestamp: timestamp, 318 }, nil 319 } 320 321 // SnapFileSHA3_384 computes the SHA3-384 digest of the given snap file. 322 // It also returns its size. 323 func SnapFileSHA3_384(snapPath string) (digest string, size uint64, err error) { 324 sha3_384Dgst, size, err := osutil.FileDigest(snapPath, crypto.SHA3_384) 325 if err != nil { 326 return "", 0, fmt.Errorf("cannot compute snap %q digest: %v", snapPath, err) 327 } 328 329 sha3_384, err := EncodeDigest(crypto.SHA3_384, sha3_384Dgst) 330 if err != nil { 331 return "", 0, fmt.Errorf("cannot encode snap %q digest: %v", snapPath, err) 332 } 333 return sha3_384, size, nil 334 } 335 336 // SnapBuild holds a snap-build assertion, asserting the properties of a snap 337 // at the time it was built by the developer. 338 type SnapBuild struct { 339 assertionBase 340 size uint64 341 timestamp time.Time 342 } 343 344 // SnapSHA3_384 returns the SHA3-384 digest of the snap. 345 func (snapbld *SnapBuild) SnapSHA3_384() string { 346 return snapbld.HeaderString("snap-sha3-384") 347 } 348 349 // SnapID returns the snap id of the snap. 350 func (snapbld *SnapBuild) SnapID() string { 351 return snapbld.HeaderString("snap-id") 352 } 353 354 // SnapSize returns the size of the snap. 355 func (snapbld *SnapBuild) SnapSize() uint64 { 356 return snapbld.size 357 } 358 359 // Grade returns the grade of the snap: devel|stable 360 func (snapbld *SnapBuild) Grade() string { 361 return snapbld.HeaderString("grade") 362 } 363 364 // Timestamp returns the time when the snap-build assertion was created. 365 func (snapbld *SnapBuild) Timestamp() time.Time { 366 return snapbld.timestamp 367 } 368 369 func assembleSnapBuild(assert assertionBase) (Assertion, error) { 370 _, err := checkDigest(assert.headers, "snap-sha3-384", crypto.SHA3_384) 371 if err != nil { 372 return nil, err 373 } 374 375 _, err = checkNotEmptyString(assert.headers, "snap-id") 376 if err != nil { 377 return nil, err 378 } 379 380 _, err = checkNotEmptyString(assert.headers, "grade") 381 if err != nil { 382 return nil, err 383 } 384 385 size, err := checkUint(assert.headers, "snap-size", 64) 386 if err != nil { 387 return nil, err 388 } 389 390 timestamp, err := checkRFC3339Date(assert.headers, "timestamp") 391 if err != nil { 392 return nil, err 393 } 394 // ignore extra headers and non-empty body for future compatibility 395 return &SnapBuild{ 396 assertionBase: assert, 397 size: size, 398 timestamp: timestamp, 399 }, nil 400 } 401 402 // SnapRevision holds a snap-revision assertion, which is a statement by the 403 // store acknowledging the receipt of a build of a snap and labeling it with a 404 // snap revision. 405 type SnapRevision struct { 406 assertionBase 407 snapSize uint64 408 snapRevision int 409 timestamp time.Time 410 } 411 412 // SnapSHA3_384 returns the SHA3-384 digest of the snap. 413 func (snaprev *SnapRevision) SnapSHA3_384() string { 414 return snaprev.HeaderString("snap-sha3-384") 415 } 416 417 // SnapID returns the snap id of the snap. 418 func (snaprev *SnapRevision) SnapID() string { 419 return snaprev.HeaderString("snap-id") 420 } 421 422 // SnapSize returns the size in bytes of the snap submitted to the store. 423 func (snaprev *SnapRevision) SnapSize() uint64 { 424 return snaprev.snapSize 425 } 426 427 // SnapRevision returns the revision assigned to this build of the snap. 428 func (snaprev *SnapRevision) SnapRevision() int { 429 return snaprev.snapRevision 430 } 431 432 // DeveloperID returns the id of the developer that submitted this build of the 433 // snap. 434 func (snaprev *SnapRevision) DeveloperID() string { 435 return snaprev.HeaderString("developer-id") 436 } 437 438 // Timestamp returns the time when the snap-revision was issued. 439 func (snaprev *SnapRevision) Timestamp() time.Time { 440 return snaprev.timestamp 441 } 442 443 // Implement further consistency checks. 444 func (snaprev *SnapRevision) checkConsistency(db RODatabase, acck *AccountKey) error { 445 // TODO: expand this to consider other stores signing on their own 446 if !db.IsTrustedAccount(snaprev.AuthorityID()) { 447 return fmt.Errorf("snap-revision assertion for snap id %q is not signed by a store: %s", snaprev.SnapID(), snaprev.AuthorityID()) 448 } 449 _, err := db.Find(AccountType, map[string]string{ 450 "account-id": snaprev.DeveloperID(), 451 }) 452 if IsNotFound(err) { 453 return fmt.Errorf("snap-revision assertion for snap id %q does not have a matching account assertion for the developer %q", snaprev.SnapID(), snaprev.DeveloperID()) 454 } 455 if err != nil { 456 return err 457 } 458 _, err = db.Find(SnapDeclarationType, map[string]string{ 459 // XXX: mediate getting current series through some context object? this gets the job done for now 460 "series": release.Series, 461 "snap-id": snaprev.SnapID(), 462 }) 463 if IsNotFound(err) { 464 return fmt.Errorf("snap-revision assertion for snap id %q does not have a matching snap-declaration assertion", snaprev.SnapID()) 465 } 466 if err != nil { 467 return err 468 } 469 return nil 470 } 471 472 // sanity 473 var _ consistencyChecker = (*SnapRevision)(nil) 474 475 // Prerequisites returns references to this snap-revision's prerequisite assertions. 476 func (snaprev *SnapRevision) Prerequisites() []*Ref { 477 return []*Ref{ 478 // XXX: mediate getting current series through some context object? this gets the job done for now 479 {Type: SnapDeclarationType, PrimaryKey: []string{release.Series, snaprev.SnapID()}}, 480 {Type: AccountType, PrimaryKey: []string{snaprev.DeveloperID()}}, 481 } 482 } 483 484 func checkSnapRevisionWhat(headers map[string]interface{}, name, what string) (snapRevision int, err error) { 485 snapRevision, err = checkIntWhat(headers, name, what) 486 if err != nil { 487 return 0, err 488 } 489 if snapRevision < 1 { 490 return 0, fmt.Errorf(`%q %s must be >=1: %d`, name, what, snapRevision) 491 } 492 return snapRevision, nil 493 } 494 495 func assembleSnapRevision(assert assertionBase) (Assertion, error) { 496 _, err := checkDigest(assert.headers, "snap-sha3-384", crypto.SHA3_384) 497 if err != nil { 498 return nil, err 499 } 500 501 _, err = checkNotEmptyString(assert.headers, "snap-id") 502 if err != nil { 503 return nil, err 504 } 505 506 snapSize, err := checkUint(assert.headers, "snap-size", 64) 507 if err != nil { 508 return nil, err 509 } 510 511 snapRevision, err := checkSnapRevisionWhat(assert.headers, "snap-revision", "header") 512 if err != nil { 513 return nil, err 514 } 515 516 _, err = checkNotEmptyString(assert.headers, "developer-id") 517 if err != nil { 518 return nil, err 519 } 520 521 timestamp, err := checkRFC3339Date(assert.headers, "timestamp") 522 if err != nil { 523 return nil, err 524 } 525 526 return &SnapRevision{ 527 assertionBase: assert, 528 snapSize: snapSize, 529 snapRevision: snapRevision, 530 timestamp: timestamp, 531 }, nil 532 } 533 534 // Validation holds a validation assertion, describing that a combination of 535 // (snap-id, approved-snap-id, approved-revision) has been validated for 536 // the series, meaning updating to that revision of approved-snap-id 537 // has been approved by the owner of the gating snap with snap-id. 538 type Validation struct { 539 assertionBase 540 revoked bool 541 timestamp time.Time 542 approvedSnapRevision int 543 } 544 545 // Series returns the series for which the validation holds. 546 func (validation *Validation) Series() string { 547 return validation.HeaderString("series") 548 } 549 550 // SnapID returns the ID of the gating snap. 551 func (validation *Validation) SnapID() string { 552 return validation.HeaderString("snap-id") 553 } 554 555 // ApprovedSnapID returns the ID of the gated snap. 556 func (validation *Validation) ApprovedSnapID() string { 557 return validation.HeaderString("approved-snap-id") 558 } 559 560 // ApprovedSnapRevision returns the approved revision of the gated snap. 561 func (validation *Validation) ApprovedSnapRevision() int { 562 return validation.approvedSnapRevision 563 } 564 565 // Revoked returns true if the validation has been revoked. 566 func (validation *Validation) Revoked() bool { 567 return validation.revoked 568 } 569 570 // Timestamp returns the time when the validation was issued. 571 func (validation *Validation) Timestamp() time.Time { 572 return validation.timestamp 573 } 574 575 // Implement further consistency checks. 576 func (validation *Validation) checkConsistency(db RODatabase, acck *AccountKey) error { 577 _, err := db.Find(SnapDeclarationType, map[string]string{ 578 "series": validation.Series(), 579 "snap-id": validation.ApprovedSnapID(), 580 }) 581 if IsNotFound(err) { 582 return fmt.Errorf("validation assertion by snap-id %q does not have a matching snap-declaration assertion for approved-snap-id %q", validation.SnapID(), validation.ApprovedSnapID()) 583 } 584 if err != nil { 585 return err 586 } 587 a, err := db.Find(SnapDeclarationType, map[string]string{ 588 "series": validation.Series(), 589 "snap-id": validation.SnapID(), 590 }) 591 if IsNotFound(err) { 592 return fmt.Errorf("validation assertion by snap-id %q does not have a matching snap-declaration assertion", validation.SnapID()) 593 } 594 if err != nil { 595 return err 596 } 597 598 gatingDecl := a.(*SnapDeclaration) 599 if gatingDecl.PublisherID() != validation.AuthorityID() { 600 return fmt.Errorf("validation assertion by snap %q (id %q) not signed by its publisher", gatingDecl.SnapName(), validation.SnapID()) 601 } 602 603 return nil 604 } 605 606 // sanity 607 var _ consistencyChecker = (*Validation)(nil) 608 609 // Prerequisites returns references to this validation's prerequisite assertions. 610 func (validation *Validation) Prerequisites() []*Ref { 611 return []*Ref{ 612 {Type: SnapDeclarationType, PrimaryKey: []string{validation.Series(), validation.SnapID()}}, 613 {Type: SnapDeclarationType, PrimaryKey: []string{validation.Series(), validation.ApprovedSnapID()}}, 614 } 615 } 616 617 func assembleValidation(assert assertionBase) (Assertion, error) { 618 approvedSnapRevision, err := checkSnapRevisionWhat(assert.headers, "approved-snap-revision", "header") 619 if err != nil { 620 return nil, err 621 } 622 623 revoked, err := checkOptionalBool(assert.headers, "revoked") 624 if err != nil { 625 return nil, err 626 } 627 628 timestamp, err := checkRFC3339Date(assert.headers, "timestamp") 629 if err != nil { 630 return nil, err 631 } 632 633 return &Validation{ 634 assertionBase: assert, 635 revoked: revoked, 636 timestamp: timestamp, 637 approvedSnapRevision: approvedSnapRevision, 638 }, nil 639 } 640 641 // BaseDeclaration holds a base-declaration assertion, declaring the 642 // policies (to start with interface ones) applying to all snaps of 643 // a series. 644 type BaseDeclaration struct { 645 assertionBase 646 plugRules map[string]*PlugRule 647 slotRules map[string]*SlotRule 648 timestamp time.Time 649 } 650 651 // Series returns the series whose snaps are governed by the declaration. 652 func (basedcl *BaseDeclaration) Series() string { 653 return basedcl.HeaderString("series") 654 } 655 656 // Timestamp returns the time when the base-declaration was issued. 657 func (basedcl *BaseDeclaration) Timestamp() time.Time { 658 return basedcl.timestamp 659 } 660 661 // PlugRule returns the plug-side rule about the given interface if one was included in the plugs stanza of the declaration, otherwise it returns nil. 662 func (basedcl *BaseDeclaration) PlugRule(interfaceName string) *PlugRule { 663 return basedcl.plugRules[interfaceName] 664 } 665 666 // SlotRule returns the slot-side rule about the given interface if one was included in the slots stanza of the declaration, otherwise it returns nil. 667 func (basedcl *BaseDeclaration) SlotRule(interfaceName string) *SlotRule { 668 return basedcl.slotRules[interfaceName] 669 } 670 671 // Implement further consistency checks. 672 func (basedcl *BaseDeclaration) checkConsistency(db RODatabase, acck *AccountKey) error { 673 // XXX: not signed or stored yet in a db, but being ready for that 674 if !db.IsTrustedAccount(basedcl.AuthorityID()) { 675 return fmt.Errorf("base-declaration assertion for series %s is not signed by a directly trusted authority: %s", basedcl.Series(), basedcl.AuthorityID()) 676 } 677 return nil 678 } 679 680 // sanity 681 var _ consistencyChecker = (*BaseDeclaration)(nil) 682 683 func assembleBaseDeclaration(assert assertionBase) (Assertion, error) { 684 var plugRules map[string]*PlugRule 685 plugs, err := checkMap(assert.headers, "plugs") 686 if err != nil { 687 return nil, err 688 } 689 if plugs != nil { 690 plugRules = make(map[string]*PlugRule, len(plugs)) 691 err := compilePlugRules(plugs, func(iface string, rule *PlugRule) { 692 plugRules[iface] = rule 693 }) 694 if err != nil { 695 return nil, err 696 } 697 } 698 699 var slotRules map[string]*SlotRule 700 slots, err := checkMap(assert.headers, "slots") 701 if err != nil { 702 return nil, err 703 } 704 if slots != nil { 705 slotRules = make(map[string]*SlotRule, len(slots)) 706 err := compileSlotRules(slots, func(iface string, rule *SlotRule) { 707 slotRules[iface] = rule 708 }) 709 if err != nil { 710 return nil, err 711 } 712 } 713 714 timestamp, err := checkRFC3339Date(assert.headers, "timestamp") 715 if err != nil { 716 return nil, err 717 } 718 719 return &BaseDeclaration{ 720 assertionBase: assert, 721 plugRules: plugRules, 722 slotRules: slotRules, 723 timestamp: timestamp, 724 }, nil 725 } 726 727 var builtinBaseDeclaration *BaseDeclaration 728 729 // BuiltinBaseDeclaration exposes the initialized builtin base-declaration assertion. This is used by overlord/assertstate, other code should use assertstate.BaseDeclaration. 730 func BuiltinBaseDeclaration() *BaseDeclaration { 731 return builtinBaseDeclaration 732 } 733 734 var ( 735 builtinBaseDeclarationCheckOrder = []string{"type", "authority-id", "series"} 736 builtinBaseDeclarationExpectedHeaders = map[string]interface{}{ 737 "type": "base-declaration", 738 "authority-id": "canonical", 739 "series": release.Series, 740 } 741 ) 742 743 // InitBuiltinBaseDeclaration initializes the builtin base-declaration based on headers (or resets it if headers is nil). 744 func InitBuiltinBaseDeclaration(headers []byte) error { 745 if headers == nil { 746 builtinBaseDeclaration = nil 747 return nil 748 } 749 trimmed := bytes.TrimSpace(headers) 750 h, err := parseHeaders(trimmed) 751 if err != nil { 752 return err 753 } 754 for _, name := range builtinBaseDeclarationCheckOrder { 755 expected := builtinBaseDeclarationExpectedHeaders[name] 756 if h[name] != expected { 757 return fmt.Errorf("the builtin base-declaration %q header is not set to expected value %q", name, expected) 758 } 759 } 760 revision, err := checkRevision(h) 761 if err != nil { 762 return fmt.Errorf("cannot assemble the builtin-base declaration: %v", err) 763 } 764 h["timestamp"] = time.Now().UTC().Format(time.RFC3339) 765 a, err := assembleBaseDeclaration(assertionBase{ 766 headers: h, 767 body: nil, 768 revision: revision, 769 content: trimmed, 770 signature: []byte("$builtin"), 771 }) 772 if err != nil { 773 return fmt.Errorf("cannot assemble the builtin base-declaration: %v", err) 774 } 775 builtinBaseDeclaration = a.(*BaseDeclaration) 776 return nil 777 } 778 779 type dateRange struct { 780 Since time.Time 781 Until time.Time 782 } 783 784 // SnapDeveloper holds a snap-developer assertion, defining the developers who 785 // can collaborate on a snap while it's owned by a specific publisher. 786 // 787 // The primary key (snap-id, publisher-id) allows a snap to have many 788 // snap-developer assertions, e.g. to allow a future publisher's collaborations 789 // to be defined before the snap is transferred. However only the 790 // snap-developer for the current publisher (the snap-declaration publisher-id) 791 // is relevant to a device. 792 type SnapDeveloper struct { 793 assertionBase 794 developerRanges map[string][]*dateRange 795 } 796 797 // SnapID returns the snap id of the snap. 798 func (snapdev *SnapDeveloper) SnapID() string { 799 return snapdev.HeaderString("snap-id") 800 } 801 802 // PublisherID returns the publisher's account id. 803 func (snapdev *SnapDeveloper) PublisherID() string { 804 return snapdev.HeaderString("publisher-id") 805 } 806 807 func (snapdev *SnapDeveloper) checkConsistency(db RODatabase, acck *AccountKey) error { 808 // Check authority is the publisher or trusted. 809 authorityID := snapdev.AuthorityID() 810 publisherID := snapdev.PublisherID() 811 if !db.IsTrustedAccount(authorityID) && (publisherID != authorityID) { 812 return fmt.Errorf("snap-developer must be signed by the publisher or a trusted authority but got authority %q and publisher %q", authorityID, publisherID) 813 } 814 815 // Check snap-declaration for the snap-id exists for the series. 816 // Note: the current publisher is irrelevant here because this assertion 817 // may be for a future publisher. 818 _, err := db.Find(SnapDeclarationType, map[string]string{ 819 // XXX: mediate getting current series through some context object? this gets the job done for now 820 "series": release.Series, 821 "snap-id": snapdev.SnapID(), 822 }) 823 if err != nil { 824 if IsNotFound(err) { 825 return fmt.Errorf("snap-developer assertion for snap id %q does not have a matching snap-declaration assertion", snapdev.SnapID()) 826 } 827 return err 828 } 829 830 // check there's an account for the publisher-id 831 _, err = db.Find(AccountType, map[string]string{"account-id": publisherID}) 832 if err != nil { 833 if IsNotFound(err) { 834 return fmt.Errorf("snap-developer assertion for snap-id %q does not have a matching account assertion for the publisher %q", snapdev.SnapID(), publisherID) 835 } 836 return err 837 } 838 839 // check there's an account for each developer 840 for developerID := range snapdev.developerRanges { 841 if developerID == publisherID { 842 continue 843 } 844 _, err = db.Find(AccountType, map[string]string{"account-id": developerID}) 845 if err != nil { 846 if IsNotFound(err) { 847 return fmt.Errorf("snap-developer assertion for snap-id %q does not have a matching account assertion for the developer %q", snapdev.SnapID(), developerID) 848 } 849 return err 850 } 851 } 852 853 return nil 854 } 855 856 // sanity 857 var _ consistencyChecker = (*SnapDeveloper)(nil) 858 859 // Prerequisites returns references to this snap-developer's prerequisite assertions. 860 func (snapdev *SnapDeveloper) Prerequisites() []*Ref { 861 // Capacity for the snap-declaration, the publisher and all developers. 862 refs := make([]*Ref, 0, 2+len(snapdev.developerRanges)) 863 864 // snap-declaration 865 // XXX: mediate getting current series through some context object? this gets the job done for now 866 refs = append(refs, &Ref{SnapDeclarationType, []string{release.Series, snapdev.SnapID()}}) 867 868 // the publisher and developers 869 publisherID := snapdev.PublisherID() 870 refs = append(refs, &Ref{AccountType, []string{publisherID}}) 871 for developerID := range snapdev.developerRanges { 872 if developerID != publisherID { 873 refs = append(refs, &Ref{AccountType, []string{developerID}}) 874 } 875 } 876 877 return refs 878 } 879 880 func assembleSnapDeveloper(assert assertionBase) (Assertion, error) { 881 developerRanges, err := checkDevelopers(assert.headers) 882 if err != nil { 883 return nil, err 884 } 885 886 return &SnapDeveloper{ 887 assertionBase: assert, 888 developerRanges: developerRanges, 889 }, nil 890 } 891 892 func checkDevelopers(headers map[string]interface{}) (map[string][]*dateRange, error) { 893 value, ok := headers["developers"] 894 if !ok { 895 return nil, nil 896 } 897 developers, ok := value.([]interface{}) 898 if !ok { 899 return nil, fmt.Errorf(`"developers" must be a list of developer maps`) 900 } 901 if len(developers) == 0 { 902 return nil, nil 903 } 904 905 // Used to check for a developer with revoking and non-revoking items. 906 // No entry means developer not yet seen, false means seen but not revoked, 907 // true means seen and revoked. 908 revocationStatus := map[string]bool{} 909 910 developerRanges := make(map[string][]*dateRange) 911 for i, item := range developers { 912 developer, ok := item.(map[string]interface{}) 913 if !ok { 914 return nil, fmt.Errorf(`"developers" must be a list of developer maps`) 915 } 916 917 what := fmt.Sprintf(`in "developers" item %d`, i+1) 918 accountID, err := checkStringMatchesWhat(developer, "developer-id", what, validAccountID) 919 if err != nil { 920 return nil, err 921 } 922 923 what = fmt.Sprintf(`in "developers" item %d for developer %q`, i+1, accountID) 924 since, err := checkRFC3339DateWhat(developer, "since", what) 925 if err != nil { 926 return nil, err 927 } 928 until, err := checkRFC3339DateWithDefaultWhat(developer, "until", what, time.Time{}) 929 if err != nil { 930 return nil, err 931 } 932 if !until.IsZero() && since.After(until) { 933 return nil, fmt.Errorf(`"since" %s must be less than or equal to "until"`, what) 934 } 935 936 // Track/check for revocation conflicts. 937 revoked := since.Equal(until) 938 previouslyRevoked, ok := revocationStatus[accountID] 939 if !ok { 940 revocationStatus[accountID] = revoked 941 } else if previouslyRevoked || revoked { 942 return nil, fmt.Errorf(`revocation for developer %q must be standalone but found other "developers" items`, accountID) 943 } 944 945 developerRanges[accountID] = append(developerRanges[accountID], &dateRange{since, until}) 946 } 947 948 return developerRanges, nil 949 }