github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/asserts/model_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-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_test
    21  
    22  import (
    23  	"fmt"
    24  	"strings"
    25  	"time"
    26  
    27  	. "gopkg.in/check.v1"
    28  
    29  	"github.com/snapcore/snapd/asserts"
    30  	"github.com/snapcore/snapd/snap/naming"
    31  )
    32  
    33  type modelSuite struct {
    34  	ts     time.Time
    35  	tsLine string
    36  }
    37  
    38  var (
    39  	_ = Suite(&modelSuite{})
    40  )
    41  
    42  func (mods *modelSuite) SetUpSuite(c *C) {
    43  	mods.ts = time.Now().Truncate(time.Second).UTC()
    44  	mods.tsLine = "timestamp: " + mods.ts.Format(time.RFC3339) + "\n"
    45  }
    46  
    47  const (
    48  	reqSnaps     = "required-snaps:\n  - foo\n  - bar\n"
    49  	sysUserAuths = "system-user-authority: *\n"
    50  	serialAuths  = "serial-authority:\n  - generic\n"
    51  )
    52  
    53  const (
    54  	modelExample = "type: model\n" +
    55  		"authority-id: brand-id1\n" +
    56  		"series: 16\n" +
    57  		"brand-id: brand-id1\n" +
    58  		"model: baz-3000\n" +
    59  		"display-name: Baz 3000\n" +
    60  		"architecture: amd64\n" +
    61  		"gadget: brand-gadget\n" +
    62  		"base: core18\n" +
    63  		"kernel: baz-linux\n" +
    64  		"store: brand-store\n" +
    65  		serialAuths +
    66  		sysUserAuths +
    67  		reqSnaps +
    68  		"TSLINE" +
    69  		"body-length: 0\n" +
    70  		"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
    71  		"\n\n" +
    72  		"AXNpZw=="
    73  
    74  	classicModelExample = "type: model\n" +
    75  		"authority-id: brand-id1\n" +
    76  		"series: 16\n" +
    77  		"brand-id: brand-id1\n" +
    78  		"model: baz-3000\n" +
    79  		"display-name: Baz 3000\n" +
    80  		"classic: true\n" +
    81  		"architecture: amd64\n" +
    82  		"gadget: brand-gadget\n" +
    83  		"store: brand-store\n" +
    84  		reqSnaps +
    85  		"TSLINE" +
    86  		"body-length: 0\n" +
    87  		"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
    88  		"\n\n" +
    89  		"AXNpZw=="
    90  
    91  	core20ModelExample = `type: model
    92  authority-id: brand-id1
    93  series: 16
    94  brand-id: brand-id1
    95  model: baz-3000
    96  display-name: Baz 3000
    97  architecture: amd64
    98  system-user-authority: *
    99  base: core20
   100  store: brand-store
   101  snaps:
   102    -
   103      name: baz-linux
   104      id: bazlinuxidididididididididididid
   105      type: kernel
   106      default-channel: 20
   107    -
   108      name: brand-gadget
   109      id: brandgadgetdidididididididididid
   110      type: gadget
   111    -
   112      name: other-base
   113      id: otherbasedididididididididididid
   114      type: base
   115      modes:
   116        - run
   117      presence: required
   118    -
   119      name: nm
   120      id: nmididididididididididididididid
   121      modes:
   122        - ephemeral
   123        - run
   124      default-channel: 1.0
   125    -
   126      name: myapp
   127      id: myappdididididididididididididid
   128      type: app
   129      default-channel: 2.0
   130    -
   131      name: myappopt
   132      id: myappoptidididididididididididid
   133      type: app
   134      presence: optional
   135  OTHERgrade: secured
   136  ` + "TSLINE" +
   137  		"body-length: 0\n" +
   138  		"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
   139  		"\n\n" +
   140  		"AXNpZw=="
   141  )
   142  
   143  func (mods *modelSuite) TestDecodeOK(c *C) {
   144  	encoded := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
   145  	a, err := asserts.Decode([]byte(encoded))
   146  	c.Assert(err, IsNil)
   147  	c.Check(a.Type(), Equals, asserts.ModelType)
   148  	model := a.(*asserts.Model)
   149  	c.Check(model.AuthorityID(), Equals, "brand-id1")
   150  	c.Check(model.Timestamp(), Equals, mods.ts)
   151  	c.Check(model.Series(), Equals, "16")
   152  	c.Check(model.BrandID(), Equals, "brand-id1")
   153  	c.Check(model.Model(), Equals, "baz-3000")
   154  	c.Check(model.DisplayName(), Equals, "Baz 3000")
   155  	c.Check(model.Architecture(), Equals, "amd64")
   156  	c.Check(model.GadgetSnap(), DeepEquals, &asserts.ModelSnap{
   157  		Name:     "brand-gadget",
   158  		SnapType: "gadget",
   159  		Modes:    []string{"run"},
   160  		Presence: "required",
   161  	})
   162  	c.Check(model.Gadget(), Equals, "brand-gadget")
   163  	c.Check(model.GadgetTrack(), Equals, "")
   164  	c.Check(model.KernelSnap(), DeepEquals, &asserts.ModelSnap{
   165  		Name:     "baz-linux",
   166  		SnapType: "kernel",
   167  		Modes:    []string{"run"},
   168  		Presence: "required",
   169  	})
   170  	c.Check(model.Kernel(), Equals, "baz-linux")
   171  	c.Check(model.KernelTrack(), Equals, "")
   172  	c.Check(model.Base(), Equals, "core18")
   173  	c.Check(model.BaseSnap(), DeepEquals, &asserts.ModelSnap{
   174  		Name:     "core18",
   175  		SnapType: "base",
   176  		Modes:    []string{"run"},
   177  		Presence: "required",
   178  	})
   179  	c.Check(model.Store(), Equals, "brand-store")
   180  	c.Check(model.Grade(), Equals, asserts.ModelGradeUnset)
   181  	essentialSnaps := model.EssentialSnaps()
   182  	c.Check(essentialSnaps, DeepEquals, []*asserts.ModelSnap{
   183  		model.KernelSnap(),
   184  		model.BaseSnap(),
   185  		model.GadgetSnap(),
   186  	})
   187  	snaps := model.SnapsWithoutEssential()
   188  	c.Check(snaps, DeepEquals, []*asserts.ModelSnap{
   189  		{
   190  			Name:     "foo",
   191  			Modes:    []string{"run"},
   192  			Presence: "required",
   193  		},
   194  		{
   195  			Name:     "bar",
   196  			Modes:    []string{"run"},
   197  			Presence: "required",
   198  		},
   199  	})
   200  	// essential snaps included
   201  	reqSnaps := naming.NewSnapSet(model.RequiredWithEssentialSnaps())
   202  	for _, e := range essentialSnaps {
   203  		c.Check(reqSnaps.Contains(e), Equals, true)
   204  	}
   205  	for _, s := range snaps {
   206  		c.Check(reqSnaps.Contains(s), Equals, true)
   207  	}
   208  	c.Check(reqSnaps.Size(), Equals, len(essentialSnaps)+len(snaps))
   209  	// essential snaps excluded
   210  	noEssential := naming.NewSnapSet(model.RequiredNoEssentialSnaps())
   211  	for _, e := range essentialSnaps {
   212  		c.Check(noEssential.Contains(e), Equals, false)
   213  	}
   214  	for _, s := range snaps {
   215  		c.Check(noEssential.Contains(s), Equals, true)
   216  	}
   217  	c.Check(noEssential.Size(), Equals, len(snaps))
   218  
   219  	c.Check(model.SystemUserAuthority(), HasLen, 0)
   220  	c.Check(model.SerialAuthority(), DeepEquals, []string{"brand-id1", "generic"})
   221  }
   222  
   223  func (mods *modelSuite) TestDecodeStoreIsOptional(c *C) {
   224  	withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
   225  	encoded := strings.Replace(withTimestamp, "store: brand-store\n", "store: \n", 1)
   226  	a, err := asserts.Decode([]byte(encoded))
   227  	c.Assert(err, IsNil)
   228  	model := a.(*asserts.Model)
   229  	c.Check(model.Store(), Equals, "")
   230  
   231  	encoded = strings.Replace(withTimestamp, "store: brand-store\n", "", 1)
   232  	a, err = asserts.Decode([]byte(encoded))
   233  	c.Assert(err, IsNil)
   234  	model = a.(*asserts.Model)
   235  	c.Check(model.Store(), Equals, "")
   236  }
   237  
   238  func (mods *modelSuite) TestDecodeBaseIsOptional(c *C) {
   239  	withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
   240  	encoded := strings.Replace(withTimestamp, "base: core18\n", "base: \n", 1)
   241  	a, err := asserts.Decode([]byte(encoded))
   242  	c.Assert(err, IsNil)
   243  	model := a.(*asserts.Model)
   244  	c.Check(model.Base(), Equals, "")
   245  
   246  	encoded = strings.Replace(withTimestamp, "base: core18\n", "", 1)
   247  	a, err = asserts.Decode([]byte(encoded))
   248  	c.Assert(err, IsNil)
   249  	model = a.(*asserts.Model)
   250  	c.Check(model.Base(), Equals, "")
   251  	c.Check(model.BaseSnap(), IsNil)
   252  }
   253  
   254  func (mods *modelSuite) TestDecodeDisplayNameIsOptional(c *C) {
   255  	withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
   256  	encoded := strings.Replace(withTimestamp, "display-name: Baz 3000\n", "display-name: \n", 1)
   257  	a, err := asserts.Decode([]byte(encoded))
   258  	c.Assert(err, IsNil)
   259  	model := a.(*asserts.Model)
   260  	// optional but we fallback to Model
   261  	c.Check(model.DisplayName(), Equals, "baz-3000")
   262  
   263  	encoded = strings.Replace(withTimestamp, "display-name: Baz 3000\n", "", 1)
   264  	a, err = asserts.Decode([]byte(encoded))
   265  	c.Assert(err, IsNil)
   266  	model = a.(*asserts.Model)
   267  	// optional but we fallback to Model
   268  	c.Check(model.DisplayName(), Equals, "baz-3000")
   269  }
   270  
   271  func (mods *modelSuite) TestDecodeRequiredSnapsAreOptional(c *C) {
   272  	withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
   273  	encoded := strings.Replace(withTimestamp, reqSnaps, "", 1)
   274  	a, err := asserts.Decode([]byte(encoded))
   275  	c.Assert(err, IsNil)
   276  	model := a.(*asserts.Model)
   277  	c.Check(model.RequiredNoEssentialSnaps(), HasLen, 0)
   278  }
   279  
   280  func (mods *modelSuite) TestDecodeValidatesSnapNames(c *C) {
   281  	withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
   282  	encoded := strings.Replace(withTimestamp, reqSnaps, "required-snaps:\n  - foo_bar\n  - bar\n", 1)
   283  	a, err := asserts.Decode([]byte(encoded))
   284  	c.Assert(a, IsNil)
   285  	c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "required-snaps" header: foo_bar`)
   286  
   287  	encoded = strings.Replace(withTimestamp, reqSnaps, "required-snaps:\n  - foo\n  - bar-;;''\n", 1)
   288  	a, err = asserts.Decode([]byte(encoded))
   289  	c.Assert(a, IsNil)
   290  	c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "required-snaps" header: bar-;;''`)
   291  
   292  	encoded = strings.Replace(withTimestamp, "kernel: baz-linux\n", "kernel: baz-linux_instance\n", 1)
   293  	a, err = asserts.Decode([]byte(encoded))
   294  	c.Assert(a, IsNil)
   295  	c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "kernel" header: baz-linux_instance`)
   296  
   297  	encoded = strings.Replace(withTimestamp, "gadget: brand-gadget\n", "gadget: brand-gadget_instance\n", 1)
   298  	a, err = asserts.Decode([]byte(encoded))
   299  	c.Assert(a, IsNil)
   300  	c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "gadget" header: brand-gadget_instance`)
   301  
   302  	encoded = strings.Replace(withTimestamp, "base: core18\n", "base: core18_instance\n", 1)
   303  	a, err = asserts.Decode([]byte(encoded))
   304  	c.Assert(a, IsNil)
   305  	c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "base" header: core18_instance`)
   306  }
   307  
   308  func (mods modelSuite) TestDecodeValidSnapNames(c *C) {
   309  	// reuse test cases for snap.ValidateName()
   310  
   311  	withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
   312  
   313  	validNames := []string{
   314  		"aa", "aaa", "aaaa",
   315  		"a-a", "aa-a", "a-aa", "a-b-c",
   316  		"a0", "a-0", "a-0a",
   317  		"01game", "1-or-2",
   318  		// a regexp stresser
   319  		"u-94903713687486543234157734673284536758",
   320  	}
   321  	for _, name := range validNames {
   322  		encoded := strings.Replace(withTimestamp, "kernel: baz-linux\n", fmt.Sprintf("kernel: %s\n", name), 1)
   323  		a, err := asserts.Decode([]byte(encoded))
   324  		c.Assert(err, IsNil)
   325  		model := a.(*asserts.Model)
   326  		c.Check(model.Kernel(), Equals, name)
   327  	}
   328  	invalidNames := []string{
   329  		// name cannot be empty, never reaches snap name validation
   330  		"",
   331  		// too short (min 2 chars)
   332  		"a",
   333  		// names cannot be too long
   334  		"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
   335  		"xxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxx",
   336  		"1111111111111111111111111111111111111111x",
   337  		"x1111111111111111111111111111111111111111",
   338  		"x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x",
   339  		// a regexp stresser
   340  		"u-9490371368748654323415773467328453675-",
   341  		// dashes alone are not a name
   342  		"-", "--",
   343  		// double dashes in a name are not allowed
   344  		"a--a",
   345  		// name should not end with a dash
   346  		"a-",
   347  		// name cannot have any spaces in it
   348  		"a ", " a", "a a",
   349  		// a number alone is not a name
   350  		"0", "123",
   351  		// identifier must be plain ASCII
   352  		"日本語", "한글", "ру́сский язы́к",
   353  		// instance names are invalid too
   354  		"foo_bar", "x_1",
   355  	}
   356  	for _, name := range invalidNames {
   357  		encoded := strings.Replace(withTimestamp, "kernel: baz-linux\n", fmt.Sprintf("kernel: %s\n", name), 1)
   358  		a, err := asserts.Decode([]byte(encoded))
   359  		c.Assert(a, IsNil)
   360  		if name != "" {
   361  			c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "kernel" header: .*`)
   362  		} else {
   363  			c.Assert(err, ErrorMatches, `assertion model: "kernel" header should not be empty`)
   364  		}
   365  	}
   366  }
   367  
   368  func (mods *modelSuite) TestDecodeSerialAuthorityIsOptional(c *C) {
   369  	withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
   370  	encoded := strings.Replace(withTimestamp, serialAuths, "", 1)
   371  	a, err := asserts.Decode([]byte(encoded))
   372  	c.Assert(err, IsNil)
   373  	model := a.(*asserts.Model)
   374  	// the default is just to accept the brand itself
   375  	c.Check(model.SerialAuthority(), DeepEquals, []string{"brand-id1"})
   376  
   377  	encoded = strings.Replace(withTimestamp, serialAuths, "serial-authority:\n  - foo\n  - bar\n", 1)
   378  	a, err = asserts.Decode([]byte(encoded))
   379  	c.Assert(err, IsNil)
   380  	model = a.(*asserts.Model)
   381  	// the brand is always added implicitly
   382  	c.Check(model.SerialAuthority(), DeepEquals, []string{"brand-id1", "foo", "bar"})
   383  }
   384  
   385  func (mods *modelSuite) TestDecodeSystemUserAuthorityIsOptional(c *C) {
   386  	withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
   387  	encoded := strings.Replace(withTimestamp, sysUserAuths, "", 1)
   388  	a, err := asserts.Decode([]byte(encoded))
   389  	c.Assert(err, IsNil)
   390  	model := a.(*asserts.Model)
   391  	// the default is just to accept the brand itself
   392  	c.Check(model.SystemUserAuthority(), DeepEquals, []string{"brand-id1"})
   393  
   394  	encoded = strings.Replace(withTimestamp, sysUserAuths, "system-user-authority:\n  - foo\n  - bar\n", 1)
   395  	a, err = asserts.Decode([]byte(encoded))
   396  	c.Assert(err, IsNil)
   397  	model = a.(*asserts.Model)
   398  	// the brand is always added implicitly, it can always sign
   399  	// a new revision of the model anyway
   400  	c.Check(model.SystemUserAuthority(), DeepEquals, []string{"brand-id1", "foo", "bar"})
   401  }
   402  
   403  func (mods *modelSuite) TestDecodeKernelTrack(c *C) {
   404  	withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
   405  	encoded := strings.Replace(withTimestamp, "kernel: baz-linux\n", "kernel: baz-linux=18\n", 1)
   406  	a, err := asserts.Decode([]byte(encoded))
   407  	c.Assert(err, IsNil)
   408  	model := a.(*asserts.Model)
   409  	c.Check(model.KernelSnap(), DeepEquals, &asserts.ModelSnap{
   410  		Name:        "baz-linux",
   411  		SnapType:    "kernel",
   412  		Modes:       []string{"run"},
   413  		PinnedTrack: "18",
   414  		Presence:    "required",
   415  	})
   416  	c.Check(model.Kernel(), Equals, "baz-linux")
   417  	c.Check(model.KernelTrack(), Equals, "18")
   418  }
   419  
   420  func (mods *modelSuite) TestDecodeGadgetTrack(c *C) {
   421  	withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
   422  	encoded := strings.Replace(withTimestamp, "gadget: brand-gadget\n", "gadget: brand-gadget=18\n", 1)
   423  	a, err := asserts.Decode([]byte(encoded))
   424  	c.Assert(err, IsNil)
   425  	model := a.(*asserts.Model)
   426  	c.Check(model.GadgetSnap(), DeepEquals, &asserts.ModelSnap{
   427  		Name:        "brand-gadget",
   428  		SnapType:    "gadget",
   429  		Modes:       []string{"run"},
   430  		PinnedTrack: "18",
   431  		Presence:    "required",
   432  	})
   433  	c.Check(model.Gadget(), Equals, "brand-gadget")
   434  	c.Check(model.GadgetTrack(), Equals, "18")
   435  }
   436  
   437  const (
   438  	modelErrPrefix = "assertion model: "
   439  )
   440  
   441  func (mods *modelSuite) TestDecodeInvalid(c *C) {
   442  	encoded := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
   443  
   444  	invalidTests := []struct{ original, invalid, expectedErr string }{
   445  		{"series: 16\n", "", `"series" header is mandatory`},
   446  		{"series: 16\n", "series: \n", `"series" header should not be empty`},
   447  		{"brand-id: brand-id1\n", "", `"brand-id" header is mandatory`},
   448  		{"brand-id: brand-id1\n", "brand-id: \n", `"brand-id" header should not be empty`},
   449  		{"brand-id: brand-id1\n", "brand-id: random\n", `authority-id and brand-id must match, model assertions are expected to be signed by the brand: "brand-id1" != "random"`},
   450  		{"model: baz-3000\n", "", `"model" header is mandatory`},
   451  		{"model: baz-3000\n", "model: \n", `"model" header should not be empty`},
   452  		{"model: baz-3000\n", "model: baz/3000\n", `"model" primary key header cannot contain '/'`},
   453  		// lift this restriction at a later point
   454  		{"model: baz-3000\n", "model: BAZ-3000\n", `"model" header cannot contain uppercase letters`},
   455  		{"display-name: Baz 3000\n", "display-name:\n  - xyz\n", `"display-name" header must be a string`},
   456  		{"architecture: amd64\n", "", `"architecture" header is mandatory`},
   457  		{"architecture: amd64\n", "architecture: \n", `"architecture" header should not be empty`},
   458  		{"gadget: brand-gadget\n", "", `"gadget" header is mandatory`},
   459  		{"gadget: brand-gadget\n", "gadget: \n", `"gadget" header should not be empty`},
   460  		{"gadget: brand-gadget\n", "gadget: brand-gadget=x/x/x\n", `"gadget" channel selector must be a track name only`},
   461  		{"gadget: brand-gadget\n", "gadget: brand-gadget=stable\n", `"gadget" channel selector must be a track name`},
   462  		{"gadget: brand-gadget\n", "gadget: brand-gadget=18/beta\n", `"gadget" channel selector must be a track name only`},
   463  		{"gadget: brand-gadget\n", "gadget:\n  - xyz \n", `"gadget" header must be a string`},
   464  		{"kernel: baz-linux\n", "", `"kernel" header is mandatory`},
   465  		{"kernel: baz-linux\n", "kernel: \n", `"kernel" header should not be empty`},
   466  		{"kernel: baz-linux\n", "kernel: baz-linux=x/x/x\n", `"kernel" channel selector must be a track name only`},
   467  		{"kernel: baz-linux\n", "kernel: baz-linux=stable\n", `"kernel" channel selector must be a track name`},
   468  		{"kernel: baz-linux\n", "kernel: baz-linux=18/beta\n", `"kernel" channel selector must be a track name only`},
   469  		{"kernel: baz-linux\n", "kernel:\n  - xyz \n", `"kernel" header must be a string`},
   470  		{"base: core18\n", "base:\n  - xyz \n", `"base" header must be a string`},
   471  		{"store: brand-store\n", "store:\n  - xyz\n", `"store" header must be a string`},
   472  		{mods.tsLine, "", `"timestamp" header is mandatory`},
   473  		{mods.tsLine, "timestamp: \n", `"timestamp" header should not be empty`},
   474  		{mods.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`},
   475  		{reqSnaps, "required-snaps: foo\n", `"required-snaps" header must be a list of strings`},
   476  		{reqSnaps, "required-snaps:\n  -\n    - nested\n", `"required-snaps" header must be a list of strings`},
   477  		{serialAuths, "serial-authority:\n  a: 1\n", `"serial-authority" header must be a list of account ids`},
   478  		{serialAuths, "serial-authority:\n  - 5_6\n", `"serial-authority" header must be a list of account ids`},
   479  		{sysUserAuths, "system-user-authority:\n  a: 1\n", `"system-user-authority" header must be '\*' or a list of account ids`},
   480  		{sysUserAuths, "system-user-authority:\n  - 5_6\n", `"system-user-authority" header must be '\*' or a list of account ids`},
   481  		{reqSnaps, "grade: dangerous\n", `cannot specify a grade for model without the extended snaps header`},
   482  	}
   483  
   484  	for _, test := range invalidTests {
   485  		invalid := strings.Replace(encoded, test.original, test.invalid, 1)
   486  		_, err := asserts.Decode([]byte(invalid))
   487  		c.Check(err, ErrorMatches, modelErrPrefix+test.expectedErr)
   488  	}
   489  }
   490  
   491  func (mods *modelSuite) TestModelCheck(c *C) {
   492  	ex, err := asserts.Decode([]byte(strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)))
   493  	c.Assert(err, IsNil)
   494  
   495  	storeDB, db := makeStoreAndCheckDB(c)
   496  	brandDB := setup3rdPartySigning(c, "brand-id1", storeDB, db)
   497  
   498  	headers := ex.Headers()
   499  	headers["brand-id"] = brandDB.AuthorityID
   500  	headers["timestamp"] = time.Now().Format(time.RFC3339)
   501  	model, err := brandDB.Sign(asserts.ModelType, headers, nil, "")
   502  	c.Assert(err, IsNil)
   503  
   504  	err = db.Check(model)
   505  	c.Assert(err, IsNil)
   506  }
   507  
   508  func (mods *modelSuite) TestModelCheckInconsistentTimestamp(c *C) {
   509  	ex, err := asserts.Decode([]byte(strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)))
   510  	c.Assert(err, IsNil)
   511  
   512  	storeDB, db := makeStoreAndCheckDB(c)
   513  	brandDB := setup3rdPartySigning(c, "brand-id1", storeDB, db)
   514  
   515  	headers := ex.Headers()
   516  	headers["brand-id"] = brandDB.AuthorityID
   517  	headers["timestamp"] = "2011-01-01T14:00:00Z"
   518  	model, err := brandDB.Sign(asserts.ModelType, headers, nil, "")
   519  	c.Assert(err, IsNil)
   520  
   521  	err = db.Check(model)
   522  	c.Assert(err, ErrorMatches, `model assertion timestamp outside of signing key validity \(key valid since.*\)`)
   523  }
   524  
   525  func (mods *modelSuite) TestClassicDecodeOK(c *C) {
   526  	encoded := strings.Replace(classicModelExample, "TSLINE", mods.tsLine, 1)
   527  	a, err := asserts.Decode([]byte(encoded))
   528  	c.Assert(err, IsNil)
   529  	c.Check(a.Type(), Equals, asserts.ModelType)
   530  	model := a.(*asserts.Model)
   531  	c.Check(model.AuthorityID(), Equals, "brand-id1")
   532  	c.Check(model.Timestamp(), Equals, mods.ts)
   533  	c.Check(model.Series(), Equals, "16")
   534  	c.Check(model.BrandID(), Equals, "brand-id1")
   535  	c.Check(model.Model(), Equals, "baz-3000")
   536  	c.Check(model.DisplayName(), Equals, "Baz 3000")
   537  	c.Check(model.Classic(), Equals, true)
   538  	c.Check(model.Architecture(), Equals, "amd64")
   539  	c.Check(model.GadgetSnap(), DeepEquals, &asserts.ModelSnap{
   540  		Name:     "brand-gadget",
   541  		SnapType: "gadget",
   542  		Modes:    []string{"run"},
   543  		Presence: "required",
   544  	})
   545  	c.Check(model.Gadget(), Equals, "brand-gadget")
   546  	c.Check(model.KernelSnap(), IsNil)
   547  	c.Check(model.Kernel(), Equals, "")
   548  	c.Check(model.KernelTrack(), Equals, "")
   549  	c.Check(model.Base(), Equals, "")
   550  	c.Check(model.BaseSnap(), IsNil)
   551  	c.Check(model.Store(), Equals, "brand-store")
   552  	essentialSnaps := model.EssentialSnaps()
   553  	c.Check(essentialSnaps, DeepEquals, []*asserts.ModelSnap{
   554  		model.GadgetSnap(),
   555  	})
   556  	snaps := model.SnapsWithoutEssential()
   557  	c.Check(snaps, DeepEquals, []*asserts.ModelSnap{
   558  		{
   559  			Name:     "foo",
   560  			Modes:    []string{"run"},
   561  			Presence: "required",
   562  		},
   563  		{
   564  			Name:     "bar",
   565  			Modes:    []string{"run"},
   566  			Presence: "required",
   567  		},
   568  	})
   569  	// gadget included
   570  	reqSnaps := naming.NewSnapSet(model.RequiredWithEssentialSnaps())
   571  	for _, e := range essentialSnaps {
   572  		c.Check(reqSnaps.Contains(e), Equals, true)
   573  	}
   574  	for _, s := range snaps {
   575  		c.Check(reqSnaps.Contains(s), Equals, true)
   576  	}
   577  	c.Check(reqSnaps.Size(), Equals, len(essentialSnaps)+len(snaps))
   578  	// gadget excluded
   579  	noEssential := naming.NewSnapSet(model.RequiredNoEssentialSnaps())
   580  	for _, e := range essentialSnaps {
   581  		c.Check(noEssential.Contains(e), Equals, false)
   582  	}
   583  	for _, s := range snaps {
   584  		c.Check(noEssential.Contains(s), Equals, true)
   585  	}
   586  	c.Check(noEssential.Size(), Equals, len(snaps))
   587  }
   588  
   589  func (mods *modelSuite) TestClassicDecodeInvalid(c *C) {
   590  	encoded := strings.Replace(classicModelExample, "TSLINE", mods.tsLine, 1)
   591  
   592  	invalidTests := []struct{ original, invalid, expectedErr string }{
   593  		{"classic: true\n", "classic: foo\n", `"classic" header must be 'true' or 'false'`},
   594  		{"architecture: amd64\n", "architecture:\n  - foo\n", `"architecture" header must be a string`},
   595  		{"gadget: brand-gadget\n", "gadget:\n  - foo\n", `"gadget" header must be a string`},
   596  		{"gadget: brand-gadget\n", "kernel: brand-kernel\n", `cannot specify a kernel with a classic model`},
   597  		{"gadget: brand-gadget\n", "base: some-base\n", `cannot specify a base with a classic model`},
   598  		{"gadget: brand-gadget\n", "gadget:\n  - xyz\n", `"gadget" header must be a string`},
   599  	}
   600  
   601  	for _, test := range invalidTests {
   602  		invalid := strings.Replace(encoded, test.original, test.invalid, 1)
   603  		_, err := asserts.Decode([]byte(invalid))
   604  		c.Check(err, ErrorMatches, modelErrPrefix+test.expectedErr)
   605  	}
   606  }
   607  
   608  func (mods *modelSuite) TestClassicDecodeGadgetAndArchOptional(c *C) {
   609  	encoded := strings.Replace(classicModelExample, "TSLINE", mods.tsLine, 1)
   610  	encoded = strings.Replace(encoded, "gadget: brand-gadget\n", "", 1)
   611  	encoded = strings.Replace(encoded, "architecture: amd64\n", "", 1)
   612  	a, err := asserts.Decode([]byte(encoded))
   613  	c.Assert(err, IsNil)
   614  	c.Check(a.Type(), Equals, asserts.ModelType)
   615  	model := a.(*asserts.Model)
   616  	c.Check(model.Classic(), Equals, true)
   617  	c.Check(model.Architecture(), Equals, "")
   618  	c.Check(model.GadgetSnap(), IsNil)
   619  	c.Check(model.Gadget(), Equals, "")
   620  	c.Check(model.GadgetTrack(), Equals, "")
   621  }
   622  
   623  func (mods *modelSuite) TestCore20DecodeOK(c *C) {
   624  	encoded := strings.Replace(core20ModelExample, "TSLINE", mods.tsLine, 1)
   625  	encoded = strings.Replace(encoded, "OTHER", "", 1)
   626  	a, err := asserts.Decode([]byte(encoded))
   627  	c.Assert(err, IsNil)
   628  	c.Check(a.Type(), Equals, asserts.ModelType)
   629  	model := a.(*asserts.Model)
   630  	c.Check(model.AuthorityID(), Equals, "brand-id1")
   631  	c.Check(model.Timestamp(), Equals, mods.ts)
   632  	c.Check(model.Series(), Equals, "16")
   633  	c.Check(model.BrandID(), Equals, "brand-id1")
   634  	c.Check(model.Model(), Equals, "baz-3000")
   635  	c.Check(model.DisplayName(), Equals, "Baz 3000")
   636  	c.Check(model.Architecture(), Equals, "amd64")
   637  	c.Check(model.GadgetSnap(), DeepEquals, &asserts.ModelSnap{
   638  		Name:           "brand-gadget",
   639  		SnapID:         "brandgadgetdidididididididididid",
   640  		SnapType:       "gadget",
   641  		Modes:          []string{"run", "ephemeral"},
   642  		DefaultChannel: "latest/stable",
   643  		Presence:       "required",
   644  	})
   645  	c.Check(model.Gadget(), Equals, "brand-gadget")
   646  	c.Check(model.GadgetTrack(), Equals, "")
   647  	c.Check(model.KernelSnap(), DeepEquals, &asserts.ModelSnap{
   648  		Name:           "baz-linux",
   649  		SnapID:         "bazlinuxidididididididididididid",
   650  		SnapType:       "kernel",
   651  		Modes:          []string{"run", "ephemeral"},
   652  		DefaultChannel: "20",
   653  		Presence:       "required",
   654  	})
   655  	c.Check(model.Kernel(), Equals, "baz-linux")
   656  	c.Check(model.KernelTrack(), Equals, "")
   657  	c.Check(model.Base(), Equals, "core20")
   658  	c.Check(model.BaseSnap(), DeepEquals, &asserts.ModelSnap{
   659  		Name:           "core20",
   660  		SnapID:         naming.WellKnownSnapID("core20"),
   661  		SnapType:       "base",
   662  		Modes:          []string{"run", "ephemeral"},
   663  		DefaultChannel: "latest/stable",
   664  		Presence:       "required",
   665  	})
   666  	c.Check(model.Store(), Equals, "brand-store")
   667  	c.Check(model.Grade(), Equals, asserts.ModelSecured)
   668  	essentialSnaps := model.EssentialSnaps()
   669  	c.Check(essentialSnaps, DeepEquals, []*asserts.ModelSnap{
   670  		model.KernelSnap(),
   671  		model.BaseSnap(),
   672  		model.GadgetSnap(),
   673  	})
   674  	snaps := model.SnapsWithoutEssential()
   675  	c.Check(snaps, DeepEquals, []*asserts.ModelSnap{
   676  		{
   677  			Name:           "other-base",
   678  			SnapID:         "otherbasedididididididididididid",
   679  			SnapType:       "base",
   680  			Modes:          []string{"run"},
   681  			DefaultChannel: "latest/stable",
   682  			Presence:       "required",
   683  		},
   684  		{
   685  			Name:           "nm",
   686  			SnapID:         "nmididididididididididididididid",
   687  			SnapType:       "app",
   688  			Modes:          []string{"ephemeral", "run"},
   689  			DefaultChannel: "1.0",
   690  			Presence:       "required",
   691  		},
   692  		{
   693  			Name:           "myapp",
   694  			SnapID:         "myappdididididididididididididid",
   695  			SnapType:       "app",
   696  			Modes:          []string{"run"},
   697  			DefaultChannel: "2.0",
   698  			Presence:       "required",
   699  		},
   700  		{
   701  			Name:           "myappopt",
   702  			SnapID:         "myappoptidididididididididididid",
   703  			SnapType:       "app",
   704  			Modes:          []string{"run"},
   705  			DefaultChannel: "latest/stable",
   706  			Presence:       "optional",
   707  		},
   708  	})
   709  	// essential snaps included
   710  	reqSnaps := naming.NewSnapSet(model.RequiredWithEssentialSnaps())
   711  	for _, e := range essentialSnaps {
   712  		c.Check(reqSnaps.Contains(e), Equals, true)
   713  	}
   714  	for _, s := range snaps {
   715  		c.Check(reqSnaps.Contains(s), Equals, s.Presence == "required")
   716  	}
   717  	c.Check(reqSnaps.Size(), Equals, len(essentialSnaps)+len(snaps)-1)
   718  	// essential snaps excluded
   719  	noEssential := naming.NewSnapSet(model.RequiredNoEssentialSnaps())
   720  	for _, e := range essentialSnaps {
   721  		c.Check(noEssential.Contains(e), Equals, false)
   722  	}
   723  	for _, s := range snaps {
   724  		c.Check(noEssential.Contains(s), Equals, s.Presence == "required")
   725  	}
   726  	c.Check(noEssential.Size(), Equals, len(snaps)-1)
   727  
   728  	c.Check(model.SystemUserAuthority(), HasLen, 0)
   729  	c.Check(model.SerialAuthority(), DeepEquals, []string{"brand-id1"})
   730  }
   731  
   732  func (mods *modelSuite) TestCore20ExplictBootBase(c *C) {
   733  	encoded := strings.Replace(core20ModelExample, "TSLINE", mods.tsLine, 1)
   734  	encoded = strings.Replace(encoded, "OTHER", `  -
   735      name: core20
   736      id: core20ididididididididididididid
   737      type: base
   738      default-channel: latest/candidate
   739  `, 1)
   740  	a, err := asserts.Decode([]byte(encoded))
   741  	c.Assert(err, IsNil)
   742  	c.Check(a.Type(), Equals, asserts.ModelType)
   743  	model := a.(*asserts.Model)
   744  	c.Check(model.BaseSnap(), DeepEquals, &asserts.ModelSnap{
   745  		Name:           "core20",
   746  		SnapID:         "core20ididididididididididididid",
   747  		SnapType:       "base",
   748  		Modes:          []string{"run", "ephemeral"},
   749  		DefaultChannel: "latest/candidate",
   750  		Presence:       "required",
   751  	})
   752  }
   753  
   754  func (mods *modelSuite) TestCore20ExplictSnapd(c *C) {
   755  	encoded := strings.Replace(core20ModelExample, "TSLINE", mods.tsLine, 1)
   756  	encoded = strings.Replace(encoded, "OTHER", `  -
   757      name: snapd
   758      id: snapdidididididididididididididd
   759      type: snapd
   760      default-channel: latest/edge
   761  `, 1)
   762  	a, err := asserts.Decode([]byte(encoded))
   763  	c.Assert(err, IsNil)
   764  	c.Check(a.Type(), Equals, asserts.ModelType)
   765  	model := a.(*asserts.Model)
   766  	snapdSnap := model.EssentialSnaps()[0]
   767  	c.Check(snapdSnap, DeepEquals, &asserts.ModelSnap{
   768  		Name:           "snapd",
   769  		SnapID:         "snapdidididididididididididididd",
   770  		SnapType:       "snapd",
   771  		Modes:          []string{"run", "ephemeral"},
   772  		DefaultChannel: "latest/edge",
   773  		Presence:       "required",
   774  	})
   775  }
   776  
   777  func (mods *modelSuite) TestCore20GradeOptionalDefaultSigned(c *C) {
   778  	encoded := strings.Replace(core20ModelExample, "TSLINE", mods.tsLine, 1)
   779  	encoded = strings.Replace(encoded, "OTHER", "", 1)
   780  	encoded = strings.Replace(encoded, "grade: secured\n", "", 1)
   781  
   782  	a, err := asserts.Decode([]byte(encoded))
   783  	c.Assert(err, IsNil)
   784  	c.Check(a.Type(), Equals, asserts.ModelType)
   785  	model := a.(*asserts.Model)
   786  	c.Check(model.Grade(), Equals, asserts.ModelSigned)
   787  }
   788  
   789  func (mods *modelSuite) TestCore20ValidGrades(c *C) {
   790  	encoded := strings.Replace(core20ModelExample, "TSLINE", mods.tsLine, 1)
   791  	encoded = strings.Replace(encoded, "OTHER", "", 1)
   792  
   793  	for _, grade := range []string{"signed", "secured", "dangerous"} {
   794  		ex := strings.Replace(encoded, "grade: secured\n", fmt.Sprintf("grade: %s\n", grade), 1)
   795  		a, err := asserts.Decode([]byte(ex))
   796  		c.Assert(err, IsNil)
   797  		c.Check(a.Type(), Equals, asserts.ModelType)
   798  		model := a.(*asserts.Model)
   799  		c.Check(string(model.Grade()), Equals, grade)
   800  	}
   801  }
   802  
   803  func (mods *modelSuite) TestModelGradeCode(c *C) {
   804  	for i, grade := range []asserts.ModelGrade{asserts.ModelGradeUnset, asserts.ModelDangerous, asserts.ModelSigned, asserts.ModelSecured} {
   805  		// unset is represented as zero
   806  		code := 0
   807  		if i > 0 {
   808  			// have some space between grades to add new ones
   809  			n := (i - 1) * 8
   810  			if n == 0 {
   811  				n = 1 // dangerous
   812  			}
   813  			// lower 16 bits are reserved
   814  			code = n << 16
   815  		}
   816  		c.Check(grade.Code(), Equals, uint32(code))
   817  	}
   818  }
   819  
   820  func (mods *modelSuite) TestCore20GradeDangerous(c *C) {
   821  	encoded := strings.Replace(core20ModelExample, "TSLINE", mods.tsLine, 1)
   822  	encoded = strings.Replace(encoded, "OTHER", "", 1)
   823  	encoded = strings.Replace(encoded, "grade: secured\n", "grade: dangerous\n", 1)
   824  	// snap ids are optional with grade dangerous to allow working
   825  	// with local/not pushed yet to the store snaps
   826  	encoded = strings.Replace(encoded, "    id: myappdididididididididididididid\n", "", 1)
   827  	encoded = strings.Replace(encoded, "    id: brandgadgetdidididididididididid\n", "", 1)
   828  	a, err := asserts.Decode([]byte(encoded))
   829  	c.Assert(err, IsNil)
   830  	c.Check(a.Type(), Equals, asserts.ModelType)
   831  	model := a.(*asserts.Model)
   832  	c.Check(model.Grade(), Equals, asserts.ModelDangerous)
   833  	snaps := model.SnapsWithoutEssential()
   834  	c.Check(snaps[len(snaps)-2], DeepEquals, &asserts.ModelSnap{
   835  		Name:           "myapp",
   836  		SnapType:       "app",
   837  		Modes:          []string{"run"},
   838  		DefaultChannel: "2.0",
   839  		Presence:       "required",
   840  	})
   841  }
   842  
   843  func (mods *modelSuite) TestCore20DecodeInvalid(c *C) {
   844  	encoded := strings.Replace(core20ModelExample, "TSLINE", mods.tsLine, 1)
   845  
   846  	snapsStanza := encoded[strings.Index(encoded, "snaps:"):strings.Index(encoded, "grade:")]
   847  
   848  	invalidTests := []struct{ original, invalid, expectedErr string }{
   849  		{"base: core20\n", "", `"base" header is mandatory`},
   850  		{"base: core20\n", "base: alt-base\n", `cannot specify not well-known base "alt-base" without a corresponding "snaps" header entry`},
   851  		{"OTHER", "classic: true\n", `cannot use extended snaps header for a classic model \(yet\)`},
   852  		{snapsStanza, "snaps: snap\n", `"snaps" header must be a list of maps`},
   853  		{snapsStanza, "snaps:\n  - snap\n", `"snaps" header must be a list of maps`},
   854  		{"name: myapp\n", "other: 1\n", `"name" of snap is mandatory`},
   855  		{"name: myapp\n", "name: myapp_2\n", `invalid snap name "myapp_2"`},
   856  		{"id: myappdididididididididididididid\n", "id: 2\n", `"id" of snap "myapp" contains invalid characters: "2"`},
   857  		{"    id: myappdididididididididididididid\n", "", `"id" of snap "myapp" is mandatory for secured grade model`},
   858  		{"type: gadget\n", "type:\n      - g\n", `"type" of snap "brand-gadget" must be a string`},
   859  		{"type: app\n", "type: thing\n", `"type" of snap "myappopt" must be one of must be one of app|base|gadget|kernel|core|snapd`},
   860  		{"modes:\n      - run\n", "modes: run\n", `"modes" of snap "other-base" must be a list of strings`},
   861  		{"default-channel: 20\n", "default-channel: edge\n", `default channel for snap "baz-linux" must specify a track`},
   862  		{"default-channel: 2.0\n", "default-channel:\n      - x\n", `"default-channel" of snap "myapp" must be a string`},
   863  		{"default-channel: 2.0\n", "default-channel: 2.0/xyz/z\n", `invalid default channel for snap "myapp": invalid risk in channel name: 2.0/xyz/z`},
   864  		{"presence: optional\n", "presence:\n      - opt\n", `"presence" of snap "myappopt" must be a string`},
   865  		{"presence: optional\n", "presence: no\n", `"presence" of snap "myappopt" must be one of must be one of required|optional`},
   866  		{"OTHER", "  -\n    name: myapp\n    id: myappdididididididididididididid\n", `cannot list the same snap "myapp" multiple times`},
   867  		{"OTHER", "  -\n    name: myapp2\n    id: myappdididididididididididididid\n", `cannot specify the same snap id "myappdididididididididididididid" multiple times, specified for snaps "myapp" and "myapp2"`},
   868  		{"OTHER", "  -\n    name: kernel2\n    id: kernel2didididididididididididid\n    type: kernel\n", `cannot specify multiple kernel snaps: "baz-linux" and "kernel2"`},
   869  		{"OTHER", "  -\n    name: gadget2\n    id: gadget2didididididididididididid\n    type: gadget\n", `cannot specify multiple gadget snaps: "brand-gadget" and "gadget2"`},
   870  		{"type: gadget\n", "type: gadget\n    presence: required\n", `essential snaps are always available, cannot specify modes or presence for snap "brand-gadget"`},
   871  		{"type: gadget\n", "type: gadget\n    modes:\n      - run\n", `essential snaps are always available, cannot specify modes or presence for snap "brand-gadget"`},
   872  		{"type: kernel\n", "type: kernel\n    presence: required\n", `essential snaps are always available, cannot specify modes or presence for snap "baz-linux"`},
   873  		{"OTHER", "  -\n    name: core20\n    id: core20ididididididididididididid\n    type: base\n    presence: optional\n", `essential snaps are always available, cannot specify modes or presence for snap "core20"`},
   874  		{"type: gadget\n", "type: app\n", `one "snaps" header entry must specify the model gadget`},
   875  		{"type: kernel\n", "type: app\n", `one "snaps" header entry must specify the model kernel`},
   876  		{"OTHER", "  -\n    name: core20\n    id: core20ididididididididididididid\n    type: app\n", `boot base "core20" must specify type "base", not "app"`},
   877  		{"OTHER", "kernel: foo\n", `cannot specify separate "kernel" header once using the extended snaps header`},
   878  		{"OTHER", "gadget: foo\n", `cannot specify separate "gadget" header once using the extended snaps header`},
   879  		{"OTHER", "required-snaps:\n  - foo\n", `cannot specify separate "required-snaps" header once using the extended snaps header`},
   880  		{"grade: secured\n", "grade: foo\n", `grade for model must be secured|signed|dangerous`},
   881  	}
   882  	for _, test := range invalidTests {
   883  		invalid := strings.Replace(encoded, test.original, test.invalid, 1)
   884  		invalid = strings.Replace(invalid, "OTHER", "", 1)
   885  		_, err := asserts.Decode([]byte(invalid))
   886  		c.Check(err, ErrorMatches, modelErrPrefix+test.expectedErr)
   887  	}
   888  }