gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/boot/seal_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  	"errors"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  	"strconv"
    29  
    30  	. "gopkg.in/check.v1"
    31  
    32  	"github.com/snapcore/snapd/asserts"
    33  	"github.com/snapcore/snapd/boot"
    34  	"github.com/snapcore/snapd/boot/boottest"
    35  	"github.com/snapcore/snapd/bootloader"
    36  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    37  	"github.com/snapcore/snapd/dirs"
    38  	"github.com/snapcore/snapd/kernel/fde"
    39  	"github.com/snapcore/snapd/osutil"
    40  	"github.com/snapcore/snapd/secboot"
    41  	"github.com/snapcore/snapd/seed"
    42  	"github.com/snapcore/snapd/snap"
    43  	"github.com/snapcore/snapd/snap/snaptest"
    44  	"github.com/snapcore/snapd/testutil"
    45  	"github.com/snapcore/snapd/timings"
    46  )
    47  
    48  type sealSuite struct {
    49  	testutil.BaseTest
    50  }
    51  
    52  var _ = Suite(&sealSuite{})
    53  
    54  func (s *sealSuite) SetUpTest(c *C) {
    55  	s.BaseTest.SetUpTest(c)
    56  
    57  	rootdir := c.MkDir()
    58  	dirs.SetRootDir(rootdir)
    59  	s.AddCleanup(func() { dirs.SetRootDir("/") })
    60  }
    61  
    62  func mockKernelSeedSnap(c *C, rev snap.Revision) *seed.Snap {
    63  	return mockNamedKernelSeedSnap(c, rev, "pc-kernel")
    64  }
    65  
    66  func mockNamedKernelSeedSnap(c *C, rev snap.Revision, name string) *seed.Snap {
    67  	revAsString := rev.String()
    68  	if rev.Unset() {
    69  		revAsString = "unset"
    70  	}
    71  	return &seed.Snap{
    72  		Path: fmt.Sprintf("/var/lib/snapd/seed/snaps/%v_%v.snap", name, revAsString),
    73  		SideInfo: &snap.SideInfo{
    74  			RealName: name,
    75  			Revision: rev,
    76  		},
    77  		EssentialType: snap.TypeKernel,
    78  	}
    79  }
    80  
    81  func mockGadgetSeedSnap(c *C, files [][]string) *seed.Snap {
    82  	gadgetSnapFile := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, files)
    83  	return &seed.Snap{
    84  		Path: gadgetSnapFile,
    85  		SideInfo: &snap.SideInfo{
    86  			RealName: "gadget",
    87  			Revision: snap.R(1),
    88  		},
    89  		EssentialType: snap.TypeGadget,
    90  	}
    91  }
    92  
    93  func (s *sealSuite) TestSealKeyToModeenv(c *C) {
    94  	for _, tc := range []struct {
    95  		sealErr error
    96  		err     string
    97  	}{
    98  		{sealErr: nil, err: ""},
    99  		{sealErr: errors.New("seal error"), err: "cannot seal the encryption keys: seal error"},
   100  	} {
   101  		rootdir := c.MkDir()
   102  		dirs.SetRootDir(rootdir)
   103  		defer dirs.SetRootDir("")
   104  
   105  		err := createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-seed"))
   106  		c.Assert(err, IsNil)
   107  
   108  		err = createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-boot"))
   109  		c.Assert(err, IsNil)
   110  
   111  		model := boottest.MakeMockUC20Model()
   112  
   113  		modeenv := &boot.Modeenv{
   114  			RecoverySystem: "20200825",
   115  			CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   116  				"grubx64.efi": []string{"grub-hash-1"},
   117  				"bootx64.efi": []string{"shim-hash-1"},
   118  			},
   119  
   120  			CurrentTrustedBootAssets: boot.BootAssetsMap{
   121  				"grubx64.efi": []string{"run-grub-hash-1"},
   122  			},
   123  
   124  			CurrentKernels: []string{"pc-kernel_500.snap"},
   125  
   126  			CurrentKernelCommandLines: boot.BootCommandLines{
   127  				"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   128  			},
   129  			Model:          model.Model(),
   130  			BrandID:        model.BrandID(),
   131  			Grade:          string(model.Grade()),
   132  			ModelSignKeyID: model.SignKeyID(),
   133  		}
   134  
   135  		// mock asset cache
   136  		mockAssetsCache(c, rootdir, "grub", []string{
   137  			"bootx64.efi-shim-hash-1",
   138  			"grubx64.efi-grub-hash-1",
   139  			"grubx64.efi-run-grub-hash-1",
   140  		})
   141  
   142  		// set encryption key
   143  		myKey := secboot.EncryptionKey{}
   144  		myKey2 := secboot.EncryptionKey{}
   145  		for i := range myKey {
   146  			myKey[i] = byte(i)
   147  			myKey2[i] = byte(128 + i)
   148  		}
   149  
   150  		// set a mock recovery kernel
   151  		readSystemEssentialCalls := 0
   152  		restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   153  			readSystemEssentialCalls++
   154  			return model, []*seed.Snap{mockKernelSeedSnap(c, snap.R(1)), mockGadgetSeedSnap(c, nil)}, nil
   155  		})
   156  		defer restore()
   157  
   158  		// set mock key sealing
   159  		sealKeysCalls := 0
   160  		restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error {
   161  			sealKeysCalls++
   162  			switch sealKeysCalls {
   163  			case 1:
   164  				// the run object seals only the ubuntu-data key
   165  				c.Check(params.TPMPolicyAuthKeyFile, Equals, filepath.Join(boot.InstallHostFDESaveDir, "tpm-policy-auth-key"))
   166  				c.Check(params.TPMLockoutAuthFile, Equals, filepath.Join(boot.InstallHostFDESaveDir, "tpm-lockout-auth"))
   167  
   168  				dataKeyFile := filepath.Join(rootdir, "/run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key")
   169  				c.Check(keys, DeepEquals, []secboot.SealKeyRequest{{Key: myKey, KeyName: "ubuntu-data", KeyFile: dataKeyFile}})
   170  			case 2:
   171  				// the fallback object seals the ubuntu-data and the ubuntu-save keys
   172  				c.Check(params.TPMPolicyAuthKeyFile, Equals, "")
   173  				c.Check(params.TPMLockoutAuthFile, Equals, "")
   174  
   175  				dataKeyFile := filepath.Join(rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key")
   176  				saveKeyFile := filepath.Join(rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key")
   177  				c.Check(keys, DeepEquals, []secboot.SealKeyRequest{{Key: myKey, KeyName: "ubuntu-data", KeyFile: dataKeyFile}, {Key: myKey2, KeyName: "ubuntu-save", KeyFile: saveKeyFile}})
   178  			default:
   179  				c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls)
   180  			}
   181  			c.Assert(params.ModelParams, HasLen, 1)
   182  			for _, d := range []string{boot.InitramfsSeedEncryptionKeyDir, boot.InstallHostFDEDataDir} {
   183  				ex, isdir, _ := osutil.DirExists(d)
   184  				c.Check(ex && isdir, Equals, true, Commentf("location %q does not exist or is not a directory", d))
   185  			}
   186  
   187  			shim := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/bootx64.efi-shim-hash-1"), bootloader.RoleRecovery)
   188  			grub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-grub-hash-1"), bootloader.RoleRecovery)
   189  			runGrub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-run-grub-hash-1"), bootloader.RoleRunMode)
   190  			kernel := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery)
   191  			runKernel := bootloader.NewBootFile(filepath.Join(rootdir, "var/lib/snapd/snaps/pc-kernel_500.snap"), "kernel.efi", bootloader.RoleRunMode)
   192  
   193  			switch sealKeysCalls {
   194  			case 1:
   195  				c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{
   196  					secboot.NewLoadChain(shim,
   197  						secboot.NewLoadChain(grub,
   198  							secboot.NewLoadChain(kernel))),
   199  					secboot.NewLoadChain(shim,
   200  						secboot.NewLoadChain(grub,
   201  							secboot.NewLoadChain(runGrub,
   202  								secboot.NewLoadChain(runKernel)))),
   203  				})
   204  				c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   205  					"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
   206  					"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   207  				})
   208  			case 2:
   209  				c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{
   210  					secboot.NewLoadChain(shim,
   211  						secboot.NewLoadChain(grub,
   212  							secboot.NewLoadChain(kernel))),
   213  				})
   214  				c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   215  					"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
   216  				})
   217  			default:
   218  				c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls)
   219  			}
   220  			c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
   221  
   222  			return tc.sealErr
   223  		})
   224  		defer restore()
   225  
   226  		err = boot.SealKeyToModeenv(myKey, myKey2, model, modeenv)
   227  		if tc.sealErr != nil {
   228  			c.Assert(sealKeysCalls, Equals, 1)
   229  		} else {
   230  			c.Assert(sealKeysCalls, Equals, 2)
   231  		}
   232  		if tc.err == "" {
   233  			c.Assert(err, IsNil)
   234  		} else {
   235  			c.Assert(err, ErrorMatches, tc.err)
   236  			continue
   237  		}
   238  
   239  		// verify the boot chains data file
   240  		pbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "boot-chains"))
   241  		c.Assert(err, IsNil)
   242  		c.Check(cnt, Equals, 0)
   243  		c.Check(pbc, DeepEquals, boot.PredictableBootChains{
   244  			boot.BootChain{
   245  				BrandID:        "my-brand",
   246  				Model:          "my-model-uc20",
   247  				Grade:          "dangerous",
   248  				ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
   249  				AssetChain: []boot.BootAsset{
   250  					{
   251  						Role:   "recovery",
   252  						Name:   "bootx64.efi",
   253  						Hashes: []string{"shim-hash-1"},
   254  					},
   255  					{
   256  						Role:   "recovery",
   257  						Name:   "grubx64.efi",
   258  						Hashes: []string{"grub-hash-1"},
   259  					},
   260  				},
   261  				Kernel:         "pc-kernel",
   262  				KernelRevision: "1",
   263  				KernelCmdlines: []string{
   264  					"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
   265  				},
   266  			},
   267  			boot.BootChain{
   268  				BrandID:        "my-brand",
   269  				Model:          "my-model-uc20",
   270  				Grade:          "dangerous",
   271  				ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
   272  				AssetChain: []boot.BootAsset{
   273  					{
   274  						Role:   "recovery",
   275  						Name:   "bootx64.efi",
   276  						Hashes: []string{"shim-hash-1"},
   277  					},
   278  					{
   279  						Role:   "recovery",
   280  						Name:   "grubx64.efi",
   281  						Hashes: []string{"grub-hash-1"},
   282  					},
   283  					{
   284  						Role:   "run-mode",
   285  						Name:   "grubx64.efi",
   286  						Hashes: []string{"run-grub-hash-1"},
   287  					},
   288  				},
   289  				Kernel:         "pc-kernel",
   290  				KernelRevision: "500",
   291  				KernelCmdlines: []string{
   292  					"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   293  				},
   294  			},
   295  		})
   296  
   297  		// verify the recovery boot chains
   298  		pbc, cnt, err = boot.ReadBootChains(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "recovery-boot-chains"))
   299  		c.Assert(err, IsNil)
   300  		c.Check(cnt, Equals, 0)
   301  		c.Check(pbc, DeepEquals, boot.PredictableBootChains{
   302  			boot.BootChain{
   303  				BrandID:        "my-brand",
   304  				Model:          "my-model-uc20",
   305  				Grade:          "dangerous",
   306  				ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
   307  				AssetChain: []boot.BootAsset{
   308  					{
   309  						Role:   "recovery",
   310  						Name:   "bootx64.efi",
   311  						Hashes: []string{"shim-hash-1"},
   312  					},
   313  					{
   314  						Role:   "recovery",
   315  						Name:   "grubx64.efi",
   316  						Hashes: []string{"grub-hash-1"},
   317  					},
   318  				},
   319  				Kernel:         "pc-kernel",
   320  				KernelRevision: "1",
   321  				KernelCmdlines: []string{
   322  					"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
   323  				},
   324  			},
   325  		})
   326  
   327  		// marker
   328  		marker := filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "sealed-keys")
   329  		c.Check(marker, testutil.FileEquals, "tpm")
   330  	}
   331  }
   332  
   333  // TODO:UC20: also test fallback reseal
   334  func (s *sealSuite) TestResealKeyToModeenvWithSystemFallback(c *C) {
   335  	var prevPbc boot.PredictableBootChains
   336  	var prevRecoveryPbc boot.PredictableBootChains
   337  
   338  	for idx, tc := range []struct {
   339  		sealedKeys       bool
   340  		reuseRunPbc      bool
   341  		reuseRecoveryPbc bool
   342  		resealErr        error
   343  		err              string
   344  	}{
   345  		{sealedKeys: false, resealErr: nil, err: ""},
   346  		{sealedKeys: true, resealErr: nil, err: ""},
   347  		{sealedKeys: true, resealErr: errors.New("reseal error"), err: "cannot reseal the encryption key: reseal error"},
   348  		{reuseRunPbc: true, reuseRecoveryPbc: true, sealedKeys: true, resealErr: nil, err: ""},
   349  		// recovery boot chain is unchanged
   350  		{reuseRunPbc: false, reuseRecoveryPbc: true, sealedKeys: true, resealErr: nil, err: ""},
   351  		// run boot chain is unchanged
   352  		{reuseRunPbc: true, reuseRecoveryPbc: false, sealedKeys: true, resealErr: nil, err: ""},
   353  	} {
   354  		c.Logf("tc: %v", idx)
   355  		rootdir := c.MkDir()
   356  		dirs.SetRootDir(rootdir)
   357  		defer dirs.SetRootDir("")
   358  
   359  		if tc.sealedKeys {
   360  			c.Assert(os.MkdirAll(dirs.SnapFDEDir, 0755), IsNil)
   361  			err := ioutil.WriteFile(filepath.Join(dirs.SnapFDEDir, "sealed-keys"), nil, 0644)
   362  			c.Assert(err, IsNil)
   363  
   364  		}
   365  
   366  		err := createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-seed"))
   367  		c.Assert(err, IsNil)
   368  
   369  		err = createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-boot"))
   370  		c.Assert(err, IsNil)
   371  
   372  		model := boottest.MakeMockUC20Model()
   373  
   374  		modeenv := &boot.Modeenv{
   375  			CurrentRecoverySystems: []string{"20200825"},
   376  			CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   377  				"grubx64.efi": []string{"grub-hash-1"},
   378  				"bootx64.efi": []string{"shim-hash-1", "shim-hash-2"},
   379  			},
   380  
   381  			CurrentTrustedBootAssets: boot.BootAssetsMap{
   382  				"grubx64.efi": []string{"run-grub-hash-1", "run-grub-hash-2"},
   383  			},
   384  
   385  			CurrentKernels: []string{"pc-kernel_500.snap", "pc-kernel_600.snap"},
   386  
   387  			CurrentKernelCommandLines: boot.BootCommandLines{
   388  				"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   389  			},
   390  			Model:          model.Model(),
   391  			BrandID:        model.BrandID(),
   392  			Grade:          string(model.Grade()),
   393  			ModelSignKeyID: model.SignKeyID(),
   394  		}
   395  
   396  		if tc.reuseRunPbc {
   397  			err := boot.WriteBootChains(prevPbc, filepath.Join(dirs.SnapFDEDir, "boot-chains"), 9)
   398  			c.Assert(err, IsNil)
   399  		}
   400  		if tc.reuseRecoveryPbc {
   401  			err = boot.WriteBootChains(prevRecoveryPbc, filepath.Join(dirs.SnapFDEDir, "recovery-boot-chains"), 9)
   402  			c.Assert(err, IsNil)
   403  		}
   404  
   405  		// mock asset cache
   406  		mockAssetsCache(c, rootdir, "grub", []string{
   407  			"bootx64.efi-shim-hash-1",
   408  			"bootx64.efi-shim-hash-2",
   409  			"grubx64.efi-grub-hash-1",
   410  			"grubx64.efi-run-grub-hash-1",
   411  			"grubx64.efi-run-grub-hash-2",
   412  		})
   413  
   414  		// set a mock recovery kernel
   415  		readSystemEssentialCalls := 0
   416  		restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   417  			readSystemEssentialCalls++
   418  			return model, []*seed.Snap{mockKernelSeedSnap(c, snap.R(1)), mockGadgetSeedSnap(c, nil)}, nil
   419  		})
   420  		defer restore()
   421  
   422  		// set mock key resealing
   423  		resealKeysCalls := 0
   424  		restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   425  			c.Check(params.TPMPolicyAuthKeyFile, Equals, filepath.Join(dirs.SnapSaveDir, "device/fde", "tpm-policy-auth-key"))
   426  
   427  			resealKeysCalls++
   428  			c.Assert(params.ModelParams, HasLen, 1)
   429  
   430  			// shared parameters
   431  			c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
   432  
   433  			// recovery parameters
   434  			shim := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/bootx64.efi-shim-hash-1"), bootloader.RoleRecovery)
   435  			shim2 := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/bootx64.efi-shim-hash-2"), bootloader.RoleRecovery)
   436  			grub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-grub-hash-1"), bootloader.RoleRecovery)
   437  			kernel := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery)
   438  			// run mode parameters
   439  			runGrub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-run-grub-hash-1"), bootloader.RoleRunMode)
   440  			runGrub2 := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-run-grub-hash-2"), bootloader.RoleRunMode)
   441  			runKernel := bootloader.NewBootFile(filepath.Join(rootdir, "var/lib/snapd/snaps/pc-kernel_500.snap"), "kernel.efi", bootloader.RoleRunMode)
   442  			runKernel2 := bootloader.NewBootFile(filepath.Join(rootdir, "var/lib/snapd/snaps/pc-kernel_600.snap"), "kernel.efi", bootloader.RoleRunMode)
   443  
   444  			checkRunParams := func() {
   445  				c.Check(params.KeyFiles, DeepEquals, []string{
   446  					filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
   447  				})
   448  				c.Check(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   449  					"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
   450  					"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   451  				})
   452  				// load chains
   453  				c.Assert(params.ModelParams[0].EFILoadChains, HasLen, 6)
   454  				// recovery load chains
   455  				c.Assert(params.ModelParams[0].EFILoadChains[:2], DeepEquals, []*secboot.LoadChain{
   456  					secboot.NewLoadChain(shim,
   457  						secboot.NewLoadChain(grub,
   458  							secboot.NewLoadChain(kernel))),
   459  					secboot.NewLoadChain(shim2,
   460  						secboot.NewLoadChain(grub,
   461  							secboot.NewLoadChain(kernel))),
   462  				})
   463  				// run load chains
   464  				c.Assert(params.ModelParams[0].EFILoadChains[2:4], DeepEquals, []*secboot.LoadChain{
   465  					secboot.NewLoadChain(shim,
   466  						secboot.NewLoadChain(grub,
   467  							secboot.NewLoadChain(runGrub,
   468  								secboot.NewLoadChain(runKernel)),
   469  							secboot.NewLoadChain(runGrub2,
   470  								secboot.NewLoadChain(runKernel)),
   471  						)),
   472  					secboot.NewLoadChain(shim2,
   473  						secboot.NewLoadChain(grub,
   474  							secboot.NewLoadChain(runGrub,
   475  								secboot.NewLoadChain(runKernel)),
   476  							secboot.NewLoadChain(runGrub2,
   477  								secboot.NewLoadChain(runKernel)),
   478  						)),
   479  				})
   480  
   481  				c.Assert(params.ModelParams[0].EFILoadChains[4:], DeepEquals, []*secboot.LoadChain{
   482  					secboot.NewLoadChain(shim,
   483  						secboot.NewLoadChain(grub,
   484  							secboot.NewLoadChain(runGrub,
   485  								secboot.NewLoadChain(runKernel2)),
   486  							secboot.NewLoadChain(runGrub2,
   487  								secboot.NewLoadChain(runKernel2)),
   488  						)),
   489  					secboot.NewLoadChain(shim2,
   490  						secboot.NewLoadChain(grub,
   491  							secboot.NewLoadChain(runGrub,
   492  								secboot.NewLoadChain(runKernel2)),
   493  							secboot.NewLoadChain(runGrub2,
   494  								secboot.NewLoadChain(runKernel2)),
   495  						)),
   496  				})
   497  			}
   498  
   499  			checkRecoveryParams := func() {
   500  				c.Check(params.KeyFiles, DeepEquals, []string{
   501  					filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
   502  					filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
   503  				})
   504  				c.Check(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   505  					"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
   506  				})
   507  				// load chains
   508  				c.Assert(params.ModelParams[0].EFILoadChains, HasLen, 2)
   509  				// recovery load chains
   510  				c.Assert(params.ModelParams[0].EFILoadChains[:2], DeepEquals, []*secboot.LoadChain{
   511  					secboot.NewLoadChain(shim,
   512  						secboot.NewLoadChain(grub,
   513  							secboot.NewLoadChain(kernel))),
   514  					secboot.NewLoadChain(shim2,
   515  						secboot.NewLoadChain(grub,
   516  							secboot.NewLoadChain(kernel))),
   517  				})
   518  			}
   519  
   520  			switch resealKeysCalls {
   521  			case 1:
   522  				if !tc.reuseRunPbc {
   523  					checkRunParams()
   524  				} else if !tc.reuseRecoveryPbc {
   525  					checkRecoveryParams()
   526  				} else {
   527  					c.Errorf("unexpected call to secboot.ResealKeys (call # %d)", resealKeysCalls)
   528  				}
   529  			case 2:
   530  				if !tc.reuseRecoveryPbc {
   531  					checkRecoveryParams()
   532  				} else {
   533  					c.Errorf("unexpected call to secboot.ResealKeys (call # %d)", resealKeysCalls)
   534  				}
   535  			default:
   536  				c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls)
   537  			}
   538  
   539  			return tc.resealErr
   540  		})
   541  		defer restore()
   542  
   543  		// here we don't have unasserted kernels so just set
   544  		// expectReseal to false as it doesn't matter;
   545  		// the behavior with unasserted kernel is tested in
   546  		// boot_test.go specific tests
   547  		const expectReseal = false
   548  		err = boot.ResealKeyToModeenv(rootdir, modeenv, expectReseal)
   549  		if !tc.sealedKeys || (tc.reuseRunPbc && tc.reuseRecoveryPbc) {
   550  			// did nothing
   551  			c.Assert(err, IsNil)
   552  			c.Assert(resealKeysCalls, Equals, 0)
   553  			continue
   554  		}
   555  		if tc.err == "" {
   556  			c.Assert(err, IsNil)
   557  		} else {
   558  			c.Assert(err, ErrorMatches, tc.err)
   559  		}
   560  		if tc.resealErr != nil {
   561  			// mocked error is returned on first reseal
   562  			c.Assert(resealKeysCalls, Equals, 1)
   563  		} else if !tc.reuseRecoveryPbc && !tc.reuseRunPbc {
   564  			// none of the boot chains is reused, so 2 reseals are
   565  			// observed
   566  			c.Assert(resealKeysCalls, Equals, 2)
   567  		} else {
   568  			// one of the boot chains is reused, only one reseal
   569  			c.Assert(resealKeysCalls, Equals, 1)
   570  		}
   571  		if tc.err != "" {
   572  			continue
   573  		}
   574  
   575  		// verify the boot chains data file
   576  		pbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "boot-chains"))
   577  		c.Assert(err, IsNil)
   578  		if tc.reuseRunPbc {
   579  			c.Assert(cnt, Equals, 9)
   580  		} else {
   581  			c.Assert(cnt, Equals, 1)
   582  		}
   583  		c.Check(pbc, DeepEquals, boot.PredictableBootChains{
   584  			boot.BootChain{
   585  				BrandID:        "my-brand",
   586  				Model:          "my-model-uc20",
   587  				Grade:          "dangerous",
   588  				ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
   589  				AssetChain: []boot.BootAsset{
   590  					{
   591  						Role:   "recovery",
   592  						Name:   "bootx64.efi",
   593  						Hashes: []string{"shim-hash-1", "shim-hash-2"},
   594  					},
   595  					{
   596  						Role:   "recovery",
   597  						Name:   "grubx64.efi",
   598  						Hashes: []string{"grub-hash-1"},
   599  					},
   600  				},
   601  				Kernel:         "pc-kernel",
   602  				KernelRevision: "1",
   603  				KernelCmdlines: []string{
   604  					"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
   605  				},
   606  			},
   607  			boot.BootChain{
   608  				BrandID:        "my-brand",
   609  				Model:          "my-model-uc20",
   610  				Grade:          "dangerous",
   611  				ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
   612  				AssetChain: []boot.BootAsset{
   613  					{
   614  						Role:   "recovery",
   615  						Name:   "bootx64.efi",
   616  						Hashes: []string{"shim-hash-1", "shim-hash-2"},
   617  					},
   618  					{
   619  						Role:   "recovery",
   620  						Name:   "grubx64.efi",
   621  						Hashes: []string{"grub-hash-1"},
   622  					},
   623  					{
   624  						Role:   "run-mode",
   625  						Name:   "grubx64.efi",
   626  						Hashes: []string{"run-grub-hash-1", "run-grub-hash-2"},
   627  					},
   628  				},
   629  				Kernel:         "pc-kernel",
   630  				KernelRevision: "500",
   631  				KernelCmdlines: []string{
   632  					"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   633  				},
   634  			},
   635  			boot.BootChain{
   636  				BrandID:        "my-brand",
   637  				Model:          "my-model-uc20",
   638  				Grade:          "dangerous",
   639  				ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
   640  				AssetChain: []boot.BootAsset{
   641  					{
   642  						Role:   "recovery",
   643  						Name:   "bootx64.efi",
   644  						Hashes: []string{"shim-hash-1", "shim-hash-2"},
   645  					},
   646  					{
   647  						Role:   "recovery",
   648  						Name:   "grubx64.efi",
   649  						Hashes: []string{"grub-hash-1"},
   650  					},
   651  					{
   652  						Role:   "run-mode",
   653  						Name:   "grubx64.efi",
   654  						Hashes: []string{"run-grub-hash-1", "run-grub-hash-2"},
   655  					},
   656  				},
   657  				Kernel:         "pc-kernel",
   658  				KernelRevision: "600",
   659  				KernelCmdlines: []string{
   660  					"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   661  				},
   662  			},
   663  		})
   664  		prevPbc = pbc
   665  		recoveryPbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "recovery-boot-chains"))
   666  		c.Assert(err, IsNil)
   667  		if tc.reuseRecoveryPbc {
   668  			c.Check(cnt, Equals, 9)
   669  		} else {
   670  			c.Check(cnt, Equals, 1)
   671  		}
   672  		prevRecoveryPbc = recoveryPbc
   673  	}
   674  }
   675  
   676  func (s *sealSuite) TestResealKeyToModeenvRecoveryKeysForGoodSystemsOnly(c *C) {
   677  	rootdir := c.MkDir()
   678  	dirs.SetRootDir(rootdir)
   679  	defer dirs.SetRootDir("")
   680  
   681  	c.Assert(os.MkdirAll(dirs.SnapFDEDir, 0755), IsNil)
   682  	err := ioutil.WriteFile(filepath.Join(dirs.SnapFDEDir, "sealed-keys"), nil, 0644)
   683  	c.Assert(err, IsNil)
   684  
   685  	err = createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-seed"))
   686  	c.Assert(err, IsNil)
   687  
   688  	err = createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-boot"))
   689  	c.Assert(err, IsNil)
   690  
   691  	model := boottest.MakeMockUC20Model()
   692  
   693  	modeenv := &boot.Modeenv{
   694  		// where 1234 is being tried
   695  		CurrentRecoverySystems: []string{"20200825", "1234"},
   696  		// 20200825 has known to be good
   697  		GoodRecoverySystems: []string{"20200825"},
   698  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   699  			"grubx64.efi": []string{"grub-hash"},
   700  			"bootx64.efi": []string{"shim-hash"},
   701  		},
   702  
   703  		CurrentTrustedBootAssets: boot.BootAssetsMap{
   704  			"grubx64.efi": []string{"run-grub-hash"},
   705  		},
   706  
   707  		CurrentKernels: []string{"pc-kernel_500.snap"},
   708  
   709  		CurrentKernelCommandLines: boot.BootCommandLines{
   710  			"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   711  		},
   712  		Model:          model.Model(),
   713  		BrandID:        model.BrandID(),
   714  		Grade:          string(model.Grade()),
   715  		ModelSignKeyID: model.SignKeyID(),
   716  	}
   717  
   718  	// mock asset cache
   719  	mockAssetsCache(c, rootdir, "grub", []string{
   720  		"bootx64.efi-shim-hash",
   721  		"grubx64.efi-grub-hash",
   722  		"grubx64.efi-run-grub-hash",
   723  	})
   724  
   725  	// set a mock recovery kernel
   726  	readSystemEssentialCalls := 0
   727  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   728  		readSystemEssentialCalls++
   729  		kernelRev := 1
   730  		if label == "1234" {
   731  			kernelRev = 999
   732  		}
   733  		return model, []*seed.Snap{mockKernelSeedSnap(c, snap.R(kernelRev)), mockGadgetSeedSnap(c, nil)}, nil
   734  	})
   735  	defer restore()
   736  
   737  	// set mock key resealing
   738  	resealKeysCalls := 0
   739  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   740  		c.Check(params.TPMPolicyAuthKeyFile, Equals, filepath.Join(dirs.SnapSaveDir, "device/fde", "tpm-policy-auth-key"))
   741  
   742  		resealKeysCalls++
   743  		c.Assert(params.ModelParams, HasLen, 1)
   744  
   745  		// shared parameters
   746  		c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
   747  		c.Logf("got:")
   748  		for _, ch := range params.ModelParams[0].EFILoadChains {
   749  			printChain(c, ch, "-")
   750  		}
   751  		switch resealKeysCalls {
   752  		case 1: // run key
   753  			c.Assert(params.KeyFiles, DeepEquals, []string{
   754  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
   755  			})
   756  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   757  				"snapd_recovery_mode=recover snapd_recovery_system=1234 console=ttyS0 console=tty1 panic=-1",
   758  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
   759  				"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   760  			})
   761  			// load chains
   762  			c.Assert(params.ModelParams[0].EFILoadChains, HasLen, 3)
   763  		case 2: // recovery keys
   764  			c.Assert(params.KeyFiles, DeepEquals, []string{
   765  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
   766  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
   767  			})
   768  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   769  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
   770  			})
   771  			// load chains
   772  			c.Assert(params.ModelParams[0].EFILoadChains, HasLen, 1)
   773  		default:
   774  			c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls)
   775  		}
   776  
   777  		// recovery parameters
   778  		shim := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/bootx64.efi-shim-hash"), bootloader.RoleRecovery)
   779  		grub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-grub-hash"), bootloader.RoleRecovery)
   780  		kernelGoodRecovery := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery)
   781  		// kernel from a tried recovery system
   782  		kernelTriedRecovery := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_999.snap", "kernel.efi", bootloader.RoleRecovery)
   783  		// run mode parameters
   784  		runGrub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-run-grub-hash"), bootloader.RoleRunMode)
   785  		runKernel := bootloader.NewBootFile(filepath.Join(rootdir, "var/lib/snapd/snaps/pc-kernel_500.snap"), "kernel.efi", bootloader.RoleRunMode)
   786  
   787  		switch resealKeysCalls {
   788  		case 1: // run load chain
   789  			c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{
   790  				secboot.NewLoadChain(shim,
   791  					secboot.NewLoadChain(grub,
   792  						secboot.NewLoadChain(kernelGoodRecovery),
   793  					)),
   794  				secboot.NewLoadChain(shim,
   795  					secboot.NewLoadChain(grub,
   796  						secboot.NewLoadChain(kernelTriedRecovery),
   797  					)),
   798  				secboot.NewLoadChain(shim,
   799  					secboot.NewLoadChain(grub,
   800  						secboot.NewLoadChain(runGrub,
   801  							secboot.NewLoadChain(runKernel)),
   802  					)),
   803  			})
   804  		case 2: // recovery load chains
   805  			c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{
   806  				secboot.NewLoadChain(shim,
   807  					secboot.NewLoadChain(grub,
   808  						secboot.NewLoadChain(kernelGoodRecovery),
   809  					)),
   810  			})
   811  		}
   812  
   813  		return nil
   814  	})
   815  	defer restore()
   816  
   817  	// here we don't have unasserted kernels so just set
   818  	// expectReseal to false as it doesn't matter;
   819  	// the behavior with unasserted kernel is tested in
   820  	// boot_test.go specific tests
   821  	const expectReseal = false
   822  	err = boot.ResealKeyToModeenv(rootdir, modeenv, expectReseal)
   823  	c.Assert(err, IsNil)
   824  	c.Assert(resealKeysCalls, Equals, 2)
   825  
   826  	// verify the boot chains data file for run key
   827  	runPbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "boot-chains"))
   828  	c.Assert(err, IsNil)
   829  	c.Assert(cnt, Equals, 1)
   830  	c.Check(runPbc, DeepEquals, boot.PredictableBootChains{
   831  		boot.BootChain{
   832  			BrandID:        "my-brand",
   833  			Model:          "my-model-uc20",
   834  			Grade:          "dangerous",
   835  			ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
   836  			AssetChain: []boot.BootAsset{
   837  				{
   838  					Role:   "recovery",
   839  					Name:   "bootx64.efi",
   840  					Hashes: []string{"shim-hash"},
   841  				},
   842  				{
   843  					Role:   "recovery",
   844  					Name:   "grubx64.efi",
   845  					Hashes: []string{"grub-hash"},
   846  				},
   847  			},
   848  			Kernel:         "pc-kernel",
   849  			KernelRevision: "1",
   850  			KernelCmdlines: []string{
   851  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
   852  			},
   853  		},
   854  		// includes the tried system
   855  		boot.BootChain{
   856  			BrandID:        "my-brand",
   857  			Model:          "my-model-uc20",
   858  			Grade:          "dangerous",
   859  			ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
   860  			AssetChain: []boot.BootAsset{
   861  				{
   862  					Role:   "recovery",
   863  					Name:   "bootx64.efi",
   864  					Hashes: []string{"shim-hash"},
   865  				},
   866  				{
   867  					Role:   "recovery",
   868  					Name:   "grubx64.efi",
   869  					Hashes: []string{"grub-hash"},
   870  				},
   871  			},
   872  			Kernel:         "pc-kernel",
   873  			KernelRevision: "999",
   874  			KernelCmdlines: []string{
   875  				"snapd_recovery_mode=recover snapd_recovery_system=1234 console=ttyS0 console=tty1 panic=-1",
   876  			},
   877  		},
   878  		boot.BootChain{
   879  			BrandID:        "my-brand",
   880  			Model:          "my-model-uc20",
   881  			Grade:          "dangerous",
   882  			ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
   883  			AssetChain: []boot.BootAsset{
   884  				{
   885  					Role:   "recovery",
   886  					Name:   "bootx64.efi",
   887  					Hashes: []string{"shim-hash"},
   888  				},
   889  				{
   890  					Role:   "recovery",
   891  					Name:   "grubx64.efi",
   892  					Hashes: []string{"grub-hash"},
   893  				},
   894  				{
   895  					Role:   "run-mode",
   896  					Name:   "grubx64.efi",
   897  					Hashes: []string{"run-grub-hash"},
   898  				},
   899  			},
   900  			Kernel:         "pc-kernel",
   901  			KernelRevision: "500",
   902  			KernelCmdlines: []string{
   903  				"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   904  			},
   905  		},
   906  	})
   907  	// recovery boot chains
   908  	recoveryPbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "recovery-boot-chains"))
   909  	c.Assert(err, IsNil)
   910  	c.Assert(cnt, Equals, 1)
   911  	c.Check(recoveryPbc, DeepEquals, boot.PredictableBootChains{
   912  		// only one entry for a recovery system that is known to be good
   913  		boot.BootChain{
   914  			BrandID:        "my-brand",
   915  			Model:          "my-model-uc20",
   916  			Grade:          "dangerous",
   917  			ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
   918  			AssetChain: []boot.BootAsset{
   919  				{
   920  					Role:   "recovery",
   921  					Name:   "bootx64.efi",
   922  					Hashes: []string{"shim-hash"},
   923  				},
   924  				{
   925  					Role:   "recovery",
   926  					Name:   "grubx64.efi",
   927  					Hashes: []string{"grub-hash"},
   928  				},
   929  			},
   930  			Kernel:         "pc-kernel",
   931  			KernelRevision: "1",
   932  			KernelCmdlines: []string{
   933  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
   934  			},
   935  		},
   936  	})
   937  }
   938  
   939  func (s *sealSuite) TestResealKeyToModeenvFallbackCmdline(c *C) {
   940  	rootdir := c.MkDir()
   941  	dirs.SetRootDir(rootdir)
   942  	defer dirs.SetRootDir("")
   943  
   944  	model := boottest.MakeMockUC20Model()
   945  
   946  	c.Assert(os.MkdirAll(dirs.SnapFDEDir, 0755), IsNil)
   947  	err := ioutil.WriteFile(filepath.Join(dirs.SnapFDEDir, "sealed-keys"), nil, 0644)
   948  	c.Assert(err, IsNil)
   949  
   950  	modeenv := &boot.Modeenv{
   951  		CurrentRecoverySystems: []string{"20200825"},
   952  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   953  			"asset": []string{"asset-hash-1"},
   954  		},
   955  
   956  		CurrentTrustedBootAssets: boot.BootAssetsMap{
   957  			"asset": []string{"asset-hash-1"},
   958  		},
   959  
   960  		CurrentKernels: []string{"pc-kernel_500.snap"},
   961  
   962  		// as if it is unset yet
   963  		CurrentKernelCommandLines: nil,
   964  
   965  		Model:          model.Model(),
   966  		BrandID:        model.BrandID(),
   967  		Grade:          string(model.Grade()),
   968  		ModelSignKeyID: model.SignKeyID(),
   969  	}
   970  
   971  	err = boot.WriteBootChains(nil, filepath.Join(dirs.SnapFDEDir, "boot-chains"), 9)
   972  	c.Assert(err, IsNil)
   973  	// mock asset cache
   974  	mockAssetsCache(c, rootdir, "trusted", []string{
   975  		"asset-asset-hash-1",
   976  	})
   977  
   978  	// match one of current kernels
   979  	runKernelBf := bootloader.NewBootFile("/var/lib/snapd/snap/pc-kernel_500.snap", "kernel.efi", bootloader.RoleRunMode)
   980  	// match the seed kernel
   981  	recoveryKernelBf := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery)
   982  
   983  	bootdir := c.MkDir()
   984  	mtbl := bootloadertest.Mock("trusted", bootdir).WithTrustedAssets()
   985  	mtbl.TrustedAssetsList = []string{"asset-1"}
   986  	mtbl.StaticCommandLine = "static cmdline"
   987  	mtbl.BootChainList = []bootloader.BootFile{
   988  		bootloader.NewBootFile("", "asset", bootloader.RoleRunMode),
   989  		runKernelBf,
   990  	}
   991  	mtbl.RecoveryBootChainList = []bootloader.BootFile{
   992  		bootloader.NewBootFile("", "asset", bootloader.RoleRecovery),
   993  		recoveryKernelBf,
   994  	}
   995  	bootloader.Force(mtbl)
   996  	defer bootloader.Force(nil)
   997  
   998  	// set a mock recovery kernel
   999  	readSystemEssentialCalls := 0
  1000  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
  1001  		readSystemEssentialCalls++
  1002  		return model, []*seed.Snap{mockKernelSeedSnap(c, snap.R(1)), mockGadgetSeedSnap(c, nil)}, nil
  1003  	})
  1004  	defer restore()
  1005  
  1006  	// set mock key resealing
  1007  	resealKeysCalls := 0
  1008  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
  1009  		resealKeysCalls++
  1010  		c.Assert(params.ModelParams, HasLen, 1)
  1011  		c.Logf("reseal: %+v", params)
  1012  		switch resealKeysCalls {
  1013  		case 1:
  1014  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
  1015  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
  1016  				"snapd_recovery_mode=run static cmdline",
  1017  			})
  1018  		case 2:
  1019  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
  1020  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
  1021  			})
  1022  		default:
  1023  			c.Fatalf("unexpected number of reseal calls, %v", params)
  1024  		}
  1025  		return nil
  1026  	})
  1027  	defer restore()
  1028  
  1029  	const expectReseal = false
  1030  	err = boot.ResealKeyToModeenv(rootdir, modeenv, expectReseal)
  1031  	c.Assert(err, IsNil)
  1032  	c.Assert(resealKeysCalls, Equals, 2)
  1033  
  1034  	// verify the boot chains data file
  1035  	pbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "boot-chains"))
  1036  	c.Assert(err, IsNil)
  1037  	c.Assert(cnt, Equals, 10)
  1038  	c.Check(pbc, DeepEquals, boot.PredictableBootChains{
  1039  		boot.BootChain{
  1040  			BrandID:        "my-brand",
  1041  			Model:          "my-model-uc20",
  1042  			Grade:          "dangerous",
  1043  			ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
  1044  			AssetChain: []boot.BootAsset{
  1045  				{
  1046  					Role:   "recovery",
  1047  					Name:   "asset",
  1048  					Hashes: []string{"asset-hash-1"},
  1049  				},
  1050  			},
  1051  			Kernel:         "pc-kernel",
  1052  			KernelRevision: "1",
  1053  			KernelCmdlines: []string{
  1054  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
  1055  			},
  1056  		},
  1057  		boot.BootChain{
  1058  			BrandID:        "my-brand",
  1059  			Model:          "my-model-uc20",
  1060  			Grade:          "dangerous",
  1061  			ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
  1062  			AssetChain: []boot.BootAsset{
  1063  				{
  1064  					Role:   "run-mode",
  1065  					Name:   "asset",
  1066  					Hashes: []string{"asset-hash-1"},
  1067  				},
  1068  			},
  1069  			Kernel:         "pc-kernel",
  1070  			KernelRevision: "500",
  1071  			KernelCmdlines: []string{
  1072  				"snapd_recovery_mode=run static cmdline",
  1073  			},
  1074  		},
  1075  	})
  1076  }
  1077  
  1078  func (s *sealSuite) TestRecoveryBootChainsForSystems(c *C) {
  1079  	for _, tc := range []struct {
  1080  		desc                    string
  1081  		assetsMap               boot.BootAssetsMap
  1082  		recoverySystems         []string
  1083  		undefinedKernel         bool
  1084  		gadgetFilesForSystem    map[string][][]string
  1085  		expectedAssets          []boot.BootAsset
  1086  		expectedKernelRevs      []int
  1087  		expectedBootChainsCount int
  1088  		// in the order of boot chains
  1089  		expectedCmdlines [][]string
  1090  		err              string
  1091  	}{
  1092  		{
  1093  			desc:            "transition sequences",
  1094  			recoverySystems: []string{"20200825"},
  1095  			assetsMap: boot.BootAssetsMap{
  1096  				"grubx64.efi": []string{"grub-hash-1", "grub-hash-2"},
  1097  				"bootx64.efi": []string{"shim-hash-1"},
  1098  			},
  1099  			expectedAssets: []boot.BootAsset{
  1100  				{Role: bootloader.RoleRecovery, Name: "bootx64.efi", Hashes: []string{"shim-hash-1"}},
  1101  				{Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1", "grub-hash-2"}},
  1102  			},
  1103  			expectedKernelRevs: []int{1},
  1104  			expectedCmdlines: [][]string{
  1105  				{"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1"},
  1106  			},
  1107  		},
  1108  		{
  1109  			desc:            "two systems",
  1110  			recoverySystems: []string{"20200825", "20200831"},
  1111  			assetsMap: boot.BootAssetsMap{
  1112  				"grubx64.efi": []string{"grub-hash-1", "grub-hash-2"},
  1113  				"bootx64.efi": []string{"shim-hash-1"},
  1114  			},
  1115  			expectedAssets: []boot.BootAsset{
  1116  				{Role: bootloader.RoleRecovery, Name: "bootx64.efi", Hashes: []string{"shim-hash-1"}},
  1117  				{Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1", "grub-hash-2"}},
  1118  			},
  1119  			expectedKernelRevs: []int{1, 3},
  1120  			expectedCmdlines: [][]string{
  1121  				{"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1"},
  1122  				{"snapd_recovery_mode=recover snapd_recovery_system=20200831 console=ttyS0 console=tty1 panic=-1"},
  1123  			},
  1124  		},
  1125  		{
  1126  			desc:            "non transition sequence",
  1127  			recoverySystems: []string{"20200825"},
  1128  			assetsMap: boot.BootAssetsMap{
  1129  				"grubx64.efi": []string{"grub-hash-1"},
  1130  				"bootx64.efi": []string{"shim-hash-1"},
  1131  			},
  1132  			expectedAssets: []boot.BootAsset{
  1133  				{Role: bootloader.RoleRecovery, Name: "bootx64.efi", Hashes: []string{"shim-hash-1"}},
  1134  				{Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1"}},
  1135  			},
  1136  			expectedKernelRevs: []int{1},
  1137  			expectedCmdlines: [][]string{
  1138  				{"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1"},
  1139  			},
  1140  		},
  1141  		{
  1142  			desc:            "two systems with command lines",
  1143  			recoverySystems: []string{"20200825", "20200831"},
  1144  			assetsMap: boot.BootAssetsMap{
  1145  				"grubx64.efi": []string{"grub-hash-1", "grub-hash-2"},
  1146  				"bootx64.efi": []string{"shim-hash-1"},
  1147  			},
  1148  			expectedAssets: []boot.BootAsset{
  1149  				{Role: bootloader.RoleRecovery, Name: "bootx64.efi", Hashes: []string{"shim-hash-1"}},
  1150  				{Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1", "grub-hash-2"}},
  1151  			},
  1152  			gadgetFilesForSystem: map[string][][]string{
  1153  				"20200825": {
  1154  					{"cmdline.extra", "extra for 20200825"},
  1155  				},
  1156  				"20200831": {
  1157  					// TODO: make it a cmdline.full
  1158  					{"cmdline.extra", "some-extra-for-20200831"},
  1159  				},
  1160  			},
  1161  			expectedKernelRevs: []int{1, 3},
  1162  			expectedCmdlines: [][]string{
  1163  				{"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1 extra for 20200825"},
  1164  				{"snapd_recovery_mode=recover snapd_recovery_system=20200831 console=ttyS0 console=tty1 panic=-1 some-extra-for-20200831"},
  1165  			},
  1166  		},
  1167  		{
  1168  			desc:            "three systems, one with different model",
  1169  			recoverySystems: []string{"20200825", "20200831", "off-model"},
  1170  			assetsMap: boot.BootAssetsMap{
  1171  				"grubx64.efi": []string{"grub-hash-1", "grub-hash-2"},
  1172  				"bootx64.efi": []string{"shim-hash-1"},
  1173  			},
  1174  			expectedAssets: []boot.BootAsset{
  1175  				{Role: bootloader.RoleRecovery, Name: "bootx64.efi", Hashes: []string{"shim-hash-1"}},
  1176  				{Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1", "grub-hash-2"}},
  1177  			},
  1178  			expectedKernelRevs: []int{1, 3},
  1179  			expectedCmdlines: [][]string{
  1180  				{"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1"},
  1181  				{"snapd_recovery_mode=recover snapd_recovery_system=20200831 console=ttyS0 console=tty1 panic=-1"},
  1182  			},
  1183  			expectedBootChainsCount: 2,
  1184  		},
  1185  		{
  1186  			desc:            "invalid recovery system label",
  1187  			recoverySystems: []string{"0"},
  1188  			err:             `cannot read system "0" seed: invalid system seed`,
  1189  		},
  1190  	} {
  1191  		c.Logf("tc: %q", tc.desc)
  1192  		rootdir := c.MkDir()
  1193  		dirs.SetRootDir(rootdir)
  1194  		defer dirs.SetRootDir("")
  1195  
  1196  		model := boottest.MakeMockUC20Model()
  1197  
  1198  		// set recovery kernel
  1199  		restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
  1200  			systemModel := model
  1201  			kernelRev := 1
  1202  			switch label {
  1203  			case "20200825":
  1204  				// nothing special
  1205  			case "20200831":
  1206  				kernelRev = 3
  1207  			case "off-model":
  1208  				systemModel = boottest.MakeMockUC20Model(map[string]interface{}{
  1209  					"model": "model-mismatch-uc20",
  1210  				})
  1211  			default:
  1212  				return nil, nil, fmt.Errorf("invalid system seed")
  1213  			}
  1214  			return systemModel, []*seed.Snap{mockKernelSeedSnap(c, snap.R(kernelRev)), mockGadgetSeedSnap(c, tc.gadgetFilesForSystem[label])}, nil
  1215  		})
  1216  		defer restore()
  1217  
  1218  		grubDir := filepath.Join(rootdir, "run/mnt/ubuntu-seed")
  1219  		err := createMockGrubCfg(grubDir)
  1220  		c.Assert(err, IsNil)
  1221  
  1222  		bl, err := bootloader.Find(grubDir, &bootloader.Options{Role: bootloader.RoleRecovery})
  1223  		c.Assert(err, IsNil)
  1224  		tbl, ok := bl.(bootloader.TrustedAssetsBootloader)
  1225  		c.Assert(ok, Equals, true)
  1226  
  1227  		modeenv := &boot.Modeenv{
  1228  			CurrentTrustedRecoveryBootAssets: tc.assetsMap,
  1229  
  1230  			BrandID:        model.BrandID(),
  1231  			Model:          model.Model(),
  1232  			ModelSignKeyID: model.SignKeyID(),
  1233  			Grade:          string(model.Grade()),
  1234  		}
  1235  
  1236  		includeTryModel := false
  1237  		bc, err := boot.RecoveryBootChainsForSystems(tc.recoverySystems, tbl, modeenv, includeTryModel)
  1238  		if tc.err == "" {
  1239  			c.Assert(err, IsNil)
  1240  			if tc.expectedBootChainsCount == 0 {
  1241  				// usually there is a boot chain for each recovery system
  1242  				c.Assert(bc, HasLen, len(tc.recoverySystems))
  1243  			} else {
  1244  				c.Assert(bc, HasLen, tc.expectedBootChainsCount)
  1245  			}
  1246  			c.Assert(tc.expectedCmdlines, HasLen, len(bc), Commentf("broken test, expected command lines must be of the same length as recovery systems and recovery boot chains"))
  1247  			for i, chain := range bc {
  1248  				c.Assert(chain.AssetChain, DeepEquals, tc.expectedAssets)
  1249  				c.Assert(chain.Kernel, Equals, "pc-kernel")
  1250  				expectedKernelRev := tc.expectedKernelRevs[i]
  1251  				c.Assert(chain.KernelRevision, Equals, fmt.Sprintf("%d", expectedKernelRev))
  1252  				c.Assert(chain.KernelBootFile(), DeepEquals, bootloader.BootFile{
  1253  					Snap: fmt.Sprintf("/var/lib/snapd/seed/snaps/pc-kernel_%d.snap", expectedKernelRev),
  1254  					Path: "kernel.efi",
  1255  					Role: bootloader.RoleRecovery,
  1256  				})
  1257  				c.Assert(chain.KernelCmdlines, DeepEquals, tc.expectedCmdlines[i])
  1258  			}
  1259  		} else {
  1260  			c.Assert(err, ErrorMatches, tc.err)
  1261  		}
  1262  
  1263  	}
  1264  
  1265  }
  1266  
  1267  func createMockGrubCfg(baseDir string) error {
  1268  	cfg := filepath.Join(baseDir, "EFI/ubuntu/grub.cfg")
  1269  	if err := os.MkdirAll(filepath.Dir(cfg), 0755); err != nil {
  1270  		return err
  1271  	}
  1272  	return ioutil.WriteFile(cfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644)
  1273  }
  1274  
  1275  func (s *sealSuite) TestSealKeyModelParams(c *C) {
  1276  	rootdir := c.MkDir()
  1277  	dirs.SetRootDir(rootdir)
  1278  	defer dirs.SetRootDir("")
  1279  
  1280  	model := boottest.MakeMockUC20Model()
  1281  
  1282  	roleToBlName := map[bootloader.Role]string{
  1283  		bootloader.RoleRecovery: "grub",
  1284  		bootloader.RoleRunMode:  "grub",
  1285  	}
  1286  	// mock asset cache
  1287  	mockAssetsCache(c, rootdir, "grub", []string{
  1288  		"shim-shim-hash",
  1289  		"loader-loader-hash1",
  1290  		"loader-loader-hash2",
  1291  	})
  1292  
  1293  	oldmodel := boottest.MakeMockUC20Model(map[string]interface{}{
  1294  		"model":     "old-model-uc20",
  1295  		"timestamp": "2019-10-01T08:00:00+00:00",
  1296  	})
  1297  
  1298  	// old recovery
  1299  	oldrc := boot.BootChain{
  1300  		BrandID:        oldmodel.BrandID(),
  1301  		Model:          oldmodel.Model(),
  1302  		Grade:          oldmodel.Grade(),
  1303  		ModelSignKeyID: oldmodel.SignKeyID(),
  1304  		AssetChain: []boot.BootAsset{
  1305  			{Name: "shim", Role: bootloader.RoleRecovery, Hashes: []string{"shim-hash"}},
  1306  			{Name: "loader", Role: bootloader.RoleRecovery, Hashes: []string{"loader-hash1"}},
  1307  		},
  1308  		KernelCmdlines: []string{"panic=1", "oldrc"},
  1309  	}
  1310  	oldkbf := bootloader.BootFile{Snap: "pc-kernel_1.snap"}
  1311  	oldrc.SetKernelBootFile(oldkbf)
  1312  
  1313  	// recovery
  1314  	rc1 := boot.BootChain{
  1315  		BrandID:        model.BrandID(),
  1316  		Model:          model.Model(),
  1317  		Grade:          model.Grade(),
  1318  		ModelSignKeyID: model.SignKeyID(),
  1319  		AssetChain: []boot.BootAsset{
  1320  			{Name: "shim", Role: bootloader.RoleRecovery, Hashes: []string{"shim-hash"}},
  1321  			{Name: "loader", Role: bootloader.RoleRecovery, Hashes: []string{"loader-hash1"}},
  1322  		},
  1323  		KernelCmdlines: []string{"panic=1", "rc1"},
  1324  	}
  1325  	rc1kbf := bootloader.BootFile{Snap: "pc-kernel_10.snap"}
  1326  	rc1.SetKernelBootFile(rc1kbf)
  1327  
  1328  	// run system
  1329  	runc1 := boot.BootChain{
  1330  		BrandID:        model.BrandID(),
  1331  		Model:          model.Model(),
  1332  		Grade:          model.Grade(),
  1333  		ModelSignKeyID: model.SignKeyID(),
  1334  		AssetChain: []boot.BootAsset{
  1335  			{Name: "shim", Role: bootloader.RoleRecovery, Hashes: []string{"shim-hash"}},
  1336  			{Name: "loader", Role: bootloader.RoleRecovery, Hashes: []string{"loader-hash1"}},
  1337  			{Name: "loader", Role: bootloader.RoleRunMode, Hashes: []string{"loader-hash2"}},
  1338  		},
  1339  		KernelCmdlines: []string{"panic=1", "runc1"},
  1340  	}
  1341  	runc1kbf := bootloader.BootFile{Snap: "pc-kernel_50.snap"}
  1342  	runc1.SetKernelBootFile(runc1kbf)
  1343  
  1344  	pbc := boot.ToPredictableBootChains([]boot.BootChain{rc1, runc1, oldrc})
  1345  
  1346  	shim := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/shim-shim-hash"), bootloader.RoleRecovery)
  1347  	loader1 := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/loader-loader-hash1"), bootloader.RoleRecovery)
  1348  	loader2 := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/loader-loader-hash2"), bootloader.RoleRunMode)
  1349  
  1350  	params, err := boot.SealKeyModelParams(pbc, roleToBlName)
  1351  	c.Assert(err, IsNil)
  1352  	c.Check(params, HasLen, 2)
  1353  	c.Check(params[0].Model.Model(), Equals, model.Model())
  1354  	// NB: merging of lists makes panic=1 appear once
  1355  	c.Check(params[0].KernelCmdlines, DeepEquals, []string{"panic=1", "rc1", "runc1"})
  1356  
  1357  	c.Check(params[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{
  1358  		secboot.NewLoadChain(shim,
  1359  			secboot.NewLoadChain(loader1,
  1360  				secboot.NewLoadChain(rc1kbf))),
  1361  		secboot.NewLoadChain(shim,
  1362  			secboot.NewLoadChain(loader1,
  1363  				secboot.NewLoadChain(loader2,
  1364  					secboot.NewLoadChain(runc1kbf)))),
  1365  	})
  1366  
  1367  	c.Check(params[1].Model.Model(), Equals, oldmodel.Model())
  1368  	c.Check(params[1].KernelCmdlines, DeepEquals, []string{"oldrc", "panic=1"})
  1369  	c.Check(params[1].EFILoadChains, DeepEquals, []*secboot.LoadChain{
  1370  		secboot.NewLoadChain(shim,
  1371  			secboot.NewLoadChain(loader1,
  1372  				secboot.NewLoadChain(oldkbf))),
  1373  	})
  1374  }
  1375  
  1376  func (s *sealSuite) TestIsResealNeeded(c *C) {
  1377  	if os.Geteuid() == 0 {
  1378  		c.Skip("the test cannot be run by the root user")
  1379  	}
  1380  
  1381  	chains := []boot.BootChain{
  1382  		{
  1383  			BrandID:        "mybrand",
  1384  			Model:          "foo",
  1385  			Grade:          "signed",
  1386  			ModelSignKeyID: "my-key-id",
  1387  			AssetChain: []boot.BootAsset{
  1388  				// hashes will be sorted
  1389  				{Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"x", "y"}},
  1390  				{Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"c", "d"}},
  1391  				{Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"z", "x"}},
  1392  			},
  1393  			Kernel:         "pc-kernel-other",
  1394  			KernelRevision: "2345",
  1395  			KernelCmdlines: []string{`snapd_recovery_mode=run foo`},
  1396  		}, {
  1397  			BrandID:        "mybrand",
  1398  			Model:          "foo",
  1399  			Grade:          "dangerous",
  1400  			ModelSignKeyID: "my-key-id",
  1401  			AssetChain: []boot.BootAsset{
  1402  				// hashes will be sorted
  1403  				{Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"y", "x"}},
  1404  				{Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"c", "d"}},
  1405  			},
  1406  			Kernel:         "pc-kernel-recovery",
  1407  			KernelRevision: "1234",
  1408  			KernelCmdlines: []string{`snapd_recovery_mode=recover foo`},
  1409  		},
  1410  	}
  1411  
  1412  	pbc := boot.ToPredictableBootChains(chains)
  1413  
  1414  	rootdir := c.MkDir()
  1415  	err := boot.WriteBootChains(pbc, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), 2)
  1416  	c.Assert(err, IsNil)
  1417  
  1418  	needed, _, err := boot.IsResealNeeded(pbc, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), false)
  1419  	c.Assert(err, IsNil)
  1420  	c.Check(needed, Equals, false)
  1421  
  1422  	otherchain := []boot.BootChain{pbc[0]}
  1423  	needed, cnt, err := boot.IsResealNeeded(otherchain, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), false)
  1424  	c.Assert(err, IsNil)
  1425  	// chains are different
  1426  	c.Check(needed, Equals, true)
  1427  	c.Check(cnt, Equals, 3)
  1428  
  1429  	// boot-chains does not exist, we cannot compare so advise to reseal
  1430  	otherRootdir := c.MkDir()
  1431  	needed, cnt, err = boot.IsResealNeeded(otherchain, filepath.Join(dirs.SnapFDEDirUnder(otherRootdir), "boot-chains"), false)
  1432  	c.Assert(err, IsNil)
  1433  	c.Check(needed, Equals, true)
  1434  	c.Check(cnt, Equals, 1)
  1435  
  1436  	// exists but cannot be read
  1437  	c.Assert(os.Chmod(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), 0000), IsNil)
  1438  	defer os.Chmod(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), 0755)
  1439  	needed, _, err = boot.IsResealNeeded(otherchain, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), false)
  1440  	c.Assert(err, ErrorMatches, "cannot open existing boot chains data file: open .*/boot-chains: permission denied")
  1441  	c.Check(needed, Equals, false)
  1442  
  1443  	// unrevisioned kernel chain
  1444  	unrevchain := []boot.BootChain{pbc[0], pbc[1]}
  1445  	unrevchain[1].KernelRevision = ""
  1446  	// write on disk
  1447  	bootChainsFile := filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains")
  1448  	err = boot.WriteBootChains(unrevchain, bootChainsFile, 2)
  1449  	c.Assert(err, IsNil)
  1450  
  1451  	needed, cnt, err = boot.IsResealNeeded(pbc, bootChainsFile, false)
  1452  	c.Assert(err, IsNil)
  1453  	c.Check(needed, Equals, true)
  1454  	c.Check(cnt, Equals, 3)
  1455  
  1456  	// cases falling back to expectReseal
  1457  	needed, _, err = boot.IsResealNeeded(unrevchain, bootChainsFile, false)
  1458  	c.Assert(err, IsNil)
  1459  	c.Check(needed, Equals, false)
  1460  
  1461  	needed, cnt, err = boot.IsResealNeeded(unrevchain, bootChainsFile, true)
  1462  	c.Assert(err, IsNil)
  1463  	c.Check(needed, Equals, true)
  1464  	c.Check(cnt, Equals, 3)
  1465  }
  1466  
  1467  func (s *sealSuite) TestSealToModeenvWithFdeHookHappy(c *C) {
  1468  	rootdir := c.MkDir()
  1469  	dirs.SetRootDir(rootdir)
  1470  	defer dirs.SetRootDir("")
  1471  
  1472  	restore := boot.MockHasFDESetupHook(func() (bool, error) {
  1473  		return true, nil
  1474  	})
  1475  	defer restore()
  1476  
  1477  	model := boottest.MakeMockUC20Model()
  1478  
  1479  	n := 0
  1480  	var runFDESetupHookReqs []*fde.SetupRequest
  1481  	restore = boot.MockRunFDESetupHook(func(req *fde.SetupRequest) ([]byte, error) {
  1482  		n++
  1483  		runFDESetupHookReqs = append(runFDESetupHookReqs, req)
  1484  
  1485  		key := []byte(fmt.Sprintf("key-%v", strconv.Itoa(n)))
  1486  		return key, nil
  1487  	})
  1488  	defer restore()
  1489  	keyToSave := make(map[string][]byte)
  1490  	restore = boot.MockSecbootSealKeysWithFDESetupHook(func(runHook fde.RunSetupHookFunc, skrs []secboot.SealKeyRequest, params *secboot.SealKeysWithFDESetupHookParams) error {
  1491  		c.Check(params.Model.Model(), Equals, model.Model())
  1492  		c.Check(params.AuxKeyFile, Equals, filepath.Join(boot.InstallHostFDESaveDir, "aux-key"))
  1493  		for _, skr := range skrs {
  1494  			out, err := runHook(&fde.SetupRequest{
  1495  				Key:     skr.Key,
  1496  				KeyName: skr.KeyName,
  1497  			})
  1498  			c.Assert(err, IsNil)
  1499  			keyToSave[skr.KeyFile] = out
  1500  		}
  1501  		return nil
  1502  	})
  1503  	defer restore()
  1504  
  1505  	modeenv := &boot.Modeenv{
  1506  		RecoverySystem: "20200825",
  1507  		Model:          model.Model(),
  1508  		BrandID:        model.BrandID(),
  1509  		Grade:          string(model.Grade()),
  1510  		ModelSignKeyID: model.SignKeyID(),
  1511  	}
  1512  	key := secboot.EncryptionKey{1, 2, 3, 4}
  1513  	saveKey := secboot.EncryptionKey{5, 6, 7, 8}
  1514  
  1515  	err := boot.SealKeyToModeenv(key, saveKey, model, modeenv)
  1516  	c.Assert(err, IsNil)
  1517  	// check that runFDESetupHook was called the expected way
  1518  	c.Check(runFDESetupHookReqs, DeepEquals, []*fde.SetupRequest{
  1519  		{Key: key, KeyName: "ubuntu-data"},
  1520  		{Key: key, KeyName: "ubuntu-data"},
  1521  		{Key: saveKey, KeyName: "ubuntu-save"},
  1522  	})
  1523  	// check that the sealed keys got written to the expected places
  1524  	for i, p := range []string{
  1525  		filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
  1526  		filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
  1527  		filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
  1528  	} {
  1529  		// Check for a valid platform handle, encrypted payload (base64)
  1530  		mockedSealedKey := []byte(fmt.Sprintf("key-%v", strconv.Itoa(i+1)))
  1531  		c.Check(keyToSave[p], DeepEquals, mockedSealedKey)
  1532  	}
  1533  
  1534  	marker := filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "sealed-keys")
  1535  	c.Check(marker, testutil.FileEquals, "fde-setup-hook")
  1536  }
  1537  
  1538  func (s *sealSuite) TestSealToModeenvWithFdeHookSad(c *C) {
  1539  	rootdir := c.MkDir()
  1540  	dirs.SetRootDir(rootdir)
  1541  	defer dirs.SetRootDir("")
  1542  
  1543  	restore := boot.MockHasFDESetupHook(func() (bool, error) {
  1544  		return true, nil
  1545  	})
  1546  	defer restore()
  1547  
  1548  	restore = boot.MockSecbootSealKeysWithFDESetupHook(func(fde.RunSetupHookFunc, []secboot.SealKeyRequest, *secboot.SealKeysWithFDESetupHookParams) error {
  1549  		return fmt.Errorf("hook failed")
  1550  	})
  1551  	defer restore()
  1552  
  1553  	modeenv := &boot.Modeenv{
  1554  		RecoverySystem: "20200825",
  1555  	}
  1556  	key := secboot.EncryptionKey{1, 2, 3, 4}
  1557  	saveKey := secboot.EncryptionKey{5, 6, 7, 8}
  1558  
  1559  	model := boottest.MakeMockUC20Model()
  1560  	err := boot.SealKeyToModeenv(key, saveKey, model, modeenv)
  1561  	c.Assert(err, ErrorMatches, "hook failed")
  1562  	marker := filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "sealed-keys")
  1563  	c.Check(marker, testutil.FileAbsent)
  1564  }
  1565  
  1566  func (s *sealSuite) TestResealKeyToModeenvWithFdeHookCalled(c *C) {
  1567  	rootdir := c.MkDir()
  1568  	dirs.SetRootDir(rootdir)
  1569  	defer dirs.SetRootDir("")
  1570  
  1571  	resealKeyToModeenvUsingFDESetupHookCalled := 0
  1572  	restore := boot.MockResealKeyToModeenvUsingFDESetupHook(func(string, *boot.Modeenv, bool) error {
  1573  		resealKeyToModeenvUsingFDESetupHookCalled++
  1574  		return nil
  1575  	})
  1576  	defer restore()
  1577  
  1578  	// TODO: this simulates that the hook is not available yet
  1579  	//       because of e.g. seeding. Longer term there will be
  1580  	//       more, see TODO in resealKeyToModeenvUsingFDESetupHookImpl
  1581  	restore = boot.MockHasFDESetupHook(func() (bool, error) {
  1582  		return false, fmt.Errorf("hook not available yet because e.g. seeding")
  1583  	})
  1584  	defer restore()
  1585  
  1586  	marker := filepath.Join(dirs.SnapFDEDirUnder(rootdir), "sealed-keys")
  1587  	err := os.MkdirAll(filepath.Dir(marker), 0755)
  1588  	c.Assert(err, IsNil)
  1589  	err = ioutil.WriteFile(marker, []byte("fde-setup-hook"), 0644)
  1590  	c.Assert(err, IsNil)
  1591  
  1592  	model := boottest.MakeMockUC20Model()
  1593  	modeenv := &boot.Modeenv{
  1594  		RecoverySystem: "20200825",
  1595  		Model:          model.Model(),
  1596  		BrandID:        model.BrandID(),
  1597  		Grade:          string(model.Grade()),
  1598  		ModelSignKeyID: model.SignKeyID(),
  1599  	}
  1600  	expectReseal := false
  1601  	err = boot.ResealKeyToModeenv(rootdir, modeenv, expectReseal)
  1602  	c.Assert(err, IsNil)
  1603  	c.Check(resealKeyToModeenvUsingFDESetupHookCalled, Equals, 1)
  1604  }
  1605  
  1606  func (s *sealSuite) TestResealKeyToModeenvWithFdeHookVerySad(c *C) {
  1607  	rootdir := c.MkDir()
  1608  	dirs.SetRootDir(rootdir)
  1609  	defer dirs.SetRootDir("")
  1610  
  1611  	resealKeyToModeenvUsingFDESetupHookCalled := 0
  1612  	restore := boot.MockResealKeyToModeenvUsingFDESetupHook(func(string, *boot.Modeenv, bool) error {
  1613  		resealKeyToModeenvUsingFDESetupHookCalled++
  1614  		return fmt.Errorf("fde setup hook failed")
  1615  	})
  1616  	defer restore()
  1617  
  1618  	marker := filepath.Join(dirs.SnapFDEDirUnder(rootdir), "sealed-keys")
  1619  	err := os.MkdirAll(filepath.Dir(marker), 0755)
  1620  	c.Assert(err, IsNil)
  1621  	err = ioutil.WriteFile(marker, []byte("fde-setup-hook"), 0644)
  1622  	c.Assert(err, IsNil)
  1623  
  1624  	model := boottest.MakeMockUC20Model()
  1625  	modeenv := &boot.Modeenv{
  1626  		RecoverySystem: "20200825",
  1627  		Model:          model.Model(),
  1628  		BrandID:        model.BrandID(),
  1629  		Grade:          string(model.Grade()),
  1630  		ModelSignKeyID: model.SignKeyID(),
  1631  	}
  1632  	expectReseal := false
  1633  	err = boot.ResealKeyToModeenv(rootdir, modeenv, expectReseal)
  1634  	c.Assert(err, ErrorMatches, "fde setup hook failed")
  1635  	c.Check(resealKeyToModeenvUsingFDESetupHookCalled, Equals, 1)
  1636  }
  1637  
  1638  func (s *sealSuite) TestResealKeyToModeenvWithTryModel(c *C) {
  1639  	rootdir := c.MkDir()
  1640  	dirs.SetRootDir(rootdir)
  1641  	defer dirs.SetRootDir("")
  1642  
  1643  	c.Assert(os.MkdirAll(dirs.SnapFDEDir, 0755), IsNil)
  1644  	err := ioutil.WriteFile(filepath.Join(dirs.SnapFDEDir, "sealed-keys"), nil, 0644)
  1645  	c.Assert(err, IsNil)
  1646  
  1647  	err = createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-seed"))
  1648  	c.Assert(err, IsNil)
  1649  
  1650  	err = createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-boot"))
  1651  	c.Assert(err, IsNil)
  1652  
  1653  	model := boottest.MakeMockUC20Model()
  1654  	// a try model which would normally only appear during remodel
  1655  	tryModel := boottest.MakeMockUC20Model(map[string]interface{}{
  1656  		"model": "try-my-model-uc20",
  1657  		"grade": "secured",
  1658  	})
  1659  
  1660  	modeenv := &boot.Modeenv{
  1661  		// recovery system set up like during a remodel, right before a
  1662  		// set-device is called
  1663  		CurrentRecoverySystems: []string{"20200825", "1234", "off-model"},
  1664  		GoodRecoverySystems:    []string{"20200825", "1234"},
  1665  
  1666  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  1667  			"grubx64.efi": []string{"grub-hash"},
  1668  			"bootx64.efi": []string{"shim-hash"},
  1669  		},
  1670  
  1671  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  1672  			"grubx64.efi": []string{"run-grub-hash"},
  1673  		},
  1674  
  1675  		CurrentKernels: []string{"pc-kernel_500.snap"},
  1676  
  1677  		CurrentKernelCommandLines: boot.BootCommandLines{
  1678  			"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
  1679  		},
  1680  		// the current model
  1681  		Model:          model.Model(),
  1682  		BrandID:        model.BrandID(),
  1683  		Grade:          string(model.Grade()),
  1684  		ModelSignKeyID: model.SignKeyID(),
  1685  		// the try model
  1686  		TryModel:          tryModel.Model(),
  1687  		TryBrandID:        tryModel.BrandID(),
  1688  		TryGrade:          string(tryModel.Grade()),
  1689  		TryModelSignKeyID: tryModel.SignKeyID(),
  1690  	}
  1691  
  1692  	// mock asset cache
  1693  	mockAssetsCache(c, rootdir, "grub", []string{
  1694  		"bootx64.efi-shim-hash",
  1695  		"grubx64.efi-grub-hash",
  1696  		"grubx64.efi-run-grub-hash",
  1697  	})
  1698  
  1699  	// set a mock recovery kernel
  1700  	readSystemEssentialCalls := 0
  1701  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
  1702  		readSystemEssentialCalls++
  1703  		kernelRev := 1
  1704  		systemModel := model
  1705  		if label == "1234" {
  1706  			// recovery system for new model
  1707  			kernelRev = 999
  1708  			systemModel = tryModel
  1709  		}
  1710  		if label == "off-model" {
  1711  			// a model that matches neither current not try models
  1712  			systemModel = boottest.MakeMockUC20Model(map[string]interface{}{
  1713  				"model": "different-model-uc20",
  1714  				"grade": "secured",
  1715  			})
  1716  		}
  1717  		return systemModel, []*seed.Snap{mockKernelSeedSnap(c, snap.R(kernelRev)), mockGadgetSeedSnap(c, nil)}, nil
  1718  	})
  1719  	defer restore()
  1720  
  1721  	// set mock key resealing
  1722  	resealKeysCalls := 0
  1723  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
  1724  		c.Check(params.TPMPolicyAuthKeyFile, Equals, filepath.Join(dirs.SnapSaveDir, "device/fde", "tpm-policy-auth-key"))
  1725  		c.Logf("got:")
  1726  		for _, mp := range params.ModelParams {
  1727  			c.Logf("model: %v", mp.Model.Model())
  1728  			for _, ch := range mp.EFILoadChains {
  1729  				printChain(c, ch, "-")
  1730  			}
  1731  		}
  1732  
  1733  		resealKeysCalls++
  1734  
  1735  		switch resealKeysCalls {
  1736  		case 1: // run key
  1737  			c.Assert(params.KeyFiles, DeepEquals, []string{
  1738  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
  1739  			})
  1740  			// 2 models, one current and one try model
  1741  			c.Assert(params.ModelParams, HasLen, 2)
  1742  			// shared parameters
  1743  			c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
  1744  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
  1745  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
  1746  				"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
  1747  			})
  1748  			// 2 load chains (bootloader + run kernel, bootloader + recovery kernel)
  1749  			c.Assert(params.ModelParams[0].EFILoadChains, HasLen, 2)
  1750  
  1751  			c.Assert(params.ModelParams[1].Model.Model(), Equals, "try-my-model-uc20")
  1752  			c.Assert(params.ModelParams[1].KernelCmdlines, DeepEquals, []string{
  1753  				"snapd_recovery_mode=recover snapd_recovery_system=1234 console=ttyS0 console=tty1 panic=-1",
  1754  				"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
  1755  			})
  1756  			// 2 load chains (bootloader + run kernel, bootloader + recovery kernel)
  1757  			c.Assert(params.ModelParams[1].EFILoadChains, HasLen, 2)
  1758  		case 2: // recovery keys
  1759  			c.Assert(params.KeyFiles, DeepEquals, []string{
  1760  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
  1761  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
  1762  			})
  1763  			// only the current model
  1764  			c.Assert(params.ModelParams, HasLen, 1)
  1765  			// shared parameters
  1766  			c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
  1767  			for _, mp := range params.ModelParams {
  1768  				c.Assert(mp.KernelCmdlines, DeepEquals, []string{
  1769  					"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
  1770  				})
  1771  				// load chains
  1772  				c.Assert(mp.EFILoadChains, HasLen, 1)
  1773  			}
  1774  		default:
  1775  			c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls)
  1776  		}
  1777  
  1778  		// recovery parameters
  1779  		shim := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/bootx64.efi-shim-hash"), bootloader.RoleRecovery)
  1780  		grub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-grub-hash"), bootloader.RoleRecovery)
  1781  		kernelOldRecovery := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery)
  1782  		// kernel from a tried recovery system
  1783  		kernelNewRecovery := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_999.snap", "kernel.efi", bootloader.RoleRecovery)
  1784  		// run mode parameters
  1785  		runGrub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-run-grub-hash"), bootloader.RoleRunMode)
  1786  		runKernel := bootloader.NewBootFile(filepath.Join(rootdir, "var/lib/snapd/snaps/pc-kernel_500.snap"), "kernel.efi", bootloader.RoleRunMode)
  1787  
  1788  		// verify the load chains, which  are identical for both models
  1789  		switch resealKeysCalls {
  1790  		case 1: // run load chain for 2 models, current and a try model
  1791  			c.Assert(params.ModelParams, HasLen, 2)
  1792  			// each load chain has either the run kernel (shared for
  1793  			// both), or the kernel of the respective recovery
  1794  			// system
  1795  			c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{
  1796  				secboot.NewLoadChain(shim,
  1797  					secboot.NewLoadChain(grub,
  1798  						secboot.NewLoadChain(kernelOldRecovery),
  1799  					)),
  1800  				secboot.NewLoadChain(shim,
  1801  					secboot.NewLoadChain(grub,
  1802  						secboot.NewLoadChain(runGrub,
  1803  							secboot.NewLoadChain(runKernel)),
  1804  					)),
  1805  			})
  1806  			c.Assert(params.ModelParams[1].EFILoadChains, DeepEquals, []*secboot.LoadChain{
  1807  				secboot.NewLoadChain(shim,
  1808  					secboot.NewLoadChain(grub,
  1809  						secboot.NewLoadChain(kernelNewRecovery),
  1810  					)),
  1811  				secboot.NewLoadChain(shim,
  1812  					secboot.NewLoadChain(grub,
  1813  						secboot.NewLoadChain(runGrub,
  1814  							secboot.NewLoadChain(runKernel)),
  1815  					)),
  1816  			})
  1817  		case 2: // recovery load chains, only for the current model
  1818  			c.Assert(params.ModelParams, HasLen, 1)
  1819  			// load chain with a kernel from a recovery system that
  1820  			// matches the current model only
  1821  			c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{
  1822  				secboot.NewLoadChain(shim,
  1823  					secboot.NewLoadChain(grub,
  1824  						secboot.NewLoadChain(kernelOldRecovery),
  1825  					)),
  1826  			})
  1827  		}
  1828  
  1829  		return nil
  1830  	})
  1831  	defer restore()
  1832  
  1833  	// here we don't have unasserted kernels so just set
  1834  	// expectReseal to false as it doesn't matter;
  1835  	// the behavior with unasserted kernel is tested in
  1836  	// boot_test.go specific tests
  1837  	const expectReseal = false
  1838  	err = boot.ResealKeyToModeenv(rootdir, modeenv, expectReseal)
  1839  	c.Assert(err, IsNil)
  1840  	c.Assert(resealKeysCalls, Equals, 2)
  1841  
  1842  	// verify the boot chains data file for run key
  1843  
  1844  	recoveryAssetChain := []boot.BootAsset{{
  1845  		Role:   "recovery",
  1846  		Name:   "bootx64.efi",
  1847  		Hashes: []string{"shim-hash"},
  1848  	}, {
  1849  		Role:   "recovery",
  1850  		Name:   "grubx64.efi",
  1851  		Hashes: []string{"grub-hash"},
  1852  	}}
  1853  	runAssetChain := []boot.BootAsset{{
  1854  		Role:   "recovery",
  1855  		Name:   "bootx64.efi",
  1856  		Hashes: []string{"shim-hash"},
  1857  	}, {
  1858  		Role:   "recovery",
  1859  		Name:   "grubx64.efi",
  1860  		Hashes: []string{"grub-hash"},
  1861  	}, {
  1862  		Role:   "run-mode",
  1863  		Name:   "grubx64.efi",
  1864  		Hashes: []string{"run-grub-hash"},
  1865  	}}
  1866  	runPbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "boot-chains"))
  1867  	c.Assert(err, IsNil)
  1868  	c.Assert(cnt, Equals, 1)
  1869  	c.Check(runPbc, DeepEquals, boot.PredictableBootChains{
  1870  		// the current model
  1871  		boot.BootChain{
  1872  			BrandID:        "my-brand",
  1873  			Model:          "my-model-uc20",
  1874  			Grade:          "dangerous",
  1875  			ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
  1876  			AssetChain:     recoveryAssetChain,
  1877  			Kernel:         "pc-kernel",
  1878  			KernelRevision: "1",
  1879  			KernelCmdlines: []string{
  1880  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
  1881  			},
  1882  		},
  1883  		boot.BootChain{
  1884  			BrandID:        "my-brand",
  1885  			Model:          "my-model-uc20",
  1886  			Grade:          "dangerous",
  1887  			ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
  1888  			AssetChain:     runAssetChain,
  1889  			Kernel:         "pc-kernel",
  1890  			KernelRevision: "500",
  1891  			KernelCmdlines: []string{
  1892  				"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
  1893  			},
  1894  		},
  1895  		// the try model
  1896  		boot.BootChain{
  1897  			BrandID:        "my-brand",
  1898  			Model:          "try-my-model-uc20",
  1899  			Grade:          "secured",
  1900  			ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
  1901  			AssetChain:     recoveryAssetChain,
  1902  			Kernel:         "pc-kernel",
  1903  			KernelRevision: "999",
  1904  			KernelCmdlines: []string{
  1905  				"snapd_recovery_mode=recover snapd_recovery_system=1234 console=ttyS0 console=tty1 panic=-1",
  1906  			},
  1907  		},
  1908  		boot.BootChain{
  1909  			BrandID:        "my-brand",
  1910  			Model:          "try-my-model-uc20",
  1911  			Grade:          "secured",
  1912  			ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
  1913  			AssetChain:     runAssetChain,
  1914  			Kernel:         "pc-kernel",
  1915  			KernelRevision: "500",
  1916  			KernelCmdlines: []string{
  1917  				"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
  1918  			},
  1919  		},
  1920  	})
  1921  	// recovery boot chains
  1922  	recoveryPbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "recovery-boot-chains"))
  1923  	c.Assert(err, IsNil)
  1924  	c.Assert(cnt, Equals, 1)
  1925  	c.Check(recoveryPbc, DeepEquals, boot.PredictableBootChains{
  1926  		// recovery keys are sealed to current model only
  1927  		boot.BootChain{
  1928  			BrandID:        "my-brand",
  1929  			Model:          "my-model-uc20",
  1930  			Grade:          "dangerous",
  1931  			ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
  1932  			AssetChain:     recoveryAssetChain,
  1933  			Kernel:         "pc-kernel",
  1934  			KernelRevision: "1",
  1935  			KernelCmdlines: []string{
  1936  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
  1937  			},
  1938  		},
  1939  	})
  1940  }