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  }