github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/boot/bootchain_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 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 boot_test
    21  
    22  import (
    23  	"encoding/json"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/asserts"
    31  	"github.com/snapcore/snapd/boot"
    32  	"github.com/snapcore/snapd/bootloader"
    33  	"github.com/snapcore/snapd/dirs"
    34  	"github.com/snapcore/snapd/secboot"
    35  	"github.com/snapcore/snapd/testutil"
    36  )
    37  
    38  type bootchainSuite struct {
    39  	testutil.BaseTest
    40  
    41  	rootDir string
    42  }
    43  
    44  var _ = Suite(&bootchainSuite{})
    45  
    46  func (s *bootchainSuite) SetUpTest(c *C) {
    47  	s.BaseTest.SetUpTest(c)
    48  	s.rootDir = c.MkDir()
    49  	s.AddCleanup(func() { dirs.SetRootDir("/") })
    50  	dirs.SetRootDir(s.rootDir)
    51  
    52  	c.Assert(os.MkdirAll(filepath.Join(dirs.SnapBootAssetsDir), 0755), IsNil)
    53  }
    54  
    55  func (s *bootchainSuite) TestBootAssetLess(c *C) {
    56  	for _, tc := range []struct {
    57  		l, r *boot.BootAsset
    58  		exp  bool
    59  	}{
    60  		{&boot.BootAsset{Role: "recovery"}, &boot.BootAsset{Role: "run"}, true},
    61  		{&boot.BootAsset{Role: "run"}, &boot.BootAsset{Role: "recovery"}, false},
    62  		{&boot.BootAsset{Name: "1"}, &boot.BootAsset{Name: "11"}, true},
    63  		{&boot.BootAsset{Name: "11"}, &boot.BootAsset{Name: "1"}, false},
    64  		{&boot.BootAsset{Hashes: []string{"11"}}, &boot.BootAsset{Hashes: []string{"11", "11"}}, true},
    65  		{&boot.BootAsset{Hashes: []string{"11"}}, &boot.BootAsset{Hashes: []string{"12"}}, true},
    66  	} {
    67  		less := boot.BootAssetLess(tc.l, tc.r)
    68  		c.Check(less, Equals, tc.exp, Commentf("expected %v got %v for:\nl:%v\nr:%v", tc.exp, less, tc.l, tc.r))
    69  	}
    70  }
    71  
    72  func (s *bootchainSuite) TestBootAssetsPredictable(c *C) {
    73  	// by role
    74  	ba := boot.BootAsset{
    75  		Role: bootloader.RoleRunMode, Name: "list", Hashes: []string{"b", "a"},
    76  	}
    77  	pred := boot.ToPredictableBootAsset(&ba)
    78  	c.Check(pred, DeepEquals, &boot.BootAsset{
    79  		Role: bootloader.RoleRunMode, Name: "list", Hashes: []string{"a", "b"},
    80  	})
    81  	// original structure is not changed
    82  	c.Check(ba, DeepEquals, boot.BootAsset{
    83  		Role: bootloader.RoleRunMode, Name: "list", Hashes: []string{"b", "a"},
    84  	})
    85  
    86  	// try to make a predictable struct predictable once more
    87  	predAgain := boot.ToPredictableBootAsset(pred)
    88  	c.Check(predAgain, DeepEquals, pred)
    89  
    90  	baNil := boot.ToPredictableBootAsset(nil)
    91  	c.Check(baNil, IsNil)
    92  }
    93  
    94  func (s *bootchainSuite) TestBootChainMarshalOnlyAssets(c *C) {
    95  	pbNil := boot.ToPredictableBootChain(nil)
    96  	c.Check(pbNil, IsNil)
    97  
    98  	bc := &boot.BootChain{
    99  		AssetChain: []boot.BootAsset{
   100  			{Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"b"}},
   101  			{Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"e", "d"}},
   102  			{Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"d", "c"}},
   103  			{Role: bootloader.RoleRunMode, Name: "1oader", Hashes: []string{"e", "d"}},
   104  			{Role: bootloader.RoleRunMode, Name: "0oader", Hashes: []string{"z", "x"}},
   105  		},
   106  	}
   107  
   108  	predictableBc := boot.ToPredictableBootChain(bc)
   109  
   110  	c.Check(predictableBc, DeepEquals, &boot.BootChain{
   111  		// assets not reordered
   112  		AssetChain: []boot.BootAsset{
   113  			// hash lists are sorted
   114  			{Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"b"}},
   115  			{Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"d", "e"}},
   116  			{Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"c", "d"}},
   117  			{Role: bootloader.RoleRunMode, Name: "1oader", Hashes: []string{"d", "e"}},
   118  			{Role: bootloader.RoleRunMode, Name: "0oader", Hashes: []string{"x", "z"}},
   119  		},
   120  	})
   121  
   122  	// already predictable, but try again
   123  	alreadySortedBc := boot.ToPredictableBootChain(predictableBc)
   124  	c.Check(alreadySortedBc, DeepEquals, predictableBc)
   125  
   126  	// boot chain with 2 identical assets
   127  	bcIdenticalAssets := &boot.BootChain{
   128  		AssetChain: []boot.BootAsset{
   129  			{Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"z"}},
   130  			{Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"z"}},
   131  		},
   132  	}
   133  	sortedBcIdentical := boot.ToPredictableBootChain(bcIdenticalAssets)
   134  	c.Check(sortedBcIdentical, DeepEquals, bcIdenticalAssets)
   135  }
   136  
   137  func (s *bootchainSuite) TestBootChainMarshalFull(c *C) {
   138  	bc := &boot.BootChain{
   139  		BrandID:        "mybrand",
   140  		Model:          "foo",
   141  		Grade:          "dangerous",
   142  		ModelSignKeyID: "my-key-id",
   143  		// asset chain does not get sorted when marshaling
   144  		AssetChain: []boot.BootAsset{
   145  			// hash list will get sorted
   146  			{Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"b", "a"}},
   147  			{Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"d"}},
   148  			{Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"c", "d"}},
   149  		},
   150  		Kernel:         "pc-kernel",
   151  		KernelRevision: "1234",
   152  		KernelCmdlines: []string{`foo=bar baz=0x123`, `a=1`},
   153  	}
   154  
   155  	kernelBootFile := bootloader.NewBootFile("pc-kernel", "/foo", bootloader.RoleRecovery)
   156  	bc.SetKernelBootFile(kernelBootFile)
   157  
   158  	expectedPredictableBc := &boot.BootChain{
   159  		BrandID:        "mybrand",
   160  		Model:          "foo",
   161  		Grade:          "dangerous",
   162  		ModelSignKeyID: "my-key-id",
   163  		// assets are not reordered
   164  		AssetChain: []boot.BootAsset{
   165  			// hash lists are sorted
   166  			{Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"a", "b"}},
   167  			{Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"d"}},
   168  			{Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"c", "d"}},
   169  		},
   170  		Kernel:         "pc-kernel",
   171  		KernelRevision: "1234",
   172  		KernelCmdlines: []string{`a=1`, `foo=bar baz=0x123`},
   173  	}
   174  	// those can't be set directly, but are copied as well
   175  	expectedPredictableBc.SetKernelBootFile(kernelBootFile)
   176  
   177  	predictableBc := boot.ToPredictableBootChain(bc)
   178  	c.Check(predictableBc, DeepEquals, expectedPredictableBc)
   179  
   180  	d, err := json.Marshal(predictableBc)
   181  	c.Assert(err, IsNil)
   182  	c.Check(string(d), Equals, `{"brand-id":"mybrand","model":"foo","grade":"dangerous","model-sign-key-id":"my-key-id","asset-chain":[{"role":"recovery","name":"shim","hashes":["a","b"]},{"role":"recovery","name":"loader","hashes":["d"]},{"role":"run-mode","name":"loader","hashes":["c","d"]}],"kernel":"pc-kernel","kernel-revision":"1234","kernel-cmdlines":["a=1","foo=bar baz=0x123"]}`)
   183  	expectedOriginal := &boot.BootChain{
   184  		BrandID:        "mybrand",
   185  		Model:          "foo",
   186  		Grade:          "dangerous",
   187  		ModelSignKeyID: "my-key-id",
   188  		AssetChain: []boot.BootAsset{
   189  			{Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"b", "a"}},
   190  			{Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"d"}},
   191  			{Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"c", "d"}},
   192  		},
   193  		Kernel:         "pc-kernel",
   194  		KernelRevision: "1234",
   195  		KernelCmdlines: []string{`foo=bar baz=0x123`, `a=1`},
   196  	}
   197  	expectedOriginal.SetKernelBootFile(kernelBootFile)
   198  	// original structure has not been modified
   199  	c.Check(bc, DeepEquals, expectedOriginal)
   200  }
   201  
   202  func (s *bootchainSuite) TestPredictableBootChainsEqualForReseal(c *C) {
   203  	var pbNil boot.PredictableBootChains
   204  
   205  	c.Check(boot.PredictableBootChainsEqualForReseal(pbNil, pbNil), Equals, boot.BootChainEquivalent)
   206  
   207  	bcJustOne := []boot.BootChain{
   208  		{
   209  			BrandID:        "mybrand",
   210  			Model:          "foo",
   211  			Grade:          "dangerous",
   212  			ModelSignKeyID: "my-key-id",
   213  			AssetChain: []boot.BootAsset{
   214  				{Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"b", "a"}},
   215  				{Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"d"}},
   216  				{Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"c", "d"}},
   217  			},
   218  			Kernel:         "pc-kernel-other",
   219  			KernelRevision: "1234",
   220  			KernelCmdlines: []string{`foo`},
   221  		},
   222  	}
   223  	pbJustOne := boot.ToPredictableBootChains(bcJustOne)
   224  	// equal with self
   225  	c.Check(boot.PredictableBootChainsEqualForReseal(pbJustOne, pbJustOne), Equals, boot.BootChainEquivalent)
   226  
   227  	// equal with nil?
   228  	c.Check(boot.PredictableBootChainsEqualForReseal(pbJustOne, pbNil), Equals, boot.BootChainDifferent)
   229  
   230  	bcMoreAssets := []boot.BootChain{
   231  		{
   232  			BrandID:        "mybrand",
   233  			Model:          "foo",
   234  			Grade:          "dangerous",
   235  			ModelSignKeyID: "my-key-id",
   236  			AssetChain: []boot.BootAsset{
   237  				{Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"a", "b"}},
   238  				{Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"d"}},
   239  			},
   240  			Kernel:         "pc-kernel-recovery",
   241  			KernelRevision: "1234",
   242  			KernelCmdlines: []string{`foo`},
   243  		}, {
   244  			BrandID:        "mybrand",
   245  			Model:          "foo",
   246  			Grade:          "dangerous",
   247  			ModelSignKeyID: "my-key-id",
   248  			AssetChain: []boot.BootAsset{
   249  				{Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"a", "b"}},
   250  				{Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"d"}},
   251  				{Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"c", "d"}},
   252  			},
   253  			Kernel:         "pc-kernel-other",
   254  			KernelRevision: "1234",
   255  			KernelCmdlines: []string{`foo`},
   256  		},
   257  	}
   258  
   259  	pbMoreAssets := boot.ToPredictableBootChains(bcMoreAssets)
   260  
   261  	c.Check(boot.PredictableBootChainsEqualForReseal(pbMoreAssets, pbJustOne), Equals, boot.BootChainDifferent)
   262  	// with self
   263  	c.Check(boot.PredictableBootChainsEqualForReseal(pbMoreAssets, pbMoreAssets), Equals, boot.BootChainEquivalent)
   264  	// chains composed of respective elements are not equal
   265  	c.Check(boot.PredictableBootChainsEqualForReseal(
   266  		[]boot.BootChain{pbMoreAssets[0]},
   267  		[]boot.BootChain{pbMoreAssets[1]}),
   268  		Equals, boot.BootChainDifferent)
   269  
   270  	// unrevisioned/unasserted kernels
   271  	bcUnrevOne := []boot.BootChain{pbJustOne[0]}
   272  	bcUnrevOne[0].KernelRevision = ""
   273  	pbUnrevOne := boot.ToPredictableBootChains(bcUnrevOne)
   274  	// soundness
   275  	c.Check(boot.PredictableBootChainsEqualForReseal(pbJustOne, pbJustOne), Equals, boot.BootChainEquivalent)
   276  	// never equal even with self because of unrevisioned
   277  	c.Check(boot.PredictableBootChainsEqualForReseal(pbJustOne, pbUnrevOne), Equals, boot.BootChainDifferent)
   278  	c.Check(boot.PredictableBootChainsEqualForReseal(pbUnrevOne, pbUnrevOne), Equals, boot.BootChainUnrevisioned)
   279  
   280  	bcUnrevMoreAssets := []boot.BootChain{pbMoreAssets[0], pbMoreAssets[1]}
   281  	bcUnrevMoreAssets[1].KernelRevision = ""
   282  	pbUnrevMoreAssets := boot.ToPredictableBootChains(bcUnrevMoreAssets)
   283  	// never equal even with self because of unrevisioned
   284  	c.Check(boot.PredictableBootChainsEqualForReseal(pbUnrevMoreAssets, pbMoreAssets), Equals, boot.BootChainDifferent)
   285  	c.Check(boot.PredictableBootChainsEqualForReseal(pbUnrevMoreAssets, pbUnrevMoreAssets), Equals, boot.BootChainUnrevisioned)
   286  }
   287  
   288  func (s *bootchainSuite) TestPredictableBootChainsFullMarshal(c *C) {
   289  	// chains will be sorted
   290  	chains := []boot.BootChain{
   291  		{
   292  			BrandID:        "mybrand",
   293  			Model:          "foo",
   294  			Grade:          "signed",
   295  			ModelSignKeyID: "my-key-id",
   296  			AssetChain: []boot.BootAsset{
   297  				// hashes will be sorted
   298  				{Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"x", "y"}},
   299  				{Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"c", "d"}},
   300  				{Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"z", "x"}},
   301  			},
   302  			Kernel:         "pc-kernel-other",
   303  			KernelRevision: "2345",
   304  			KernelCmdlines: []string{`snapd_recovery_mode=run foo`},
   305  		}, {
   306  			BrandID:        "mybrand",
   307  			Model:          "foo",
   308  			Grade:          "dangerous",
   309  			ModelSignKeyID: "my-key-id",
   310  			AssetChain: []boot.BootAsset{
   311  				// hashes will be sorted
   312  				{Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"y", "x"}},
   313  				{Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"c", "d"}},
   314  				{Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"b", "a"}},
   315  			},
   316  			Kernel:         "pc-kernel-other",
   317  			KernelRevision: "1234",
   318  			KernelCmdlines: []string{`snapd_recovery_mode=run foo`},
   319  		}, {
   320  			// recovery system
   321  			BrandID:        "mybrand",
   322  			Model:          "foo",
   323  			Grade:          "dangerous",
   324  			ModelSignKeyID: "my-key-id",
   325  			AssetChain: []boot.BootAsset{
   326  				// hashes will be sorted
   327  				{Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"y", "x"}},
   328  				{Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"c", "d"}},
   329  			},
   330  			Kernel:         "pc-kernel-other",
   331  			KernelRevision: "12",
   332  			KernelCmdlines: []string{
   333  				// will be sorted
   334  				`snapd_recovery_mode=recover snapd_recovery_system=23 foo`,
   335  				`snapd_recovery_mode=recover snapd_recovery_system=12 foo`,
   336  			},
   337  		},
   338  	}
   339  
   340  	predictableChains := boot.ToPredictableBootChains(chains)
   341  	d, err := json.Marshal(predictableChains)
   342  	c.Assert(err, IsNil)
   343  
   344  	var data []map[string]interface{}
   345  	err = json.Unmarshal(d, &data)
   346  	c.Assert(err, IsNil)
   347  	c.Check(data, DeepEquals, []map[string]interface{}{
   348  		{
   349  			"model":             "foo",
   350  			"brand-id":          "mybrand",
   351  			"grade":             "dangerous",
   352  			"model-sign-key-id": "my-key-id",
   353  			"kernel":            "pc-kernel-other",
   354  			"kernel-revision":   "12",
   355  			"kernel-cmdlines": []interface{}{
   356  				`snapd_recovery_mode=recover snapd_recovery_system=12 foo`,
   357  				`snapd_recovery_mode=recover snapd_recovery_system=23 foo`,
   358  			},
   359  			"asset-chain": []interface{}{
   360  				map[string]interface{}{"role": "recovery", "name": "shim", "hashes": []interface{}{"x", "y"}},
   361  				map[string]interface{}{"role": "recovery", "name": "loader", "hashes": []interface{}{"c", "d"}},
   362  			},
   363  		}, {
   364  			"model":             "foo",
   365  			"brand-id":          "mybrand",
   366  			"grade":             "dangerous",
   367  			"model-sign-key-id": "my-key-id",
   368  			"kernel":            "pc-kernel-other",
   369  			"kernel-revision":   "1234",
   370  			"kernel-cmdlines":   []interface{}{"snapd_recovery_mode=run foo"},
   371  			"asset-chain": []interface{}{
   372  				map[string]interface{}{"role": "recovery", "name": "shim", "hashes": []interface{}{"x", "y"}},
   373  				map[string]interface{}{"role": "recovery", "name": "loader", "hashes": []interface{}{"c", "d"}},
   374  				map[string]interface{}{"role": "run-mode", "name": "loader", "hashes": []interface{}{"a", "b"}},
   375  			},
   376  		}, {
   377  			"model":             "foo",
   378  			"brand-id":          "mybrand",
   379  			"grade":             "signed",
   380  			"model-sign-key-id": "my-key-id",
   381  			"kernel":            "pc-kernel-other",
   382  			"kernel-revision":   "2345",
   383  			"kernel-cmdlines":   []interface{}{"snapd_recovery_mode=run foo"},
   384  			"asset-chain": []interface{}{
   385  				map[string]interface{}{"role": "recovery", "name": "shim", "hashes": []interface{}{"x", "y"}},
   386  				map[string]interface{}{"role": "recovery", "name": "loader", "hashes": []interface{}{"c", "d"}},
   387  				map[string]interface{}{"role": "run-mode", "name": "loader", "hashes": []interface{}{"x", "z"}},
   388  			},
   389  		},
   390  	})
   391  }
   392  
   393  func (s *bootchainSuite) TestPredictableBootChainsFields(c *C) {
   394  	chainsNil := boot.ToPredictableBootChains(nil)
   395  	c.Check(chainsNil, IsNil)
   396  
   397  	justOne := []boot.BootChain{
   398  		{
   399  			BrandID:        "mybrand",
   400  			Model:          "foo",
   401  			Grade:          "signed",
   402  			ModelSignKeyID: "my-key-id",
   403  			Kernel:         "pc-kernel-other",
   404  			KernelRevision: "2345",
   405  			KernelCmdlines: []string{`foo`},
   406  		},
   407  	}
   408  	predictableJustOne := boot.ToPredictableBootChains(justOne)
   409  	c.Check(predictableJustOne, DeepEquals, boot.PredictableBootChains(justOne))
   410  
   411  	chainsGrade := []boot.BootChain{
   412  		{
   413  			Grade: "signed",
   414  		}, {
   415  			Grade: "dangerous",
   416  		},
   417  	}
   418  	c.Check(boot.ToPredictableBootChains(chainsGrade), DeepEquals, boot.PredictableBootChains{
   419  		{
   420  			Grade: "dangerous",
   421  		}, {
   422  			Grade: "signed",
   423  		},
   424  	})
   425  
   426  	chainsKernel := []boot.BootChain{
   427  		{
   428  			Grade:  "dangerous",
   429  			Kernel: "foo",
   430  		}, {
   431  			Grade:  "dangerous",
   432  			Kernel: "bar",
   433  		},
   434  	}
   435  	c.Check(boot.ToPredictableBootChains(chainsKernel), DeepEquals, boot.PredictableBootChains{
   436  		{
   437  			Grade:  "dangerous",
   438  			Kernel: "bar",
   439  		}, {
   440  			Grade:  "dangerous",
   441  			Kernel: "foo",
   442  		},
   443  	})
   444  
   445  	chainsKernelRevision := []boot.BootChain{
   446  		{
   447  			Kernel:         "foo",
   448  			KernelRevision: "9",
   449  		}, {
   450  			Kernel:         "foo",
   451  			KernelRevision: "21",
   452  		},
   453  	}
   454  	c.Check(boot.ToPredictableBootChains(chainsKernelRevision), DeepEquals, boot.PredictableBootChains{
   455  		{
   456  			Kernel:         "foo",
   457  			KernelRevision: "21",
   458  		}, {
   459  			Kernel:         "foo",
   460  			KernelRevision: "9",
   461  		},
   462  	})
   463  
   464  	chainsCmdline := []boot.BootChain{
   465  		{
   466  			Grade:          "dangerous",
   467  			Kernel:         "foo",
   468  			KernelCmdlines: []string{`panic=1`},
   469  		}, {
   470  			Grade:          "dangerous",
   471  			Kernel:         "foo",
   472  			KernelCmdlines: []string{`a`},
   473  		},
   474  	}
   475  	c.Check(boot.ToPredictableBootChains(chainsCmdline), DeepEquals, boot.PredictableBootChains{
   476  		{
   477  			Grade:          "dangerous",
   478  			Kernel:         "foo",
   479  			KernelCmdlines: []string{`a`},
   480  		}, {
   481  			Grade:          "dangerous",
   482  			Kernel:         "foo",
   483  			KernelCmdlines: []string{`panic=1`},
   484  		},
   485  	})
   486  
   487  	chainsModel := []boot.BootChain{
   488  		{
   489  			Model:          "fridge",
   490  			Grade:          "dangerous",
   491  			Kernel:         "foo",
   492  			KernelCmdlines: []string{`panic=1`},
   493  		}, {
   494  			Model:          "box",
   495  			Grade:          "dangerous",
   496  			Kernel:         "foo",
   497  			KernelCmdlines: []string{`panic=1`},
   498  		},
   499  	}
   500  	c.Check(boot.ToPredictableBootChains(chainsModel), DeepEquals, boot.PredictableBootChains{
   501  		{
   502  			Model:          "box",
   503  			Grade:          "dangerous",
   504  			Kernel:         "foo",
   505  			KernelCmdlines: []string{`panic=1`},
   506  		}, {
   507  			Model:          "fridge",
   508  			Grade:          "dangerous",
   509  			Kernel:         "foo",
   510  			KernelCmdlines: []string{`panic=1`},
   511  		},
   512  	})
   513  
   514  	chainsBrand := []boot.BootChain{
   515  		{
   516  			BrandID:        "foo",
   517  			Model:          "box",
   518  			Grade:          "dangerous",
   519  			Kernel:         "foo",
   520  			KernelCmdlines: []string{`panic=1`},
   521  		}, {
   522  			BrandID:        "acme",
   523  			Model:          "box",
   524  			Grade:          "dangerous",
   525  			Kernel:         "foo",
   526  			KernelCmdlines: []string{`panic=1`},
   527  		},
   528  	}
   529  	c.Check(boot.ToPredictableBootChains(chainsBrand), DeepEquals, boot.PredictableBootChains{
   530  		{
   531  			BrandID:        "acme",
   532  			Model:          "box",
   533  			Grade:          "dangerous",
   534  			Kernel:         "foo",
   535  			KernelCmdlines: []string{`panic=1`},
   536  		}, {
   537  			BrandID:        "foo",
   538  			Model:          "box",
   539  			Grade:          "dangerous",
   540  			Kernel:         "foo",
   541  			KernelCmdlines: []string{`panic=1`},
   542  		},
   543  	})
   544  
   545  	chainsKeyID := []boot.BootChain{
   546  		{
   547  			BrandID:        "foo",
   548  			Model:          "box",
   549  			Grade:          "dangerous",
   550  			Kernel:         "foo",
   551  			KernelCmdlines: []string{`panic=1`},
   552  			ModelSignKeyID: "key-2",
   553  		}, {
   554  			BrandID:        "foo",
   555  			Model:          "box",
   556  			Grade:          "dangerous",
   557  			Kernel:         "foo",
   558  			KernelCmdlines: []string{`panic=1`},
   559  			ModelSignKeyID: "key-1",
   560  		},
   561  	}
   562  	c.Check(boot.ToPredictableBootChains(chainsKeyID), DeepEquals, boot.PredictableBootChains{
   563  		{
   564  			BrandID:        "foo",
   565  			Model:          "box",
   566  			Grade:          "dangerous",
   567  			Kernel:         "foo",
   568  			KernelCmdlines: []string{`panic=1`},
   569  			ModelSignKeyID: "key-1",
   570  		}, {
   571  			BrandID:        "foo",
   572  			Model:          "box",
   573  			Grade:          "dangerous",
   574  			Kernel:         "foo",
   575  			KernelCmdlines: []string{`panic=1`},
   576  			ModelSignKeyID: "key-2",
   577  		},
   578  	})
   579  
   580  	chainsAssets := []boot.BootChain{
   581  		{
   582  			BrandID:        "foo",
   583  			Model:          "box",
   584  			Grade:          "dangerous",
   585  			ModelSignKeyID: "key-1",
   586  			AssetChain: []boot.BootAsset{
   587  				// will be sorted
   588  				{Hashes: []string{"b", "a"}},
   589  			},
   590  			Kernel:         "foo",
   591  			KernelCmdlines: []string{`panic=1`},
   592  		}, {
   593  			BrandID:        "foo",
   594  			Model:          "box",
   595  			Grade:          "dangerous",
   596  			ModelSignKeyID: "key-1",
   597  			AssetChain: []boot.BootAsset{
   598  				{Hashes: []string{"b"}},
   599  			},
   600  			Kernel:         "foo",
   601  			KernelCmdlines: []string{`panic=1`},
   602  		},
   603  	}
   604  	c.Check(boot.ToPredictableBootChains(chainsAssets), DeepEquals, boot.PredictableBootChains{
   605  		{
   606  			BrandID:        "foo",
   607  			Model:          "box",
   608  			Grade:          "dangerous",
   609  			ModelSignKeyID: "key-1",
   610  			AssetChain: []boot.BootAsset{
   611  				{Hashes: []string{"b"}},
   612  			},
   613  			Kernel:         "foo",
   614  			KernelCmdlines: []string{`panic=1`},
   615  		}, {
   616  			BrandID:        "foo",
   617  			Model:          "box",
   618  			Grade:          "dangerous",
   619  			ModelSignKeyID: "key-1",
   620  			AssetChain: []boot.BootAsset{
   621  				{Hashes: []string{"a", "b"}},
   622  			},
   623  			Kernel:         "foo",
   624  			KernelCmdlines: []string{`panic=1`},
   625  		},
   626  	})
   627  
   628  	chainsFewerAssets := []boot.BootChain{
   629  		{
   630  			AssetChain: []boot.BootAsset{
   631  				{Hashes: []string{"b", "a"}},
   632  				{Hashes: []string{"c", "d"}},
   633  			},
   634  		}, {
   635  			AssetChain: []boot.BootAsset{
   636  				{Hashes: []string{"b"}},
   637  			},
   638  		},
   639  	}
   640  	c.Check(boot.ToPredictableBootChains(chainsFewerAssets), DeepEquals, boot.PredictableBootChains{
   641  		{
   642  			AssetChain: []boot.BootAsset{
   643  				{Hashes: []string{"b"}},
   644  			},
   645  		}, {
   646  			AssetChain: []boot.BootAsset{
   647  				{Hashes: []string{"a", "b"}},
   648  				{Hashes: []string{"c", "d"}},
   649  			},
   650  		},
   651  	})
   652  
   653  	// not confused if 2 chains are identical
   654  	chainsIdenticalAssets := []boot.BootChain{
   655  		{
   656  			BrandID:        "foo",
   657  			Model:          "box",
   658  			ModelSignKeyID: "key-1",
   659  			AssetChain: []boot.BootAsset{
   660  				{Name: "asset", Hashes: []string{"a", "b"}},
   661  				{Name: "asset", Hashes: []string{"a", "b"}},
   662  			},
   663  			Grade:          "dangerous",
   664  			Kernel:         "foo",
   665  			KernelCmdlines: []string{`panic=1`},
   666  		}, {
   667  			BrandID:        "foo",
   668  			Model:          "box",
   669  			Grade:          "dangerous",
   670  			ModelSignKeyID: "key-1",
   671  			AssetChain: []boot.BootAsset{
   672  				{Name: "asset", Hashes: []string{"a", "b"}},
   673  				{Name: "asset", Hashes: []string{"a", "b"}},
   674  			},
   675  			Kernel:         "foo",
   676  			KernelCmdlines: []string{`panic=1`},
   677  		},
   678  	}
   679  	c.Check(boot.ToPredictableBootChains(chainsIdenticalAssets), DeepEquals, boot.PredictableBootChains(chainsIdenticalAssets))
   680  }
   681  
   682  func (s *bootchainSuite) TestPredictableBootChainsSortOrder(c *C) {
   683  	// check that sort order is model info, assets, kernel, kernel cmdline
   684  
   685  	chains := []boot.BootChain{
   686  		{
   687  			Model: "b",
   688  			AssetChain: []boot.BootAsset{
   689  				{Name: "asset", Hashes: []string{"y"}},
   690  			},
   691  			Kernel:         "k1",
   692  			KernelCmdlines: []string{"cm=1"},
   693  		},
   694  		{
   695  			Model: "b",
   696  			AssetChain: []boot.BootAsset{
   697  				{Name: "asset", Hashes: []string{"y"}},
   698  			},
   699  			Kernel:         "k2",
   700  			KernelCmdlines: []string{"cm=1"},
   701  		},
   702  		{
   703  			Model: "a",
   704  			AssetChain: []boot.BootAsset{
   705  				{Name: "asset", Hashes: []string{"y"}},
   706  			},
   707  			Kernel:         "k1",
   708  			KernelCmdlines: []string{"cm=1"},
   709  		},
   710  		{
   711  			Model: "a",
   712  			AssetChain: []boot.BootAsset{
   713  				{Name: "asset", Hashes: []string{"y"}},
   714  			},
   715  			Kernel:         "k2",
   716  			KernelCmdlines: []string{"cm=1"},
   717  		},
   718  		{
   719  			Model: "b",
   720  			AssetChain: []boot.BootAsset{
   721  				{Name: "asset", Hashes: []string{"y"}},
   722  			},
   723  			Kernel:         "k1",
   724  			KernelCmdlines: []string{"cm=2"},
   725  		},
   726  		{
   727  			Model: "b",
   728  			AssetChain: []boot.BootAsset{
   729  				{Name: "asset", Hashes: []string{"y"}},
   730  			},
   731  			Kernel:         "k2",
   732  			KernelCmdlines: []string{"cm=2"},
   733  		},
   734  		{
   735  			Model: "a",
   736  			AssetChain: []boot.BootAsset{
   737  				{Name: "asset", Hashes: []string{"y"}},
   738  			},
   739  			Kernel:         "k1",
   740  			KernelCmdlines: []string{"cm=2"},
   741  		},
   742  		{
   743  			Model: "a",
   744  			AssetChain: []boot.BootAsset{
   745  				{Name: "asset", Hashes: []string{"y"}},
   746  			},
   747  			Kernel:         "k2",
   748  			KernelCmdlines: []string{"cm=2"},
   749  		},
   750  		{
   751  			Model: "b",
   752  			AssetChain: []boot.BootAsset{
   753  				{Name: "asset", Hashes: []string{"x"}},
   754  			},
   755  			Kernel:         "k1",
   756  			KernelCmdlines: []string{"cm=1"},
   757  		},
   758  		{
   759  			Model: "b",
   760  			AssetChain: []boot.BootAsset{
   761  				{Name: "asset", Hashes: []string{"x"}},
   762  			},
   763  			Kernel:         "k2",
   764  			KernelCmdlines: []string{"cm=1"},
   765  		},
   766  		{
   767  			Model: "a",
   768  			AssetChain: []boot.BootAsset{
   769  				{Name: "asset", Hashes: []string{"x"}},
   770  			},
   771  			Kernel:         "k1",
   772  			KernelCmdlines: []string{"cm=1"},
   773  		},
   774  		{
   775  			Model: "a",
   776  			AssetChain: []boot.BootAsset{
   777  				{Name: "asset", Hashes: []string{"x"}},
   778  			},
   779  			Kernel:         "k2",
   780  			KernelCmdlines: []string{"cm=1"},
   781  		},
   782  		{
   783  			Model: "b",
   784  			AssetChain: []boot.BootAsset{
   785  				{Name: "asset", Hashes: []string{"x"}},
   786  			},
   787  			Kernel:         "k1",
   788  			KernelCmdlines: []string{"cm=2"},
   789  		},
   790  		{
   791  			Model: "b",
   792  			AssetChain: []boot.BootAsset{
   793  				{Name: "asset", Hashes: []string{"x"}},
   794  			},
   795  			Kernel:         "k2",
   796  			KernelCmdlines: []string{"cm=2"},
   797  		},
   798  		{
   799  			Model: "a",
   800  			AssetChain: []boot.BootAsset{
   801  				{Name: "asset", Hashes: []string{"x"}},
   802  			},
   803  			Kernel:         "k1",
   804  			KernelCmdlines: []string{"cm=2"},
   805  		},
   806  		{
   807  			Model: "a",
   808  			AssetChain: []boot.BootAsset{
   809  				{Name: "asset", Hashes: []string{"x"}},
   810  			},
   811  			Kernel:         "k2",
   812  			KernelCmdlines: []string{"cm=2"},
   813  		},
   814  		{
   815  			Model: "a",
   816  			AssetChain: []boot.BootAsset{
   817  				{Name: "asset", Hashes: []string{"y"}},
   818  			},
   819  			Kernel:         "k2",
   820  			KernelCmdlines: []string{"cm=1", "cm=2"},
   821  		},
   822  		{
   823  			Model: "a",
   824  			AssetChain: []boot.BootAsset{
   825  				{Name: "asset", Hashes: []string{"y"}},
   826  			},
   827  			Kernel:         "k1",
   828  			KernelCmdlines: []string{"cm=1", "cm=2"},
   829  		},
   830  	}
   831  	predictable := boot.ToPredictableBootChains(chains)
   832  	c.Check(predictable, DeepEquals, boot.PredictableBootChains{
   833  		{
   834  			Model: "a",
   835  			AssetChain: []boot.BootAsset{
   836  				{Name: "asset", Hashes: []string{"x"}},
   837  			},
   838  			Kernel:         "k1",
   839  			KernelCmdlines: []string{"cm=1"},
   840  		},
   841  		{
   842  			Model: "a",
   843  			AssetChain: []boot.BootAsset{
   844  				{Name: "asset", Hashes: []string{"x"}},
   845  			},
   846  			Kernel:         "k1",
   847  			KernelCmdlines: []string{"cm=2"},
   848  		},
   849  		{
   850  			Model: "a",
   851  			AssetChain: []boot.BootAsset{
   852  				{Name: "asset", Hashes: []string{"x"}},
   853  			},
   854  			Kernel:         "k2",
   855  			KernelCmdlines: []string{"cm=1"},
   856  		},
   857  		{
   858  			Model: "a",
   859  			AssetChain: []boot.BootAsset{
   860  				{Name: "asset", Hashes: []string{"x"}},
   861  			},
   862  			Kernel:         "k2",
   863  			KernelCmdlines: []string{"cm=2"},
   864  		},
   865  		{
   866  			Model: "a",
   867  			AssetChain: []boot.BootAsset{
   868  				{Name: "asset", Hashes: []string{"y"}},
   869  			},
   870  			Kernel:         "k1",
   871  			KernelCmdlines: []string{"cm=1"},
   872  		},
   873  		{
   874  			Model: "a",
   875  			AssetChain: []boot.BootAsset{
   876  				{Name: "asset", Hashes: []string{"y"}},
   877  			},
   878  			Kernel:         "k1",
   879  			KernelCmdlines: []string{"cm=2"},
   880  		},
   881  		{
   882  			Model: "a",
   883  			AssetChain: []boot.BootAsset{
   884  				{Name: "asset", Hashes: []string{"y"}},
   885  			},
   886  			Kernel:         "k1",
   887  			KernelCmdlines: []string{"cm=1", "cm=2"},
   888  		},
   889  		{
   890  			Model: "a",
   891  			AssetChain: []boot.BootAsset{
   892  				{Name: "asset", Hashes: []string{"y"}},
   893  			},
   894  			Kernel:         "k2",
   895  			KernelCmdlines: []string{"cm=1"},
   896  		},
   897  		{
   898  			Model: "a",
   899  			AssetChain: []boot.BootAsset{
   900  				{Name: "asset", Hashes: []string{"y"}},
   901  			},
   902  			Kernel:         "k2",
   903  			KernelCmdlines: []string{"cm=2"},
   904  		},
   905  		{
   906  			Model: "a",
   907  			AssetChain: []boot.BootAsset{
   908  				{Name: "asset", Hashes: []string{"y"}},
   909  			},
   910  			Kernel:         "k2",
   911  			KernelCmdlines: []string{"cm=1", "cm=2"},
   912  		},
   913  		{
   914  			Model: "b",
   915  			AssetChain: []boot.BootAsset{
   916  				{Name: "asset", Hashes: []string{"x"}},
   917  			},
   918  			Kernel:         "k1",
   919  			KernelCmdlines: []string{"cm=1"},
   920  		},
   921  		{
   922  			Model: "b",
   923  			AssetChain: []boot.BootAsset{
   924  				{Name: "asset", Hashes: []string{"x"}},
   925  			},
   926  			Kernel:         "k1",
   927  			KernelCmdlines: []string{"cm=2"},
   928  		},
   929  		{
   930  			Model: "b",
   931  			AssetChain: []boot.BootAsset{
   932  				{Name: "asset", Hashes: []string{"x"}},
   933  			},
   934  			Kernel:         "k2",
   935  			KernelCmdlines: []string{"cm=1"},
   936  		},
   937  		{
   938  			Model: "b",
   939  			AssetChain: []boot.BootAsset{
   940  				{Name: "asset", Hashes: []string{"x"}},
   941  			},
   942  			Kernel:         "k2",
   943  			KernelCmdlines: []string{"cm=2"},
   944  		},
   945  		{
   946  			Model: "b",
   947  			AssetChain: []boot.BootAsset{
   948  				{Name: "asset", Hashes: []string{"y"}},
   949  			},
   950  			Kernel:         "k1",
   951  			KernelCmdlines: []string{"cm=1"},
   952  		},
   953  		{
   954  			Model: "b",
   955  			AssetChain: []boot.BootAsset{
   956  				{Name: "asset", Hashes: []string{"y"}},
   957  			},
   958  			Kernel:         "k1",
   959  			KernelCmdlines: []string{"cm=2"},
   960  		},
   961  		{
   962  			Model: "b",
   963  			AssetChain: []boot.BootAsset{
   964  				{Name: "asset", Hashes: []string{"y"}},
   965  			},
   966  			Kernel:         "k2",
   967  			KernelCmdlines: []string{"cm=1"},
   968  		},
   969  		{
   970  			Model: "b",
   971  			AssetChain: []boot.BootAsset{
   972  				{Name: "asset", Hashes: []string{"y"}},
   973  			},
   974  			Kernel:         "k2",
   975  			KernelCmdlines: []string{"cm=2"},
   976  		},
   977  	})
   978  }
   979  
   980  func printChain(c *C, chain *secboot.LoadChain, prefix string) {
   981  	c.Logf("%v %v", prefix, chain.BootFile)
   982  	for _, n := range chain.Next {
   983  		printChain(c, n, prefix+"-")
   984  	}
   985  }
   986  
   987  // cPath returns a path under boot assets cache directory
   988  func cPath(p string) string {
   989  	return filepath.Join(dirs.SnapBootAssetsDir, p)
   990  }
   991  
   992  // nbf is bootloader.NewBootFile but shorter
   993  var nbf = bootloader.NewBootFile
   994  
   995  func (s *bootchainSuite) TestBootAssetsToLoadChainTrivialKernel(c *C) {
   996  	kbl := bootloader.NewBootFile("pc-kernel", "kernel.efi", bootloader.RoleRunMode)
   997  
   998  	chains, err := boot.BootAssetsToLoadChains(nil, kbl, nil)
   999  	c.Assert(err, IsNil)
  1000  
  1001  	c.Check(chains, DeepEquals, []*secboot.LoadChain{
  1002  		secboot.NewLoadChain(nbf("pc-kernel", "kernel.efi", bootloader.RoleRunMode)),
  1003  	})
  1004  }
  1005  
  1006  func (s *bootchainSuite) TestBootAssetsToLoadChainErr(c *C) {
  1007  	kbl := bootloader.NewBootFile("pc-kernel", "kernel.efi", bootloader.RoleRunMode)
  1008  
  1009  	assets := []boot.BootAsset{
  1010  		{Name: "shim", Hashes: []string{"hash0"}, Role: bootloader.RoleRecovery},
  1011  		{Name: "loader-recovery", Hashes: []string{"hash0"}, Role: bootloader.RoleRecovery},
  1012  		{Name: "loader-run", Hashes: []string{"hash0"}, Role: bootloader.RoleRunMode},
  1013  	}
  1014  
  1015  	blNames := map[bootloader.Role]string{
  1016  		bootloader.RoleRecovery: "recovery-bl",
  1017  		// missing bootloader name for role "run-mode"
  1018  	}
  1019  	// fails when probing the shim asset in the cache
  1020  	chains, err := boot.BootAssetsToLoadChains(assets, kbl, blNames)
  1021  	c.Assert(err, ErrorMatches, "file .*/recovery-bl/shim-hash0 not found in boot assets cache")
  1022  	c.Check(chains, IsNil)
  1023  	// make it work now
  1024  	c.Assert(os.MkdirAll(filepath.Dir(cPath("recovery-bl/shim-hash0")), 0755), IsNil)
  1025  	c.Assert(ioutil.WriteFile(cPath("recovery-bl/shim-hash0"), nil, 0644), IsNil)
  1026  
  1027  	// nested error bubbled up
  1028  	chains, err = boot.BootAssetsToLoadChains(assets, kbl, blNames)
  1029  	c.Assert(err, ErrorMatches, "file .*/recovery-bl/loader-recovery-hash0 not found in boot assets cache")
  1030  	c.Check(chains, IsNil)
  1031  	// again, make it work
  1032  	c.Assert(os.MkdirAll(filepath.Dir(cPath("recovery-bl/loader-recovery-hash0")), 0755), IsNil)
  1033  	c.Assert(ioutil.WriteFile(cPath("recovery-bl/loader-recovery-hash0"), nil, 0644), IsNil)
  1034  
  1035  	// fails on missing bootloader name for role "run-mode"
  1036  	chains, err = boot.BootAssetsToLoadChains(assets, kbl, blNames)
  1037  	c.Assert(err, ErrorMatches, `internal error: no bootloader name for boot asset role "run-mode"`)
  1038  	c.Check(chains, IsNil)
  1039  }
  1040  
  1041  func (s *bootchainSuite) TestBootAssetsToLoadChainSimpleChain(c *C) {
  1042  	kbl := bootloader.NewBootFile("pc-kernel", "kernel.efi", bootloader.RoleRunMode)
  1043  
  1044  	assets := []boot.BootAsset{
  1045  		{Name: "shim", Hashes: []string{"hash0"}, Role: bootloader.RoleRecovery},
  1046  		{Name: "loader-recovery", Hashes: []string{"hash0"}, Role: bootloader.RoleRecovery},
  1047  		{Name: "loader-run", Hashes: []string{"hash0"}, Role: bootloader.RoleRunMode},
  1048  	}
  1049  
  1050  	// mock relevant files in cache
  1051  	for _, name := range []string{
  1052  		"recovery-bl/shim-hash0",
  1053  		"recovery-bl/loader-recovery-hash0",
  1054  		"run-bl/loader-run-hash0",
  1055  	} {
  1056  		p := filepath.Join(dirs.SnapBootAssetsDir, name)
  1057  		c.Assert(os.MkdirAll(filepath.Dir(p), 0755), IsNil)
  1058  		c.Assert(ioutil.WriteFile(p, nil, 0644), IsNil)
  1059  	}
  1060  
  1061  	blNames := map[bootloader.Role]string{
  1062  		bootloader.RoleRecovery: "recovery-bl",
  1063  		bootloader.RoleRunMode:  "run-bl",
  1064  	}
  1065  
  1066  	chains, err := boot.BootAssetsToLoadChains(assets, kbl, blNames)
  1067  	c.Assert(err, IsNil)
  1068  
  1069  	c.Logf("got:")
  1070  	for _, ch := range chains {
  1071  		printChain(c, ch, "-")
  1072  	}
  1073  
  1074  	expected := []*secboot.LoadChain{
  1075  		secboot.NewLoadChain(nbf("", cPath("recovery-bl/shim-hash0"), bootloader.RoleRecovery),
  1076  			secboot.NewLoadChain(nbf("", cPath("recovery-bl/loader-recovery-hash0"), bootloader.RoleRecovery),
  1077  				secboot.NewLoadChain(nbf("", cPath("run-bl/loader-run-hash0"), bootloader.RoleRunMode),
  1078  					secboot.NewLoadChain(nbf("pc-kernel", "kernel.efi", bootloader.RoleRunMode))))),
  1079  	}
  1080  	c.Check(chains, DeepEquals, expected)
  1081  }
  1082  
  1083  func (s *bootchainSuite) TestBootAssetsToLoadChainWithAlternativeChains(c *C) {
  1084  	kbl := bootloader.NewBootFile("pc-kernel", "kernel.efi", bootloader.RoleRunMode)
  1085  
  1086  	assets := []boot.BootAsset{
  1087  		{Name: "shim", Hashes: []string{"hash0", "hash1"}, Role: bootloader.RoleRecovery},
  1088  		{Name: "loader-recovery", Hashes: []string{"hash0", "hash1"}, Role: bootloader.RoleRecovery},
  1089  		{Name: "loader-run", Hashes: []string{"hash0", "hash1"}, Role: bootloader.RoleRunMode},
  1090  	}
  1091  
  1092  	// mock relevant files in cache
  1093  	mockAssetsCache(c, s.rootDir, "recovery-bl", []string{
  1094  		"shim-hash0",
  1095  		"shim-hash1",
  1096  		"loader-recovery-hash0",
  1097  		"loader-recovery-hash1",
  1098  	})
  1099  	mockAssetsCache(c, s.rootDir, "run-bl", []string{
  1100  		"loader-run-hash0",
  1101  		"loader-run-hash1",
  1102  	})
  1103  
  1104  	blNames := map[bootloader.Role]string{
  1105  		bootloader.RoleRecovery: "recovery-bl",
  1106  		bootloader.RoleRunMode:  "run-bl",
  1107  	}
  1108  	chains, err := boot.BootAssetsToLoadChains(assets, kbl, blNames)
  1109  	c.Assert(err, IsNil)
  1110  
  1111  	c.Logf("got:")
  1112  	for _, ch := range chains {
  1113  		printChain(c, ch, "-")
  1114  	}
  1115  
  1116  	expected := []*secboot.LoadChain{
  1117  		secboot.NewLoadChain(nbf("", cPath("recovery-bl/shim-hash0"), bootloader.RoleRecovery),
  1118  			secboot.NewLoadChain(nbf("", cPath("recovery-bl/loader-recovery-hash0"), bootloader.RoleRecovery),
  1119  				secboot.NewLoadChain(nbf("", cPath("run-bl/loader-run-hash0"), bootloader.RoleRunMode),
  1120  					secboot.NewLoadChain(nbf("pc-kernel", "kernel.efi", bootloader.RoleRunMode))),
  1121  				secboot.NewLoadChain(nbf("", cPath("run-bl/loader-run-hash1"), bootloader.RoleRunMode),
  1122  					secboot.NewLoadChain(nbf("pc-kernel", "kernel.efi", bootloader.RoleRunMode)))),
  1123  			secboot.NewLoadChain(nbf("", cPath("recovery-bl/loader-recovery-hash1"), bootloader.RoleRecovery),
  1124  				secboot.NewLoadChain(nbf("", cPath("run-bl/loader-run-hash0"), bootloader.RoleRunMode),
  1125  					secboot.NewLoadChain(nbf("pc-kernel", "kernel.efi", bootloader.RoleRunMode))),
  1126  				secboot.NewLoadChain(nbf("", cPath("run-bl/loader-run-hash1"), bootloader.RoleRunMode),
  1127  					secboot.NewLoadChain(nbf("pc-kernel", "kernel.efi", bootloader.RoleRunMode))))),
  1128  		secboot.NewLoadChain(nbf("", cPath("recovery-bl/shim-hash1"), bootloader.RoleRecovery),
  1129  			secboot.NewLoadChain(nbf("", cPath("recovery-bl/loader-recovery-hash0"), bootloader.RoleRecovery),
  1130  				secboot.NewLoadChain(nbf("", cPath("run-bl/loader-run-hash0"), bootloader.RoleRunMode),
  1131  					secboot.NewLoadChain(nbf("pc-kernel", "kernel.efi", bootloader.RoleRunMode))),
  1132  				secboot.NewLoadChain(nbf("", cPath("run-bl/loader-run-hash1"), bootloader.RoleRunMode),
  1133  					secboot.NewLoadChain(nbf("pc-kernel", "kernel.efi", bootloader.RoleRunMode)))),
  1134  			secboot.NewLoadChain(nbf("", cPath("recovery-bl/loader-recovery-hash1"), bootloader.RoleRecovery),
  1135  				secboot.NewLoadChain(nbf("", cPath("run-bl/loader-run-hash0"), bootloader.RoleRunMode),
  1136  					secboot.NewLoadChain(nbf("pc-kernel", "kernel.efi", bootloader.RoleRunMode))),
  1137  				secboot.NewLoadChain(nbf("", cPath("run-bl/loader-run-hash1"), bootloader.RoleRunMode),
  1138  					secboot.NewLoadChain(nbf("pc-kernel", "kernel.efi", bootloader.RoleRunMode))))),
  1139  	}
  1140  	c.Check(chains, DeepEquals, expected)
  1141  }
  1142  
  1143  func (s *sealSuite) TestReadWriteBootChains(c *C) {
  1144  	if os.Geteuid() == 0 {
  1145  		c.Skip("the test cannot be run by the root user")
  1146  	}
  1147  
  1148  	chains := []boot.BootChain{
  1149  		{
  1150  			BrandID:        "mybrand",
  1151  			Model:          "foo",
  1152  			Grade:          "signed",
  1153  			ModelSignKeyID: "my-key-id",
  1154  			AssetChain: []boot.BootAsset{
  1155  				// hashes will be sorted
  1156  				{Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"x", "y"}},
  1157  				{Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"c", "d"}},
  1158  				{Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"z", "x"}},
  1159  			},
  1160  			Kernel:         "pc-kernel-other",
  1161  			KernelRevision: "2345",
  1162  			KernelCmdlines: []string{`snapd_recovery_mode=run foo`},
  1163  		}, {
  1164  			BrandID:        "mybrand",
  1165  			Model:          "foo",
  1166  			Grade:          "dangerous",
  1167  			ModelSignKeyID: "my-key-id",
  1168  			AssetChain: []boot.BootAsset{
  1169  				// hashes will be sorted
  1170  				{Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"y", "x"}},
  1171  				{Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"c", "d"}},
  1172  			},
  1173  			Kernel:         "pc-kernel-recovery",
  1174  			KernelRevision: "1234",
  1175  			KernelCmdlines: []string{`snapd_recovery_mode=recover foo`},
  1176  		},
  1177  	}
  1178  
  1179  	pbc := boot.ToPredictableBootChains(chains)
  1180  
  1181  	rootdir := c.MkDir()
  1182  
  1183  	expected := `{"reseal-count":0,"boot-chains":[{"brand-id":"mybrand","model":"foo","grade":"dangerous","model-sign-key-id":"my-key-id","asset-chain":[{"role":"recovery","name":"shim","hashes":["x","y"]},{"role":"recovery","name":"loader","hashes":["c","d"]}],"kernel":"pc-kernel-recovery","kernel-revision":"1234","kernel-cmdlines":["snapd_recovery_mode=recover foo"]},{"brand-id":"mybrand","model":"foo","grade":"signed","model-sign-key-id":"my-key-id","asset-chain":[{"role":"recovery","name":"shim","hashes":["x","y"]},{"role":"recovery","name":"loader","hashes":["c","d"]},{"role":"run-mode","name":"loader","hashes":["x","z"]}],"kernel":"pc-kernel-other","kernel-revision":"2345","kernel-cmdlines":["snapd_recovery_mode=run foo"]}]}
  1184  `
  1185  	// creates a complete tree and writes a file
  1186  	err := boot.WriteBootChains(pbc, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), 0)
  1187  	c.Assert(err, IsNil)
  1188  	c.Check(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), testutil.FileEquals, expected)
  1189  
  1190  	fi, err := os.Stat(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"))
  1191  	c.Assert(err, IsNil)
  1192  	c.Check(fi.Mode().Perm(), Equals, os.FileMode(0600))
  1193  
  1194  	loaded, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"))
  1195  	c.Assert(err, IsNil)
  1196  	c.Check(loaded, DeepEquals, pbc)
  1197  	c.Check(cnt, Equals, 0)
  1198  	// boot chains should be same for reseal purpose
  1199  	c.Check(boot.PredictableBootChainsEqualForReseal(pbc, loaded), Equals, boot.BootChainEquivalent)
  1200  
  1201  	// write them again with count > 0
  1202  	err = boot.WriteBootChains(pbc, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), 99)
  1203  	c.Assert(err, IsNil)
  1204  
  1205  	_, cnt, err = boot.ReadBootChains(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"))
  1206  	c.Assert(err, IsNil)
  1207  	c.Check(cnt, Equals, 99)
  1208  
  1209  	// make device/fde directory read only so that writing fails
  1210  	otherRootdir := c.MkDir()
  1211  	c.Assert(os.MkdirAll(dirs.SnapFDEDirUnder(otherRootdir), 0755), IsNil)
  1212  	c.Assert(os.Chmod(dirs.SnapFDEDirUnder(otherRootdir), 0000), IsNil)
  1213  	defer os.Chmod(dirs.SnapFDEDirUnder(otherRootdir), 0755)
  1214  
  1215  	err = boot.WriteBootChains(pbc, filepath.Join(dirs.SnapFDEDirUnder(otherRootdir), "boot-chains"), 0)
  1216  	c.Assert(err, ErrorMatches, `cannot create a temporary boot chains file: open .*/boot-chains\.[a-zA-Z0-9]+~: permission denied`)
  1217  
  1218  	// make the original file non readable
  1219  	c.Assert(os.Chmod(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), 0000), IsNil)
  1220  	defer os.Chmod(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), 0755)
  1221  	loaded, _, err = boot.ReadBootChains(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"))
  1222  	c.Assert(err, ErrorMatches, "cannot open existing boot chains data file: open .*/boot-chains: permission denied")
  1223  	c.Check(loaded, IsNil)
  1224  
  1225  	// loading from a file that does not exist yields a nil boot chain
  1226  	// and 0 count
  1227  	loaded, cnt, err = boot.ReadBootChains("does-not-exist")
  1228  	c.Assert(err, IsNil)
  1229  	c.Check(loaded, IsNil)
  1230  	c.Check(cnt, Equals, 0)
  1231  }
  1232  
  1233  func (s *bootchainSuite) TestModelForSealing(c *C) {
  1234  	bc := boot.BootChain{
  1235  		BrandID:        "my-brand",
  1236  		Model:          "my-model",
  1237  		Grade:          "signed",
  1238  		ModelSignKeyID: "my-key-id",
  1239  	}
  1240  
  1241  	modelForSealing := bc.SecbootModelForSealing()
  1242  	c.Check(modelForSealing.Model(), Equals, "my-model")
  1243  	c.Check(modelForSealing.BrandID(), Equals, "my-brand")
  1244  	c.Check(modelForSealing.Grade(), Equals, asserts.ModelGrade("signed"))
  1245  	c.Check(modelForSealing.SignKeyID(), Equals, "my-key-id")
  1246  	c.Check(modelForSealing.Series(), Equals, "16")
  1247  	c.Check(boot.ModelUniqueID(modelForSealing), Equals, "my-brand/my-model,signed,my-key-id")
  1248  
  1249  }