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