github.com/freetocompute/snapd@v0.0.0-20210618182524-2fb355d72fd9/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, model, 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, model, 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, model, 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  		// in the order of boot chains
  1088  		expectedCmdlines [][]string
  1089  		err              string
  1090  	}{
  1091  		{
  1092  			desc:            "transition sequences",
  1093  			recoverySystems: []string{"20200825"},
  1094  			assetsMap: boot.BootAssetsMap{
  1095  				"grubx64.efi": []string{"grub-hash-1", "grub-hash-2"},
  1096  				"bootx64.efi": []string{"shim-hash-1"},
  1097  			},
  1098  			expectedAssets: []boot.BootAsset{
  1099  				{Role: bootloader.RoleRecovery, Name: "bootx64.efi", Hashes: []string{"shim-hash-1"}},
  1100  				{Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1", "grub-hash-2"}},
  1101  			},
  1102  			expectedKernelRevs: []int{1},
  1103  			expectedCmdlines: [][]string{
  1104  				{"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1"},
  1105  			},
  1106  		},
  1107  		{
  1108  			desc:            "two systems",
  1109  			recoverySystems: []string{"20200825", "20200831"},
  1110  			assetsMap: boot.BootAssetsMap{
  1111  				"grubx64.efi": []string{"grub-hash-1", "grub-hash-2"},
  1112  				"bootx64.efi": []string{"shim-hash-1"},
  1113  			},
  1114  			expectedAssets: []boot.BootAsset{
  1115  				{Role: bootloader.RoleRecovery, Name: "bootx64.efi", Hashes: []string{"shim-hash-1"}},
  1116  				{Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1", "grub-hash-2"}},
  1117  			},
  1118  			expectedKernelRevs: []int{1, 3},
  1119  			expectedCmdlines: [][]string{
  1120  				{"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1"},
  1121  				{"snapd_recovery_mode=recover snapd_recovery_system=20200831 console=ttyS0 console=tty1 panic=-1"},
  1122  			},
  1123  		},
  1124  		{
  1125  			desc:            "non transition sequence",
  1126  			recoverySystems: []string{"20200825"},
  1127  			assetsMap: boot.BootAssetsMap{
  1128  				"grubx64.efi": []string{"grub-hash-1"},
  1129  				"bootx64.efi": []string{"shim-hash-1"},
  1130  			},
  1131  			expectedAssets: []boot.BootAsset{
  1132  				{Role: bootloader.RoleRecovery, Name: "bootx64.efi", Hashes: []string{"shim-hash-1"}},
  1133  				{Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1"}},
  1134  			},
  1135  			expectedKernelRevs: []int{1},
  1136  			expectedCmdlines: [][]string{
  1137  				{"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1"},
  1138  			},
  1139  		},
  1140  		{
  1141  			desc:            "two systems with command lines",
  1142  			recoverySystems: []string{"20200825", "20200831"},
  1143  			assetsMap: boot.BootAssetsMap{
  1144  				"grubx64.efi": []string{"grub-hash-1", "grub-hash-2"},
  1145  				"bootx64.efi": []string{"shim-hash-1"},
  1146  			},
  1147  			expectedAssets: []boot.BootAsset{
  1148  				{Role: bootloader.RoleRecovery, Name: "bootx64.efi", Hashes: []string{"shim-hash-1"}},
  1149  				{Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1", "grub-hash-2"}},
  1150  			},
  1151  			gadgetFilesForSystem: map[string][][]string{
  1152  				"20200825": {
  1153  					{"cmdline.extra", "extra for 20200825"},
  1154  				},
  1155  				"20200831": {
  1156  					// TODO: make it a cmdline.full
  1157  					{"cmdline.extra", "some-extra-for-20200831"},
  1158  				},
  1159  			},
  1160  			expectedKernelRevs: []int{1, 3},
  1161  			expectedCmdlines: [][]string{
  1162  				{"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1 extra for 20200825"},
  1163  				{"snapd_recovery_mode=recover snapd_recovery_system=20200831 console=ttyS0 console=tty1 panic=-1 some-extra-for-20200831"},
  1164  			},
  1165  		},
  1166  		{
  1167  			desc:            "invalid recovery system label",
  1168  			recoverySystems: []string{"0"},
  1169  			err:             `cannot read system "0" seed: invalid system seed`,
  1170  		},
  1171  	} {
  1172  		c.Logf("tc: %q", tc.desc)
  1173  		rootdir := c.MkDir()
  1174  		dirs.SetRootDir(rootdir)
  1175  		defer dirs.SetRootDir("")
  1176  
  1177  		// set recovery kernel
  1178  		restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
  1179  			if label != "20200825" && label != "20200831" {
  1180  				return nil, nil, fmt.Errorf("invalid system seed")
  1181  			}
  1182  			kernelRev := 1
  1183  			if label == "20200831" {
  1184  				kernelRev = 3
  1185  			}
  1186  			return nil, []*seed.Snap{mockKernelSeedSnap(c, snap.R(kernelRev)), mockGadgetSeedSnap(c, tc.gadgetFilesForSystem[label])}, nil
  1187  		})
  1188  		defer restore()
  1189  
  1190  		grubDir := filepath.Join(rootdir, "run/mnt/ubuntu-seed")
  1191  		err := createMockGrubCfg(grubDir)
  1192  		c.Assert(err, IsNil)
  1193  
  1194  		bl, err := bootloader.Find(grubDir, &bootloader.Options{Role: bootloader.RoleRecovery})
  1195  		c.Assert(err, IsNil)
  1196  		tbl, ok := bl.(bootloader.TrustedAssetsBootloader)
  1197  		c.Assert(ok, Equals, true)
  1198  
  1199  		model := boottest.MakeMockUC20Model()
  1200  
  1201  		modeenv := &boot.Modeenv{
  1202  			CurrentTrustedRecoveryBootAssets: tc.assetsMap,
  1203  
  1204  			BrandID:        model.BrandID(),
  1205  			Model:          model.Model(),
  1206  			ModelSignKeyID: model.SignKeyID(),
  1207  			Grade:          string(model.Grade()),
  1208  		}
  1209  
  1210  		bc, err := boot.RecoveryBootChainsForSystems(tc.recoverySystems, tbl, modeenv)
  1211  		if tc.err == "" {
  1212  			c.Assert(err, IsNil)
  1213  			c.Assert(bc, HasLen, len(tc.recoverySystems))
  1214  			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"))
  1215  			for i, chain := range bc {
  1216  				c.Assert(chain.AssetChain, DeepEquals, tc.expectedAssets)
  1217  				c.Assert(chain.Kernel, Equals, "pc-kernel")
  1218  				expectedKernelRev := tc.expectedKernelRevs[i]
  1219  				c.Assert(chain.KernelRevision, Equals, fmt.Sprintf("%d", expectedKernelRev))
  1220  				c.Assert(chain.KernelBootFile(), DeepEquals, bootloader.BootFile{
  1221  					Snap: fmt.Sprintf("/var/lib/snapd/seed/snaps/pc-kernel_%d.snap", expectedKernelRev),
  1222  					Path: "kernel.efi",
  1223  					Role: bootloader.RoleRecovery,
  1224  				})
  1225  				c.Assert(chain.KernelCmdlines, DeepEquals, tc.expectedCmdlines[i])
  1226  			}
  1227  		} else {
  1228  			c.Assert(err, ErrorMatches, tc.err)
  1229  		}
  1230  
  1231  	}
  1232  
  1233  }
  1234  
  1235  func createMockGrubCfg(baseDir string) error {
  1236  	cfg := filepath.Join(baseDir, "EFI/ubuntu/grub.cfg")
  1237  	if err := os.MkdirAll(filepath.Dir(cfg), 0755); err != nil {
  1238  		return err
  1239  	}
  1240  	return ioutil.WriteFile(cfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644)
  1241  }
  1242  
  1243  func (s *sealSuite) TestSealKeyModelParams(c *C) {
  1244  	rootdir := c.MkDir()
  1245  	dirs.SetRootDir(rootdir)
  1246  	defer dirs.SetRootDir("")
  1247  
  1248  	model := boottest.MakeMockUC20Model()
  1249  
  1250  	roleToBlName := map[bootloader.Role]string{
  1251  		bootloader.RoleRecovery: "grub",
  1252  		bootloader.RoleRunMode:  "grub",
  1253  	}
  1254  	// mock asset cache
  1255  	mockAssetsCache(c, rootdir, "grub", []string{
  1256  		"shim-shim-hash",
  1257  		"loader-loader-hash1",
  1258  		"loader-loader-hash2",
  1259  	})
  1260  
  1261  	oldmodel := boottest.MakeMockUC20Model(map[string]interface{}{
  1262  		"model":     "old-model-uc20",
  1263  		"timestamp": "2019-10-01T08:00:00+00:00",
  1264  	})
  1265  
  1266  	// old recovery
  1267  	oldrc := boot.BootChain{
  1268  		BrandID:        oldmodel.BrandID(),
  1269  		Model:          oldmodel.Model(),
  1270  		Grade:          oldmodel.Grade(),
  1271  		ModelSignKeyID: oldmodel.SignKeyID(),
  1272  		AssetChain: []boot.BootAsset{
  1273  			{Name: "shim", Role: bootloader.RoleRecovery, Hashes: []string{"shim-hash"}},
  1274  			{Name: "loader", Role: bootloader.RoleRecovery, Hashes: []string{"loader-hash1"}},
  1275  		},
  1276  		KernelCmdlines: []string{"panic=1", "oldrc"},
  1277  	}
  1278  	oldkbf := bootloader.BootFile{Snap: "pc-kernel_1.snap"}
  1279  	oldrc.SetKernelBootFile(oldkbf)
  1280  
  1281  	// recovery
  1282  	rc1 := boot.BootChain{
  1283  		BrandID:        model.BrandID(),
  1284  		Model:          model.Model(),
  1285  		Grade:          model.Grade(),
  1286  		ModelSignKeyID: model.SignKeyID(),
  1287  		AssetChain: []boot.BootAsset{
  1288  			{Name: "shim", Role: bootloader.RoleRecovery, Hashes: []string{"shim-hash"}},
  1289  			{Name: "loader", Role: bootloader.RoleRecovery, Hashes: []string{"loader-hash1"}},
  1290  		},
  1291  		KernelCmdlines: []string{"panic=1", "rc1"},
  1292  	}
  1293  	rc1kbf := bootloader.BootFile{Snap: "pc-kernel_10.snap"}
  1294  	rc1.SetKernelBootFile(rc1kbf)
  1295  
  1296  	// run system
  1297  	runc1 := boot.BootChain{
  1298  		BrandID:        model.BrandID(),
  1299  		Model:          model.Model(),
  1300  		Grade:          model.Grade(),
  1301  		ModelSignKeyID: model.SignKeyID(),
  1302  		AssetChain: []boot.BootAsset{
  1303  			{Name: "shim", Role: bootloader.RoleRecovery, Hashes: []string{"shim-hash"}},
  1304  			{Name: "loader", Role: bootloader.RoleRecovery, Hashes: []string{"loader-hash1"}},
  1305  			{Name: "loader", Role: bootloader.RoleRunMode, Hashes: []string{"loader-hash2"}},
  1306  		},
  1307  		KernelCmdlines: []string{"panic=1", "runc1"},
  1308  	}
  1309  	runc1kbf := bootloader.BootFile{Snap: "pc-kernel_50.snap"}
  1310  	runc1.SetKernelBootFile(runc1kbf)
  1311  
  1312  	pbc := boot.ToPredictableBootChains([]boot.BootChain{rc1, runc1, oldrc})
  1313  
  1314  	shim := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/shim-shim-hash"), bootloader.RoleRecovery)
  1315  	loader1 := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/loader-loader-hash1"), bootloader.RoleRecovery)
  1316  	loader2 := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/loader-loader-hash2"), bootloader.RoleRunMode)
  1317  
  1318  	params, err := boot.SealKeyModelParams(pbc, roleToBlName)
  1319  	c.Assert(err, IsNil)
  1320  	c.Check(params, HasLen, 2)
  1321  	c.Check(params[0].Model.Model(), Equals, model.Model())
  1322  	// NB: merging of lists makes panic=1 appear once
  1323  	c.Check(params[0].KernelCmdlines, DeepEquals, []string{"panic=1", "rc1", "runc1"})
  1324  
  1325  	c.Check(params[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{
  1326  		secboot.NewLoadChain(shim,
  1327  			secboot.NewLoadChain(loader1,
  1328  				secboot.NewLoadChain(rc1kbf))),
  1329  		secboot.NewLoadChain(shim,
  1330  			secboot.NewLoadChain(loader1,
  1331  				secboot.NewLoadChain(loader2,
  1332  					secboot.NewLoadChain(runc1kbf)))),
  1333  	})
  1334  
  1335  	c.Check(params[1].Model.Model(), Equals, oldmodel.Model())
  1336  	c.Check(params[1].KernelCmdlines, DeepEquals, []string{"oldrc", "panic=1"})
  1337  	c.Check(params[1].EFILoadChains, DeepEquals, []*secboot.LoadChain{
  1338  		secboot.NewLoadChain(shim,
  1339  			secboot.NewLoadChain(loader1,
  1340  				secboot.NewLoadChain(oldkbf))),
  1341  	})
  1342  }
  1343  
  1344  func (s *sealSuite) TestIsResealNeeded(c *C) {
  1345  	if os.Geteuid() == 0 {
  1346  		c.Skip("the test cannot be run by the root user")
  1347  	}
  1348  
  1349  	chains := []boot.BootChain{
  1350  		{
  1351  			BrandID:        "mybrand",
  1352  			Model:          "foo",
  1353  			Grade:          "signed",
  1354  			ModelSignKeyID: "my-key-id",
  1355  			AssetChain: []boot.BootAsset{
  1356  				// hashes will be sorted
  1357  				{Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"x", "y"}},
  1358  				{Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"c", "d"}},
  1359  				{Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"z", "x"}},
  1360  			},
  1361  			Kernel:         "pc-kernel-other",
  1362  			KernelRevision: "2345",
  1363  			KernelCmdlines: []string{`snapd_recovery_mode=run foo`},
  1364  		}, {
  1365  			BrandID:        "mybrand",
  1366  			Model:          "foo",
  1367  			Grade:          "dangerous",
  1368  			ModelSignKeyID: "my-key-id",
  1369  			AssetChain: []boot.BootAsset{
  1370  				// hashes will be sorted
  1371  				{Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"y", "x"}},
  1372  				{Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"c", "d"}},
  1373  			},
  1374  			Kernel:         "pc-kernel-recovery",
  1375  			KernelRevision: "1234",
  1376  			KernelCmdlines: []string{`snapd_recovery_mode=recover foo`},
  1377  		},
  1378  	}
  1379  
  1380  	pbc := boot.ToPredictableBootChains(chains)
  1381  
  1382  	rootdir := c.MkDir()
  1383  	err := boot.WriteBootChains(pbc, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), 2)
  1384  	c.Assert(err, IsNil)
  1385  
  1386  	needed, _, err := boot.IsResealNeeded(pbc, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), false)
  1387  	c.Assert(err, IsNil)
  1388  	c.Check(needed, Equals, false)
  1389  
  1390  	otherchain := []boot.BootChain{pbc[0]}
  1391  	needed, cnt, err := boot.IsResealNeeded(otherchain, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), false)
  1392  	c.Assert(err, IsNil)
  1393  	// chains are different
  1394  	c.Check(needed, Equals, true)
  1395  	c.Check(cnt, Equals, 3)
  1396  
  1397  	// boot-chains does not exist, we cannot compare so advise to reseal
  1398  	otherRootdir := c.MkDir()
  1399  	needed, cnt, err = boot.IsResealNeeded(otherchain, filepath.Join(dirs.SnapFDEDirUnder(otherRootdir), "boot-chains"), false)
  1400  	c.Assert(err, IsNil)
  1401  	c.Check(needed, Equals, true)
  1402  	c.Check(cnt, Equals, 1)
  1403  
  1404  	// exists but cannot be read
  1405  	c.Assert(os.Chmod(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), 0000), IsNil)
  1406  	defer os.Chmod(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), 0755)
  1407  	needed, _, err = boot.IsResealNeeded(otherchain, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), false)
  1408  	c.Assert(err, ErrorMatches, "cannot open existing boot chains data file: open .*/boot-chains: permission denied")
  1409  	c.Check(needed, Equals, false)
  1410  
  1411  	// unrevisioned kernel chain
  1412  	unrevchain := []boot.BootChain{pbc[0], pbc[1]}
  1413  	unrevchain[1].KernelRevision = ""
  1414  	// write on disk
  1415  	bootChainsFile := filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains")
  1416  	err = boot.WriteBootChains(unrevchain, bootChainsFile, 2)
  1417  	c.Assert(err, IsNil)
  1418  
  1419  	needed, cnt, err = boot.IsResealNeeded(pbc, bootChainsFile, false)
  1420  	c.Assert(err, IsNil)
  1421  	c.Check(needed, Equals, true)
  1422  	c.Check(cnt, Equals, 3)
  1423  
  1424  	// cases falling back to expectReseal
  1425  	needed, _, err = boot.IsResealNeeded(unrevchain, bootChainsFile, false)
  1426  	c.Assert(err, IsNil)
  1427  	c.Check(needed, Equals, false)
  1428  
  1429  	needed, cnt, err = boot.IsResealNeeded(unrevchain, bootChainsFile, true)
  1430  	c.Assert(err, IsNil)
  1431  	c.Check(needed, Equals, true)
  1432  	c.Check(cnt, Equals, 3)
  1433  }
  1434  
  1435  func (s *sealSuite) TestSealToModeenvWithFdeHookHappy(c *C) {
  1436  	rootdir := c.MkDir()
  1437  	dirs.SetRootDir(rootdir)
  1438  	defer dirs.SetRootDir("")
  1439  
  1440  	restore := boot.MockHasFDESetupHook(func() (bool, error) {
  1441  		return true, nil
  1442  	})
  1443  	defer restore()
  1444  
  1445  	model := boottest.MakeMockUC20Model()
  1446  
  1447  	n := 0
  1448  	var runFDESetupHookReqs []*fde.SetupRequest
  1449  	restore = boot.MockRunFDESetupHook(func(req *fde.SetupRequest) ([]byte, error) {
  1450  		n++
  1451  		runFDESetupHookReqs = append(runFDESetupHookReqs, req)
  1452  
  1453  		key := []byte(fmt.Sprintf("key-%v", strconv.Itoa(n)))
  1454  		return key, nil
  1455  	})
  1456  	defer restore()
  1457  	keyToSave := make(map[string][]byte)
  1458  	restore = boot.MockSecbootSealKeysWithFDESetupHook(func(runHook fde.RunSetupHookFunc, skrs []secboot.SealKeyRequest, params *secboot.SealKeysWithFDESetupHookParams) error {
  1459  		c.Check(params.Model.Model(), Equals, model.Model())
  1460  		c.Check(params.AuxKeyFile, Equals, filepath.Join(boot.InstallHostFDESaveDir, "aux-key"))
  1461  		for _, skr := range skrs {
  1462  			out, err := runHook(&fde.SetupRequest{
  1463  				Key:     skr.Key,
  1464  				KeyName: skr.KeyName,
  1465  			})
  1466  			c.Assert(err, IsNil)
  1467  			keyToSave[skr.KeyFile] = out
  1468  		}
  1469  		return nil
  1470  	})
  1471  	defer restore()
  1472  
  1473  	modeenv := &boot.Modeenv{
  1474  		RecoverySystem: "20200825",
  1475  		Model:          model.Model(),
  1476  		BrandID:        model.BrandID(),
  1477  		Grade:          string(model.Grade()),
  1478  		ModelSignKeyID: model.SignKeyID(),
  1479  	}
  1480  	key := secboot.EncryptionKey{1, 2, 3, 4}
  1481  	saveKey := secboot.EncryptionKey{5, 6, 7, 8}
  1482  
  1483  	err := boot.SealKeyToModeenv(key, saveKey, model, modeenv)
  1484  	c.Assert(err, IsNil)
  1485  	// check that runFDESetupHook was called the expected way
  1486  	c.Check(runFDESetupHookReqs, DeepEquals, []*fde.SetupRequest{
  1487  		{Key: key, KeyName: "ubuntu-data"},
  1488  		{Key: key, KeyName: "ubuntu-data"},
  1489  		{Key: saveKey, KeyName: "ubuntu-save"},
  1490  	})
  1491  	// check that the sealed keys got written to the expected places
  1492  	for i, p := range []string{
  1493  		filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
  1494  		filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
  1495  		filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
  1496  	} {
  1497  		// Check for a valid platform handle, encrypted payload (base64)
  1498  		mockedSealedKey := []byte(fmt.Sprintf("key-%v", strconv.Itoa(i+1)))
  1499  		c.Check(keyToSave[p], DeepEquals, mockedSealedKey)
  1500  	}
  1501  
  1502  	marker := filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "sealed-keys")
  1503  	c.Check(marker, testutil.FileEquals, "fde-setup-hook")
  1504  }
  1505  
  1506  func (s *sealSuite) TestSealToModeenvWithFdeHookSad(c *C) {
  1507  	rootdir := c.MkDir()
  1508  	dirs.SetRootDir(rootdir)
  1509  	defer dirs.SetRootDir("")
  1510  
  1511  	restore := boot.MockHasFDESetupHook(func() (bool, error) {
  1512  		return true, nil
  1513  	})
  1514  	defer restore()
  1515  
  1516  	restore = boot.MockSecbootSealKeysWithFDESetupHook(func(fde.RunSetupHookFunc, []secboot.SealKeyRequest, *secboot.SealKeysWithFDESetupHookParams) error {
  1517  		return fmt.Errorf("hook failed")
  1518  	})
  1519  	defer restore()
  1520  
  1521  	modeenv := &boot.Modeenv{
  1522  		RecoverySystem: "20200825",
  1523  	}
  1524  	key := secboot.EncryptionKey{1, 2, 3, 4}
  1525  	saveKey := secboot.EncryptionKey{5, 6, 7, 8}
  1526  
  1527  	model := boottest.MakeMockUC20Model()
  1528  	err := boot.SealKeyToModeenv(key, saveKey, model, modeenv)
  1529  	c.Assert(err, ErrorMatches, "hook failed")
  1530  	marker := filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "sealed-keys")
  1531  	c.Check(marker, testutil.FileAbsent)
  1532  }
  1533  
  1534  func (s *sealSuite) TestResealKeyToModeenvWithFdeHookCalled(c *C) {
  1535  	rootdir := c.MkDir()
  1536  	dirs.SetRootDir(rootdir)
  1537  	defer dirs.SetRootDir("")
  1538  
  1539  	resealKeyToModeenvUsingFDESetupHookCalled := 0
  1540  	restore := boot.MockResealKeyToModeenvUsingFDESetupHook(func(string, *boot.Modeenv, bool) error {
  1541  		resealKeyToModeenvUsingFDESetupHookCalled++
  1542  		return nil
  1543  	})
  1544  	defer restore()
  1545  
  1546  	// TODO: this simulates that the hook is not available yet
  1547  	//       because of e.g. seeding. Longer term there will be
  1548  	//       more, see TODO in resealKeyToModeenvUsingFDESetupHookImpl
  1549  	restore = boot.MockHasFDESetupHook(func() (bool, error) {
  1550  		return false, fmt.Errorf("hook not available yet because e.g. seeding")
  1551  	})
  1552  	defer restore()
  1553  
  1554  	marker := filepath.Join(dirs.SnapFDEDirUnder(rootdir), "sealed-keys")
  1555  	err := os.MkdirAll(filepath.Dir(marker), 0755)
  1556  	c.Assert(err, IsNil)
  1557  	err = ioutil.WriteFile(marker, []byte("fde-setup-hook"), 0644)
  1558  	c.Assert(err, IsNil)
  1559  
  1560  	modeenv := &boot.Modeenv{
  1561  		RecoverySystem: "20200825",
  1562  	}
  1563  
  1564  	model := boottest.MakeMockUC20Model()
  1565  	expectReseal := false
  1566  	err = boot.ResealKeyToModeenv(rootdir, model, modeenv, expectReseal)
  1567  	c.Assert(err, IsNil)
  1568  	c.Check(resealKeyToModeenvUsingFDESetupHookCalled, Equals, 1)
  1569  }
  1570  
  1571  func (s *sealSuite) TestResealKeyToModeenvWithFdeHookVerySad(c *C) {
  1572  	rootdir := c.MkDir()
  1573  	dirs.SetRootDir(rootdir)
  1574  	defer dirs.SetRootDir("")
  1575  
  1576  	resealKeyToModeenvUsingFDESetupHookCalled := 0
  1577  	restore := boot.MockResealKeyToModeenvUsingFDESetupHook(func(string, *boot.Modeenv, bool) error {
  1578  		resealKeyToModeenvUsingFDESetupHookCalled++
  1579  		return fmt.Errorf("fde setup hook failed")
  1580  	})
  1581  	defer restore()
  1582  
  1583  	marker := filepath.Join(dirs.SnapFDEDirUnder(rootdir), "sealed-keys")
  1584  	err := os.MkdirAll(filepath.Dir(marker), 0755)
  1585  	c.Assert(err, IsNil)
  1586  	err = ioutil.WriteFile(marker, []byte("fde-setup-hook"), 0644)
  1587  	c.Assert(err, IsNil)
  1588  
  1589  	modeenv := &boot.Modeenv{
  1590  		RecoverySystem: "20200825",
  1591  	}
  1592  
  1593  	model := boottest.MakeMockUC20Model()
  1594  	expectReseal := false
  1595  	err = boot.ResealKeyToModeenv(rootdir, model, modeenv, expectReseal)
  1596  	c.Assert(err, ErrorMatches, "fde setup hook failed")
  1597  	c.Check(resealKeyToModeenvUsingFDESetupHookCalled, Equals, 1)
  1598  }