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