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