github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/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/osutil"
    39  	"github.com/snapcore/snapd/secboot"
    40  	"github.com/snapcore/snapd/seed"
    41  	"github.com/snapcore/snapd/snap"
    42  	"github.com/snapcore/snapd/testutil"
    43  	"github.com/snapcore/snapd/timings"
    44  )
    45  
    46  type sealSuite struct {
    47  	testutil.BaseTest
    48  }
    49  
    50  var _ = Suite(&sealSuite{})
    51  
    52  func (s *sealSuite) SetUpTest(c *C) {
    53  	s.BaseTest.SetUpTest(c)
    54  
    55  	rootdir := c.MkDir()
    56  	dirs.SetRootDir(rootdir)
    57  	s.AddCleanup(func() { dirs.SetRootDir("/") })
    58  }
    59  
    60  func (s *sealSuite) TestSealKeyToModeenv(c *C) {
    61  	for _, tc := range []struct {
    62  		sealErr error
    63  		err     string
    64  	}{
    65  		{sealErr: nil, err: ""},
    66  		{sealErr: errors.New("seal error"), err: "cannot seal the encryption keys: seal error"},
    67  	} {
    68  		rootdir := c.MkDir()
    69  		dirs.SetRootDir(rootdir)
    70  		defer dirs.SetRootDir("")
    71  
    72  		err := createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-seed"))
    73  		c.Assert(err, IsNil)
    74  
    75  		err = createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-boot"))
    76  		c.Assert(err, IsNil)
    77  
    78  		modeenv := &boot.Modeenv{
    79  			RecoverySystem: "20200825",
    80  			CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
    81  				"grubx64.efi": []string{"grub-hash-1"},
    82  				"bootx64.efi": []string{"shim-hash-1"},
    83  			},
    84  
    85  			CurrentTrustedBootAssets: boot.BootAssetsMap{
    86  				"grubx64.efi": []string{"run-grub-hash-1"},
    87  			},
    88  
    89  			CurrentKernels: []string{"pc-kernel_500.snap"},
    90  
    91  			CurrentKernelCommandLines: boot.BootCommandLines{
    92  				"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
    93  			},
    94  		}
    95  
    96  		// mock asset cache
    97  		mockAssetsCache(c, rootdir, "grub", []string{
    98  			"bootx64.efi-shim-hash-1",
    99  			"grubx64.efi-grub-hash-1",
   100  			"grubx64.efi-run-grub-hash-1",
   101  		})
   102  
   103  		// set encryption key
   104  		myKey := secboot.EncryptionKey{}
   105  		myKey2 := secboot.EncryptionKey{}
   106  		for i := range myKey {
   107  			myKey[i] = byte(i)
   108  			myKey2[i] = byte(128 + i)
   109  		}
   110  
   111  		model := boottest.MakeMockUC20Model()
   112  
   113  		// set a mock recovery kernel
   114  		readSystemEssentialCalls := 0
   115  		restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   116  			readSystemEssentialCalls++
   117  			kernelSnap := &seed.Snap{
   118  				Path: "/var/lib/snapd/seed/snaps/pc-kernel_1.snap",
   119  				SideInfo: &snap.SideInfo{
   120  					RealName: "pc-kernel",
   121  					Revision: snap.Revision{N: 1},
   122  				},
   123  			}
   124  			return model, []*seed.Snap{kernelSnap}, nil
   125  		})
   126  		defer restore()
   127  
   128  		// set mock key sealing
   129  		sealKeysCalls := 0
   130  		restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error {
   131  			sealKeysCalls++
   132  			switch sealKeysCalls {
   133  			case 1:
   134  				// the run object seals only the ubuntu-data key
   135  				c.Check(params.TPMPolicyAuthKeyFile, Equals, filepath.Join(boot.InstallHostFDESaveDir, "tpm-policy-auth-key"))
   136  				c.Check(params.TPMLockoutAuthFile, Equals, filepath.Join(boot.InstallHostFDESaveDir, "tpm-lockout-auth"))
   137  
   138  				dataKeyFile := filepath.Join(rootdir, "/run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key")
   139  				c.Check(keys, DeepEquals, []secboot.SealKeyRequest{{Key: myKey, KeyName: "ubuntu-data", KeyFile: dataKeyFile}})
   140  			case 2:
   141  				// the fallback object seals the ubuntu-data and the ubuntu-save keys
   142  				c.Check(params.TPMPolicyAuthKeyFile, Equals, "")
   143  				c.Check(params.TPMLockoutAuthFile, Equals, "")
   144  
   145  				dataKeyFile := filepath.Join(rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key")
   146  				saveKeyFile := filepath.Join(rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key")
   147  				c.Check(keys, DeepEquals, []secboot.SealKeyRequest{{Key: myKey, KeyName: "ubuntu-data", KeyFile: dataKeyFile}, {Key: myKey2, KeyName: "ubuntu-save", KeyFile: saveKeyFile}})
   148  			default:
   149  				c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls)
   150  			}
   151  			c.Assert(params.ModelParams, HasLen, 1)
   152  			for _, d := range []string{boot.InitramfsSeedEncryptionKeyDir, boot.InstallHostFDEDataDir} {
   153  				ex, isdir, _ := osutil.DirExists(d)
   154  				c.Check(ex && isdir, Equals, true, Commentf("location %q does not exist or is not a directory", d))
   155  			}
   156  
   157  			shim := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/bootx64.efi-shim-hash-1"), bootloader.RoleRecovery)
   158  			grub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-grub-hash-1"), bootloader.RoleRecovery)
   159  			runGrub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-run-grub-hash-1"), bootloader.RoleRunMode)
   160  			kernel := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery)
   161  			runKernel := bootloader.NewBootFile(filepath.Join(rootdir, "var/lib/snapd/snaps/pc-kernel_500.snap"), "kernel.efi", bootloader.RoleRunMode)
   162  
   163  			switch sealKeysCalls {
   164  			case 1:
   165  				c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{
   166  					secboot.NewLoadChain(shim,
   167  						secboot.NewLoadChain(grub,
   168  							secboot.NewLoadChain(kernel))),
   169  					secboot.NewLoadChain(shim,
   170  						secboot.NewLoadChain(grub,
   171  							secboot.NewLoadChain(runGrub,
   172  								secboot.NewLoadChain(runKernel)))),
   173  				})
   174  				c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   175  					"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
   176  					"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   177  				})
   178  			case 2:
   179  				c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{
   180  					secboot.NewLoadChain(shim,
   181  						secboot.NewLoadChain(grub,
   182  							secboot.NewLoadChain(kernel))),
   183  				})
   184  				c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   185  					"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
   186  				})
   187  			default:
   188  				c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls)
   189  			}
   190  			c.Assert(params.ModelParams[0].Model.DisplayName(), Equals, "My Model")
   191  
   192  			return tc.sealErr
   193  		})
   194  		defer restore()
   195  
   196  		err = boot.SealKeyToModeenv(myKey, myKey2, model, modeenv)
   197  		if tc.sealErr != nil {
   198  			c.Assert(sealKeysCalls, Equals, 1)
   199  		} else {
   200  			c.Assert(sealKeysCalls, Equals, 2)
   201  		}
   202  		if tc.err == "" {
   203  			c.Assert(err, IsNil)
   204  		} else {
   205  			c.Assert(err, ErrorMatches, tc.err)
   206  			continue
   207  		}
   208  
   209  		// verify the boot chains data file
   210  		pbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "boot-chains"))
   211  		c.Assert(err, IsNil)
   212  		c.Check(cnt, Equals, 0)
   213  		c.Check(pbc, DeepEquals, boot.PredictableBootChains{
   214  			boot.BootChain{
   215  				BrandID:        "my-brand",
   216  				Model:          "my-model-uc20",
   217  				Grade:          "dangerous",
   218  				ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
   219  				AssetChain: []boot.BootAsset{
   220  					{
   221  						Role:   "recovery",
   222  						Name:   "bootx64.efi",
   223  						Hashes: []string{"shim-hash-1"},
   224  					},
   225  					{
   226  						Role:   "recovery",
   227  						Name:   "grubx64.efi",
   228  						Hashes: []string{"grub-hash-1"},
   229  					},
   230  				},
   231  				Kernel:         "pc-kernel",
   232  				KernelRevision: "1",
   233  				KernelCmdlines: []string{
   234  					"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
   235  				},
   236  			},
   237  			boot.BootChain{
   238  				BrandID:        "my-brand",
   239  				Model:          "my-model-uc20",
   240  				Grade:          "dangerous",
   241  				ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
   242  				AssetChain: []boot.BootAsset{
   243  					{
   244  						Role:   "recovery",
   245  						Name:   "bootx64.efi",
   246  						Hashes: []string{"shim-hash-1"},
   247  					},
   248  					{
   249  						Role:   "recovery",
   250  						Name:   "grubx64.efi",
   251  						Hashes: []string{"grub-hash-1"},
   252  					},
   253  					{
   254  						Role:   "run-mode",
   255  						Name:   "grubx64.efi",
   256  						Hashes: []string{"run-grub-hash-1"},
   257  					},
   258  				},
   259  				Kernel:         "pc-kernel",
   260  				KernelRevision: "500",
   261  				KernelCmdlines: []string{
   262  					"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   263  				},
   264  			},
   265  		})
   266  
   267  		// verify the recovery boot chains
   268  		pbc, cnt, err = boot.ReadBootChains(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "recovery-boot-chains"))
   269  		c.Assert(err, IsNil)
   270  		c.Check(cnt, Equals, 0)
   271  		c.Check(pbc, DeepEquals, boot.PredictableBootChains{
   272  			boot.BootChain{
   273  				BrandID:        "my-brand",
   274  				Model:          "my-model-uc20",
   275  				Grade:          "dangerous",
   276  				ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
   277  				AssetChain: []boot.BootAsset{
   278  					{
   279  						Role:   "recovery",
   280  						Name:   "bootx64.efi",
   281  						Hashes: []string{"shim-hash-1"},
   282  					},
   283  					{
   284  						Role:   "recovery",
   285  						Name:   "grubx64.efi",
   286  						Hashes: []string{"grub-hash-1"},
   287  					},
   288  				},
   289  				Kernel:         "pc-kernel",
   290  				KernelRevision: "1",
   291  				KernelCmdlines: []string{
   292  					"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
   293  				},
   294  			},
   295  		})
   296  
   297  		// marker
   298  		marker := filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "sealed-keys")
   299  		c.Check(marker, testutil.FileEquals, "tpm")
   300  	}
   301  }
   302  
   303  // TODO:UC20: also test fallback reseal
   304  func (s *sealSuite) TestResealKeyToModeenvWithSystemFallback(c *C) {
   305  	var prevPbc boot.PredictableBootChains
   306  
   307  	for _, tc := range []struct {
   308  		sealedKeys bool
   309  		prevPbc    bool
   310  		resealErr  error
   311  		err        string
   312  	}{
   313  		{sealedKeys: false, resealErr: nil, err: ""},
   314  		{sealedKeys: true, resealErr: nil, err: ""},
   315  		{sealedKeys: true, resealErr: errors.New("reseal error"), err: "cannot reseal the encryption key: reseal error"},
   316  		{prevPbc: true, sealedKeys: true, resealErr: nil, err: ""},
   317  	} {
   318  		rootdir := c.MkDir()
   319  		dirs.SetRootDir(rootdir)
   320  		defer dirs.SetRootDir("")
   321  
   322  		if tc.sealedKeys {
   323  			c.Assert(os.MkdirAll(dirs.SnapFDEDir, 0755), IsNil)
   324  			err := ioutil.WriteFile(filepath.Join(dirs.SnapFDEDir, "sealed-keys"), nil, 0644)
   325  			c.Assert(err, IsNil)
   326  
   327  		}
   328  
   329  		err := createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-seed"))
   330  		c.Assert(err, IsNil)
   331  
   332  		err = createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-boot"))
   333  		c.Assert(err, IsNil)
   334  
   335  		modeenv := &boot.Modeenv{
   336  			CurrentRecoverySystems: []string{"20200825"},
   337  			CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   338  				"grubx64.efi": []string{"grub-hash-1"},
   339  				"bootx64.efi": []string{"shim-hash-1", "shim-hash-2"},
   340  			},
   341  
   342  			CurrentTrustedBootAssets: boot.BootAssetsMap{
   343  				"grubx64.efi": []string{"run-grub-hash-1", "run-grub-hash-2"},
   344  			},
   345  
   346  			CurrentKernels: []string{"pc-kernel_500.snap", "pc-kernel_600.snap"},
   347  
   348  			CurrentKernelCommandLines: boot.BootCommandLines{
   349  				"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   350  			},
   351  		}
   352  
   353  		if tc.prevPbc {
   354  			err := boot.WriteBootChains(prevPbc, filepath.Join(dirs.SnapFDEDir, "boot-chains"), 9)
   355  			c.Assert(err, IsNil)
   356  		}
   357  
   358  		// mock asset cache
   359  		mockAssetsCache(c, rootdir, "grub", []string{
   360  			"bootx64.efi-shim-hash-1",
   361  			"bootx64.efi-shim-hash-2",
   362  			"grubx64.efi-grub-hash-1",
   363  			"grubx64.efi-run-grub-hash-1",
   364  			"grubx64.efi-run-grub-hash-2",
   365  		})
   366  
   367  		model := boottest.MakeMockUC20Model()
   368  
   369  		// set a mock recovery kernel
   370  		readSystemEssentialCalls := 0
   371  		restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   372  			readSystemEssentialCalls++
   373  			kernelSnap := &seed.Snap{
   374  				Path: "/var/lib/snapd/seed/snaps/pc-kernel_1.snap",
   375  				SideInfo: &snap.SideInfo{
   376  					RealName: "pc-kernel",
   377  					Revision: snap.Revision{N: 1},
   378  				},
   379  			}
   380  			return model, []*seed.Snap{kernelSnap}, nil
   381  		})
   382  		defer restore()
   383  
   384  		// set mock key resealing
   385  		resealKeysCalls := 0
   386  		restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   387  			c.Check(params.TPMPolicyAuthKeyFile, Equals, filepath.Join(dirs.SnapSaveDir, "device/fde", "tpm-policy-auth-key"))
   388  
   389  			resealKeysCalls++
   390  			c.Assert(params.ModelParams, HasLen, 1)
   391  
   392  			// shared parameters
   393  			c.Assert(params.ModelParams[0].Model.DisplayName(), Equals, "My Model")
   394  			switch resealKeysCalls {
   395  			case 1:
   396  				c.Assert(params.KeyFiles, DeepEquals, []string{
   397  					filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
   398  				})
   399  				c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   400  					"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
   401  					"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   402  				})
   403  				// load chains
   404  				c.Assert(params.ModelParams[0].EFILoadChains, HasLen, 6)
   405  			case 2:
   406  				c.Assert(params.KeyFiles, DeepEquals, []string{
   407  					filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
   408  					filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
   409  				})
   410  				c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   411  					"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
   412  				})
   413  				// load chains
   414  				c.Assert(params.ModelParams[0].EFILoadChains, HasLen, 2)
   415  			default:
   416  				c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls)
   417  			}
   418  
   419  			// recovery parameters
   420  			shim := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/bootx64.efi-shim-hash-1"), bootloader.RoleRecovery)
   421  			shim2 := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/bootx64.efi-shim-hash-2"), bootloader.RoleRecovery)
   422  			grub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-grub-hash-1"), bootloader.RoleRecovery)
   423  			kernel := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery)
   424  
   425  			c.Assert(params.ModelParams[0].EFILoadChains[:2], DeepEquals, []*secboot.LoadChain{
   426  				secboot.NewLoadChain(shim,
   427  					secboot.NewLoadChain(grub,
   428  						secboot.NewLoadChain(kernel))),
   429  				secboot.NewLoadChain(shim2,
   430  					secboot.NewLoadChain(grub,
   431  						secboot.NewLoadChain(kernel))),
   432  			})
   433  
   434  			// run mode parameters
   435  			runGrub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-run-grub-hash-1"), bootloader.RoleRunMode)
   436  			runGrub2 := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-run-grub-hash-2"), bootloader.RoleRunMode)
   437  			runKernel := bootloader.NewBootFile(filepath.Join(rootdir, "var/lib/snapd/snaps/pc-kernel_500.snap"), "kernel.efi", bootloader.RoleRunMode)
   438  			runKernel2 := bootloader.NewBootFile(filepath.Join(rootdir, "var/lib/snapd/snaps/pc-kernel_600.snap"), "kernel.efi", bootloader.RoleRunMode)
   439  
   440  			switch resealKeysCalls {
   441  			case 1:
   442  				c.Assert(params.ModelParams[0].EFILoadChains[2:4], DeepEquals, []*secboot.LoadChain{
   443  					secboot.NewLoadChain(shim,
   444  						secboot.NewLoadChain(grub,
   445  							secboot.NewLoadChain(runGrub,
   446  								secboot.NewLoadChain(runKernel)),
   447  							secboot.NewLoadChain(runGrub2,
   448  								secboot.NewLoadChain(runKernel)),
   449  						)),
   450  					secboot.NewLoadChain(shim2,
   451  						secboot.NewLoadChain(grub,
   452  							secboot.NewLoadChain(runGrub,
   453  								secboot.NewLoadChain(runKernel)),
   454  							secboot.NewLoadChain(runGrub2,
   455  								secboot.NewLoadChain(runKernel)),
   456  						)),
   457  				})
   458  
   459  				c.Assert(params.ModelParams[0].EFILoadChains[4:], DeepEquals, []*secboot.LoadChain{
   460  					secboot.NewLoadChain(shim,
   461  						secboot.NewLoadChain(grub,
   462  							secboot.NewLoadChain(runGrub,
   463  								secboot.NewLoadChain(runKernel2)),
   464  							secboot.NewLoadChain(runGrub2,
   465  								secboot.NewLoadChain(runKernel2)),
   466  						)),
   467  					secboot.NewLoadChain(shim2,
   468  						secboot.NewLoadChain(grub,
   469  							secboot.NewLoadChain(runGrub,
   470  								secboot.NewLoadChain(runKernel2)),
   471  							secboot.NewLoadChain(runGrub2,
   472  								secboot.NewLoadChain(runKernel2)),
   473  						)),
   474  				})
   475  			}
   476  
   477  			return tc.resealErr
   478  		})
   479  		defer restore()
   480  
   481  		// here we don't have unasserted kernels so just set
   482  		// expectReseal to false as it doesn't matter;
   483  		// the behavior with unasserted kernel is tested in
   484  		// boot_test.go specific tests
   485  		const expectReseal = false
   486  		err = boot.ResealKeyToModeenv(rootdir, model, modeenv, expectReseal)
   487  		if !tc.sealedKeys || tc.prevPbc {
   488  			// did nothing
   489  			c.Assert(err, IsNil)
   490  			c.Assert(resealKeysCalls, Equals, 0)
   491  			continue
   492  		}
   493  		if tc.resealErr != nil {
   494  			c.Assert(resealKeysCalls, Equals, 1)
   495  		} else {
   496  			c.Assert(resealKeysCalls, Equals, 2)
   497  		}
   498  		if tc.err == "" {
   499  			c.Assert(err, IsNil)
   500  		} else {
   501  			c.Assert(err, ErrorMatches, tc.err)
   502  			continue
   503  		}
   504  
   505  		// verify the boot chains data file
   506  		pbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "boot-chains"))
   507  		c.Assert(err, IsNil)
   508  		if tc.prevPbc {
   509  			c.Assert(cnt, Equals, 10)
   510  		} else {
   511  			c.Assert(cnt, Equals, 1)
   512  		}
   513  		c.Check(pbc, DeepEquals, boot.PredictableBootChains{
   514  			boot.BootChain{
   515  				BrandID:        "my-brand",
   516  				Model:          "my-model-uc20",
   517  				Grade:          "dangerous",
   518  				ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
   519  				AssetChain: []boot.BootAsset{
   520  					{
   521  						Role:   "recovery",
   522  						Name:   "bootx64.efi",
   523  						Hashes: []string{"shim-hash-1", "shim-hash-2"},
   524  					},
   525  					{
   526  						Role:   "recovery",
   527  						Name:   "grubx64.efi",
   528  						Hashes: []string{"grub-hash-1"},
   529  					},
   530  				},
   531  				Kernel:         "pc-kernel",
   532  				KernelRevision: "1",
   533  				KernelCmdlines: []string{
   534  					"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
   535  				},
   536  			},
   537  			boot.BootChain{
   538  				BrandID:        "my-brand",
   539  				Model:          "my-model-uc20",
   540  				Grade:          "dangerous",
   541  				ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
   542  				AssetChain: []boot.BootAsset{
   543  					{
   544  						Role:   "recovery",
   545  						Name:   "bootx64.efi",
   546  						Hashes: []string{"shim-hash-1", "shim-hash-2"},
   547  					},
   548  					{
   549  						Role:   "recovery",
   550  						Name:   "grubx64.efi",
   551  						Hashes: []string{"grub-hash-1"},
   552  					},
   553  					{
   554  						Role:   "run-mode",
   555  						Name:   "grubx64.efi",
   556  						Hashes: []string{"run-grub-hash-1", "run-grub-hash-2"},
   557  					},
   558  				},
   559  				Kernel:         "pc-kernel",
   560  				KernelRevision: "500",
   561  				KernelCmdlines: []string{
   562  					"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   563  				},
   564  			},
   565  			boot.BootChain{
   566  				BrandID:        "my-brand",
   567  				Model:          "my-model-uc20",
   568  				Grade:          "dangerous",
   569  				ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
   570  				AssetChain: []boot.BootAsset{
   571  					{
   572  						Role:   "recovery",
   573  						Name:   "bootx64.efi",
   574  						Hashes: []string{"shim-hash-1", "shim-hash-2"},
   575  					},
   576  					{
   577  						Role:   "recovery",
   578  						Name:   "grubx64.efi",
   579  						Hashes: []string{"grub-hash-1"},
   580  					},
   581  					{
   582  						Role:   "run-mode",
   583  						Name:   "grubx64.efi",
   584  						Hashes: []string{"run-grub-hash-1", "run-grub-hash-2"},
   585  					},
   586  				},
   587  				Kernel:         "pc-kernel",
   588  				KernelRevision: "600",
   589  				KernelCmdlines: []string{
   590  					"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   591  				},
   592  			},
   593  		})
   594  		prevPbc = pbc
   595  	}
   596  }
   597  
   598  func (s *sealSuite) TestResealKeyToModeenvRecoveryKeysForGoodSystemsOnly(c *C) {
   599  	rootdir := c.MkDir()
   600  	dirs.SetRootDir(rootdir)
   601  	defer dirs.SetRootDir("")
   602  
   603  	c.Assert(os.MkdirAll(dirs.SnapFDEDir, 0755), IsNil)
   604  	err := ioutil.WriteFile(filepath.Join(dirs.SnapFDEDir, "sealed-keys"), nil, 0644)
   605  	c.Assert(err, IsNil)
   606  
   607  	err = createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-seed"))
   608  	c.Assert(err, IsNil)
   609  
   610  	err = createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-boot"))
   611  	c.Assert(err, IsNil)
   612  
   613  	modeenv := &boot.Modeenv{
   614  		// where 1234 is being tried
   615  		CurrentRecoverySystems: []string{"20200825", "1234"},
   616  		// 20200825 has known to be good
   617  		GoodRecoverySystems: []string{"20200825"},
   618  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   619  			"grubx64.efi": []string{"grub-hash"},
   620  			"bootx64.efi": []string{"shim-hash"},
   621  		},
   622  
   623  		CurrentTrustedBootAssets: boot.BootAssetsMap{
   624  			"grubx64.efi": []string{"run-grub-hash"},
   625  		},
   626  
   627  		CurrentKernels: []string{"pc-kernel_500.snap"},
   628  
   629  		CurrentKernelCommandLines: boot.BootCommandLines{
   630  			"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   631  		},
   632  	}
   633  
   634  	// mock asset cache
   635  	mockAssetsCache(c, rootdir, "grub", []string{
   636  		"bootx64.efi-shim-hash",
   637  		"grubx64.efi-grub-hash",
   638  		"grubx64.efi-run-grub-hash",
   639  	})
   640  
   641  	model := boottest.MakeMockUC20Model()
   642  
   643  	// set a mock recovery kernel
   644  	readSystemEssentialCalls := 0
   645  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   646  		readSystemEssentialCalls++
   647  		kernelSnap := &seed.Snap{
   648  			Path: "/var/lib/snapd/seed/snaps/pc-kernel_1.snap",
   649  			SideInfo: &snap.SideInfo{
   650  				RealName: "pc-kernel",
   651  				Revision: snap.Revision{N: 1},
   652  			},
   653  		}
   654  		if label == "1234" {
   655  			// special kernel snap for this system
   656  			kernelSnap = &seed.Snap{
   657  				Path: "/var/lib/snapd/seed/snaps/pc-kernel_999.snap",
   658  				SideInfo: &snap.SideInfo{
   659  					RealName: "pc-kernel",
   660  					Revision: snap.Revision{N: 999},
   661  				},
   662  			}
   663  		}
   664  		return model, []*seed.Snap{kernelSnap}, nil
   665  	})
   666  	defer restore()
   667  
   668  	// set mock key resealing
   669  	resealKeysCalls := 0
   670  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   671  		c.Check(params.TPMPolicyAuthKeyFile, Equals, filepath.Join(dirs.SnapSaveDir, "device/fde", "tpm-policy-auth-key"))
   672  
   673  		resealKeysCalls++
   674  		c.Assert(params.ModelParams, HasLen, 1)
   675  
   676  		// shared parameters
   677  		c.Assert(params.ModelParams[0].Model.DisplayName(), Equals, "My Model")
   678  		c.Logf("got:")
   679  		for _, ch := range params.ModelParams[0].EFILoadChains {
   680  			printChain(c, ch, "-")
   681  		}
   682  		switch resealKeysCalls {
   683  		case 1: // run key
   684  			c.Assert(params.KeyFiles, DeepEquals, []string{
   685  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
   686  			})
   687  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   688  				"snapd_recovery_mode=recover snapd_recovery_system=1234 console=ttyS0 console=tty1 panic=-1",
   689  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
   690  				"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   691  			})
   692  			// load chains
   693  			c.Assert(params.ModelParams[0].EFILoadChains, HasLen, 3)
   694  		case 2: // recovery keys
   695  			c.Assert(params.KeyFiles, DeepEquals, []string{
   696  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
   697  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
   698  			})
   699  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   700  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
   701  			})
   702  			// load chains
   703  			c.Assert(params.ModelParams[0].EFILoadChains, HasLen, 1)
   704  		default:
   705  			c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls)
   706  		}
   707  
   708  		// recovery parameters
   709  		shim := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/bootx64.efi-shim-hash"), bootloader.RoleRecovery)
   710  		grub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-grub-hash"), bootloader.RoleRecovery)
   711  		kernelGoodRecovery := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery)
   712  		// kernel from a tried recovery system
   713  		kernelTriedRecovery := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_999.snap", "kernel.efi", bootloader.RoleRecovery)
   714  		// run mode parameters
   715  		runGrub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-run-grub-hash"), bootloader.RoleRunMode)
   716  		runKernel := bootloader.NewBootFile(filepath.Join(rootdir, "var/lib/snapd/snaps/pc-kernel_500.snap"), "kernel.efi", bootloader.RoleRunMode)
   717  
   718  		switch resealKeysCalls {
   719  		case 1: // run load chain
   720  			c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{
   721  				secboot.NewLoadChain(shim,
   722  					secboot.NewLoadChain(grub,
   723  						secboot.NewLoadChain(kernelGoodRecovery),
   724  					)),
   725  				secboot.NewLoadChain(shim,
   726  					secboot.NewLoadChain(grub,
   727  						secboot.NewLoadChain(kernelTriedRecovery),
   728  					)),
   729  				secboot.NewLoadChain(shim,
   730  					secboot.NewLoadChain(grub,
   731  						secboot.NewLoadChain(runGrub,
   732  							secboot.NewLoadChain(runKernel)),
   733  					)),
   734  			})
   735  		case 2: // recovery load chains
   736  			c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{
   737  				secboot.NewLoadChain(shim,
   738  					secboot.NewLoadChain(grub,
   739  						secboot.NewLoadChain(kernelGoodRecovery),
   740  					)),
   741  			})
   742  		}
   743  
   744  		return nil
   745  	})
   746  	defer restore()
   747  
   748  	// here we don't have unasserted kernels so just set
   749  	// expectReseal to false as it doesn't matter;
   750  	// the behavior with unasserted kernel is tested in
   751  	// boot_test.go specific tests
   752  	const expectReseal = false
   753  	err = boot.ResealKeyToModeenv(rootdir, model, modeenv, expectReseal)
   754  	c.Assert(resealKeysCalls, Equals, 2)
   755  	c.Assert(err, IsNil)
   756  
   757  	// verify the boot chains data file for run key
   758  	runPbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "boot-chains"))
   759  	c.Assert(err, IsNil)
   760  	c.Assert(cnt, Equals, 1)
   761  	c.Check(runPbc, DeepEquals, boot.PredictableBootChains{
   762  		boot.BootChain{
   763  			BrandID:        "my-brand",
   764  			Model:          "my-model-uc20",
   765  			Grade:          "dangerous",
   766  			ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
   767  			AssetChain: []boot.BootAsset{
   768  				{
   769  					Role:   "recovery",
   770  					Name:   "bootx64.efi",
   771  					Hashes: []string{"shim-hash"},
   772  				},
   773  				{
   774  					Role:   "recovery",
   775  					Name:   "grubx64.efi",
   776  					Hashes: []string{"grub-hash"},
   777  				},
   778  			},
   779  			Kernel:         "pc-kernel",
   780  			KernelRevision: "1",
   781  			KernelCmdlines: []string{
   782  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
   783  			},
   784  		},
   785  		// includes the tried system
   786  		boot.BootChain{
   787  			BrandID:        "my-brand",
   788  			Model:          "my-model-uc20",
   789  			Grade:          "dangerous",
   790  			ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
   791  			AssetChain: []boot.BootAsset{
   792  				{
   793  					Role:   "recovery",
   794  					Name:   "bootx64.efi",
   795  					Hashes: []string{"shim-hash"},
   796  				},
   797  				{
   798  					Role:   "recovery",
   799  					Name:   "grubx64.efi",
   800  					Hashes: []string{"grub-hash"},
   801  				},
   802  			},
   803  			Kernel:         "pc-kernel",
   804  			KernelRevision: "999",
   805  			KernelCmdlines: []string{
   806  				"snapd_recovery_mode=recover snapd_recovery_system=1234 console=ttyS0 console=tty1 panic=-1",
   807  			},
   808  		},
   809  		boot.BootChain{
   810  			BrandID:        "my-brand",
   811  			Model:          "my-model-uc20",
   812  			Grade:          "dangerous",
   813  			ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
   814  			AssetChain: []boot.BootAsset{
   815  				{
   816  					Role:   "recovery",
   817  					Name:   "bootx64.efi",
   818  					Hashes: []string{"shim-hash"},
   819  				},
   820  				{
   821  					Role:   "recovery",
   822  					Name:   "grubx64.efi",
   823  					Hashes: []string{"grub-hash"},
   824  				},
   825  				{
   826  					Role:   "run-mode",
   827  					Name:   "grubx64.efi",
   828  					Hashes: []string{"run-grub-hash"},
   829  				},
   830  			},
   831  			Kernel:         "pc-kernel",
   832  			KernelRevision: "500",
   833  			KernelCmdlines: []string{
   834  				"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   835  			},
   836  		},
   837  	})
   838  	// recovery boot chains
   839  	recoveryPbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "recovery-boot-chains"))
   840  	c.Assert(err, IsNil)
   841  	c.Assert(cnt, Equals, 1)
   842  	c.Check(recoveryPbc, DeepEquals, boot.PredictableBootChains{
   843  		// only one entry for a recovery system that is known to be good
   844  		boot.BootChain{
   845  			BrandID:        "my-brand",
   846  			Model:          "my-model-uc20",
   847  			Grade:          "dangerous",
   848  			ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
   849  			AssetChain: []boot.BootAsset{
   850  				{
   851  					Role:   "recovery",
   852  					Name:   "bootx64.efi",
   853  					Hashes: []string{"shim-hash"},
   854  				},
   855  				{
   856  					Role:   "recovery",
   857  					Name:   "grubx64.efi",
   858  					Hashes: []string{"grub-hash"},
   859  				},
   860  			},
   861  			Kernel:         "pc-kernel",
   862  			KernelRevision: "1",
   863  			KernelCmdlines: []string{
   864  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1",
   865  			},
   866  		},
   867  	})
   868  }
   869  
   870  func (s *sealSuite) TestResealKeyToModeenvFallbackCmdline(c *C) {
   871  	rootdir := c.MkDir()
   872  	dirs.SetRootDir(rootdir)
   873  	defer dirs.SetRootDir("")
   874  
   875  	model := boottest.MakeMockUC20Model()
   876  
   877  	c.Assert(os.MkdirAll(dirs.SnapFDEDir, 0755), IsNil)
   878  	err := ioutil.WriteFile(filepath.Join(dirs.SnapFDEDir, "sealed-keys"), nil, 0644)
   879  	c.Assert(err, IsNil)
   880  
   881  	modeenv := &boot.Modeenv{
   882  		CurrentRecoverySystems: []string{"20200825"},
   883  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   884  			"asset": []string{"asset-hash-1"},
   885  		},
   886  
   887  		CurrentTrustedBootAssets: boot.BootAssetsMap{
   888  			"asset": []string{"asset-hash-1"},
   889  		},
   890  
   891  		CurrentKernels: []string{"pc-kernel_500.snap"},
   892  
   893  		// as if it is unset yet
   894  		CurrentKernelCommandLines: nil,
   895  	}
   896  
   897  	err = boot.WriteBootChains(nil, filepath.Join(dirs.SnapFDEDir, "boot-chains"), 9)
   898  	c.Assert(err, IsNil)
   899  	// mock asset cache
   900  	mockAssetsCache(c, rootdir, "trusted", []string{
   901  		"asset-asset-hash-1",
   902  	})
   903  
   904  	// match one of current kernels
   905  	runKernelBf := bootloader.NewBootFile("/var/lib/snapd/snap/pc-kernel_500.snap", "kernel.efi", bootloader.RoleRunMode)
   906  	// match the seed kernel
   907  	recoveryKernelBf := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery)
   908  
   909  	bootdir := c.MkDir()
   910  	mtbl := bootloadertest.Mock("trusted", bootdir).WithTrustedAssets()
   911  	mtbl.TrustedAssetsList = []string{"asset-1"}
   912  	mtbl.StaticCommandLine = "static cmdline"
   913  	mtbl.BootChainList = []bootloader.BootFile{
   914  		bootloader.NewBootFile("", "asset", bootloader.RoleRunMode),
   915  		runKernelBf,
   916  	}
   917  	mtbl.RecoveryBootChainList = []bootloader.BootFile{
   918  		bootloader.NewBootFile("", "asset", bootloader.RoleRecovery),
   919  		recoveryKernelBf,
   920  	}
   921  	bootloader.Force(mtbl)
   922  	defer bootloader.Force(nil)
   923  
   924  	// set a mock recovery kernel
   925  	readSystemEssentialCalls := 0
   926  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   927  		readSystemEssentialCalls++
   928  		kernelSnap := &seed.Snap{
   929  			Path: "/var/lib/snapd/seed/snaps/pc-kernel_1.snap",
   930  			SideInfo: &snap.SideInfo{
   931  				RealName: "pc-kernel",
   932  				Revision: snap.Revision{N: 1},
   933  			},
   934  		}
   935  		return model, []*seed.Snap{kernelSnap}, nil
   936  	})
   937  	defer restore()
   938  
   939  	// set mock key resealing
   940  	resealKeysCalls := 0
   941  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   942  		resealKeysCalls++
   943  		c.Assert(params.ModelParams, HasLen, 1)
   944  		c.Logf("reseal: %+v", params)
   945  		switch resealKeysCalls {
   946  		case 1:
   947  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   948  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
   949  				"snapd_recovery_mode=run static cmdline",
   950  			})
   951  		case 2:
   952  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   953  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
   954  			})
   955  		default:
   956  			c.Fatalf("unexpected number of reseal calls, %v", params)
   957  		}
   958  		return nil
   959  	})
   960  	defer restore()
   961  
   962  	const expectReseal = false
   963  	err = boot.ResealKeyToModeenv(rootdir, model, modeenv, expectReseal)
   964  	c.Assert(err, IsNil)
   965  	c.Assert(resealKeysCalls, Equals, 2)
   966  
   967  	// verify the boot chains data file
   968  	pbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "boot-chains"))
   969  	c.Assert(err, IsNil)
   970  	c.Assert(cnt, Equals, 10)
   971  	c.Check(pbc, DeepEquals, boot.PredictableBootChains{
   972  		boot.BootChain{
   973  			BrandID:        "my-brand",
   974  			Model:          "my-model-uc20",
   975  			Grade:          "dangerous",
   976  			ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
   977  			AssetChain: []boot.BootAsset{
   978  				{
   979  					Role:   "recovery",
   980  					Name:   "asset",
   981  					Hashes: []string{"asset-hash-1"},
   982  				},
   983  			},
   984  			Kernel:         "pc-kernel",
   985  			KernelRevision: "1",
   986  			KernelCmdlines: []string{
   987  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
   988  			},
   989  		},
   990  		boot.BootChain{
   991  			BrandID:        "my-brand",
   992  			Model:          "my-model-uc20",
   993  			Grade:          "dangerous",
   994  			ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
   995  			AssetChain: []boot.BootAsset{
   996  				{
   997  					Role:   "run-mode",
   998  					Name:   "asset",
   999  					Hashes: []string{"asset-hash-1"},
  1000  				},
  1001  			},
  1002  			Kernel:         "pc-kernel",
  1003  			KernelRevision: "500",
  1004  			KernelCmdlines: []string{
  1005  				"snapd_recovery_mode=run static cmdline",
  1006  			},
  1007  		},
  1008  	})
  1009  }
  1010  
  1011  func (s *sealSuite) TestRecoveryBootChainsForSystems(c *C) {
  1012  	for _, tc := range []struct {
  1013  		assetsMap          boot.BootAssetsMap
  1014  		recoverySystems    []string
  1015  		undefinedKernel    bool
  1016  		expectedAssets     []boot.BootAsset
  1017  		expectedKernelRevs []int
  1018  		err                string
  1019  	}{
  1020  		{
  1021  			// transition sequences
  1022  			recoverySystems: []string{"20200825"},
  1023  			assetsMap: boot.BootAssetsMap{
  1024  				"grubx64.efi": []string{"grub-hash-1", "grub-hash-2"},
  1025  				"bootx64.efi": []string{"shim-hash-1"},
  1026  			},
  1027  			expectedAssets: []boot.BootAsset{
  1028  				{Role: bootloader.RoleRecovery, Name: "bootx64.efi", Hashes: []string{"shim-hash-1"}},
  1029  				{Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1", "grub-hash-2"}},
  1030  			},
  1031  			expectedKernelRevs: []int{1},
  1032  		},
  1033  		{
  1034  			// two systems
  1035  			recoverySystems: []string{"20200825", "20200831"},
  1036  			assetsMap: boot.BootAssetsMap{
  1037  				"grubx64.efi": []string{"grub-hash-1", "grub-hash-2"},
  1038  				"bootx64.efi": []string{"shim-hash-1"},
  1039  			},
  1040  			expectedAssets: []boot.BootAsset{
  1041  				{Role: bootloader.RoleRecovery, Name: "bootx64.efi", Hashes: []string{"shim-hash-1"}},
  1042  				{Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1", "grub-hash-2"}},
  1043  			},
  1044  			expectedKernelRevs: []int{1, 3},
  1045  		},
  1046  		{
  1047  			// non-transition sequence
  1048  			recoverySystems: []string{"20200825"},
  1049  			assetsMap: boot.BootAssetsMap{
  1050  				"grubx64.efi": []string{"grub-hash-1"},
  1051  				"bootx64.efi": []string{"shim-hash-1"},
  1052  			},
  1053  			expectedAssets: []boot.BootAsset{
  1054  				{Role: bootloader.RoleRecovery, Name: "bootx64.efi", Hashes: []string{"shim-hash-1"}},
  1055  				{Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1"}},
  1056  			},
  1057  			expectedKernelRevs: []int{1},
  1058  		},
  1059  		{
  1060  			// invalid recovery system label
  1061  			recoverySystems: []string{"0"},
  1062  			err:             `cannot read system "0" seed: invalid system seed`,
  1063  		},
  1064  	} {
  1065  		rootdir := c.MkDir()
  1066  		dirs.SetRootDir(rootdir)
  1067  		defer dirs.SetRootDir("")
  1068  
  1069  		// set recovery kernel
  1070  		restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
  1071  			if label != "20200825" && label != "20200831" {
  1072  				return nil, nil, fmt.Errorf("invalid system seed")
  1073  			}
  1074  			kernelRev := 1
  1075  			if label == "20200831" {
  1076  				kernelRev = 3
  1077  			}
  1078  			kernelSnap := &seed.Snap{
  1079  				Path: fmt.Sprintf("/var/lib/snapd/seed/snaps/pc-kernel_%d.snap", kernelRev),
  1080  				SideInfo: &snap.SideInfo{
  1081  					RealName: "pc-kernel",
  1082  					Revision: snap.R(kernelRev),
  1083  				},
  1084  			}
  1085  			return nil, []*seed.Snap{kernelSnap}, nil
  1086  		})
  1087  		defer restore()
  1088  
  1089  		grubDir := filepath.Join(rootdir, "run/mnt/ubuntu-seed")
  1090  		err := createMockGrubCfg(grubDir)
  1091  		c.Assert(err, IsNil)
  1092  
  1093  		bl, err := bootloader.Find(grubDir, &bootloader.Options{Role: bootloader.RoleRecovery})
  1094  		c.Assert(err, IsNil)
  1095  		tbl, ok := bl.(bootloader.TrustedAssetsBootloader)
  1096  		c.Assert(ok, Equals, true)
  1097  
  1098  		model := boottest.MakeMockUC20Model()
  1099  
  1100  		modeenv := &boot.Modeenv{
  1101  			CurrentTrustedRecoveryBootAssets: tc.assetsMap,
  1102  		}
  1103  
  1104  		bc, err := boot.RecoveryBootChainsForSystems(tc.recoverySystems, tbl, model, modeenv)
  1105  		if tc.err == "" {
  1106  			c.Assert(err, IsNil)
  1107  			c.Assert(bc, HasLen, len(tc.recoverySystems))
  1108  			for i, chain := range bc {
  1109  				c.Assert(chain.AssetChain, DeepEquals, tc.expectedAssets)
  1110  				c.Check(chain.Kernel, Equals, "pc-kernel")
  1111  				expectedKernelRev := tc.expectedKernelRevs[i]
  1112  				c.Check(chain.KernelRevision, Equals, fmt.Sprintf("%d", expectedKernelRev))
  1113  				c.Check(chain.KernelBootFile(), DeepEquals, bootloader.BootFile{Snap: fmt.Sprintf("/var/lib/snapd/seed/snaps/pc-kernel_%d.snap", expectedKernelRev), Path: "kernel.efi", Role: bootloader.RoleRecovery})
  1114  			}
  1115  		} else {
  1116  			c.Assert(err, ErrorMatches, tc.err)
  1117  		}
  1118  
  1119  	}
  1120  
  1121  }
  1122  
  1123  func createMockGrubCfg(baseDir string) error {
  1124  	cfg := filepath.Join(baseDir, "EFI/ubuntu/grub.cfg")
  1125  	if err := os.MkdirAll(filepath.Dir(cfg), 0755); err != nil {
  1126  		return err
  1127  	}
  1128  	return ioutil.WriteFile(cfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644)
  1129  }
  1130  
  1131  func (s *sealSuite) TestSealKeyModelParams(c *C) {
  1132  	rootdir := c.MkDir()
  1133  	dirs.SetRootDir(rootdir)
  1134  	defer dirs.SetRootDir("")
  1135  
  1136  	model := boottest.MakeMockUC20Model()
  1137  
  1138  	roleToBlName := map[bootloader.Role]string{
  1139  		bootloader.RoleRecovery: "grub",
  1140  		bootloader.RoleRunMode:  "grub",
  1141  	}
  1142  	// mock asset cache
  1143  	mockAssetsCache(c, rootdir, "grub", []string{
  1144  		"shim-shim-hash",
  1145  		"loader-loader-hash1",
  1146  		"loader-loader-hash2",
  1147  	})
  1148  
  1149  	oldmodel := boottest.MakeMockUC20Model(map[string]interface{}{
  1150  		"model":     "old-model-uc20",
  1151  		"timestamp": "2019-10-01T08:00:00+00:00",
  1152  	})
  1153  
  1154  	// old recovery
  1155  	oldrc := boot.BootChain{
  1156  		BrandID: oldmodel.BrandID(),
  1157  		Model:   oldmodel.Model(),
  1158  		AssetChain: []boot.BootAsset{
  1159  			{Name: "shim", Role: bootloader.RoleRecovery, Hashes: []string{"shim-hash"}},
  1160  			{Name: "loader", Role: bootloader.RoleRecovery, Hashes: []string{"loader-hash1"}},
  1161  		},
  1162  		KernelCmdlines: []string{"panic=1", "oldrc"},
  1163  	}
  1164  	oldrc.SetModelAssertion(oldmodel)
  1165  	oldkbf := bootloader.BootFile{Snap: "pc-kernel_1.snap"}
  1166  	oldrc.SetKernelBootFile(oldkbf)
  1167  
  1168  	// recovery
  1169  	rc1 := boot.BootChain{
  1170  		BrandID: model.BrandID(),
  1171  		Model:   model.Model(),
  1172  		AssetChain: []boot.BootAsset{
  1173  			{Name: "shim", Role: bootloader.RoleRecovery, Hashes: []string{"shim-hash"}},
  1174  			{Name: "loader", Role: bootloader.RoleRecovery, Hashes: []string{"loader-hash1"}},
  1175  		},
  1176  		KernelCmdlines: []string{"panic=1", "rc1"},
  1177  	}
  1178  	rc1.SetModelAssertion(model)
  1179  	rc1kbf := bootloader.BootFile{Snap: "pc-kernel_10.snap"}
  1180  	rc1.SetKernelBootFile(rc1kbf)
  1181  
  1182  	// run system
  1183  	runc1 := boot.BootChain{
  1184  		BrandID: model.BrandID(),
  1185  		Model:   model.Model(),
  1186  		AssetChain: []boot.BootAsset{
  1187  			{Name: "shim", Role: bootloader.RoleRecovery, Hashes: []string{"shim-hash"}},
  1188  			{Name: "loader", Role: bootloader.RoleRecovery, Hashes: []string{"loader-hash1"}},
  1189  			{Name: "loader", Role: bootloader.RoleRunMode, Hashes: []string{"loader-hash2"}},
  1190  		},
  1191  		KernelCmdlines: []string{"panic=1", "runc1"},
  1192  	}
  1193  	runc1.SetModelAssertion(model)
  1194  	runc1kbf := bootloader.BootFile{Snap: "pc-kernel_50.snap"}
  1195  	runc1.SetKernelBootFile(runc1kbf)
  1196  
  1197  	pbc := boot.ToPredictableBootChains([]boot.BootChain{rc1, runc1, oldrc})
  1198  
  1199  	shim := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/shim-shim-hash"), bootloader.RoleRecovery)
  1200  	loader1 := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/loader-loader-hash1"), bootloader.RoleRecovery)
  1201  	loader2 := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/loader-loader-hash2"), bootloader.RoleRunMode)
  1202  
  1203  	params, err := boot.SealKeyModelParams(pbc, roleToBlName)
  1204  	c.Assert(err, IsNil)
  1205  	c.Check(params, HasLen, 2)
  1206  	c.Check(params[0].Model, Equals, model)
  1207  	// NB: merging of lists makes panic=1 appear once
  1208  	c.Check(params[0].KernelCmdlines, DeepEquals, []string{"panic=1", "rc1", "runc1"})
  1209  
  1210  	c.Check(params[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{
  1211  		secboot.NewLoadChain(shim,
  1212  			secboot.NewLoadChain(loader1,
  1213  				secboot.NewLoadChain(rc1kbf))),
  1214  		secboot.NewLoadChain(shim,
  1215  			secboot.NewLoadChain(loader1,
  1216  				secboot.NewLoadChain(loader2,
  1217  					secboot.NewLoadChain(runc1kbf)))),
  1218  	})
  1219  
  1220  	c.Check(params[1].Model, Equals, oldmodel)
  1221  	c.Check(params[1].KernelCmdlines, DeepEquals, []string{"oldrc", "panic=1"})
  1222  	c.Check(params[1].EFILoadChains, DeepEquals, []*secboot.LoadChain{
  1223  		secboot.NewLoadChain(shim,
  1224  			secboot.NewLoadChain(loader1,
  1225  				secboot.NewLoadChain(oldkbf))),
  1226  	})
  1227  }
  1228  
  1229  func (s *sealSuite) TestIsResealNeeded(c *C) {
  1230  	if os.Geteuid() == 0 {
  1231  		c.Skip("the test cannot be run by the root user")
  1232  	}
  1233  
  1234  	chains := []boot.BootChain{
  1235  		{
  1236  			BrandID:        "mybrand",
  1237  			Model:          "foo",
  1238  			Grade:          "signed",
  1239  			ModelSignKeyID: "my-key-id",
  1240  			AssetChain: []boot.BootAsset{
  1241  				// hashes will be sorted
  1242  				{Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"x", "y"}},
  1243  				{Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"c", "d"}},
  1244  				{Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"z", "x"}},
  1245  			},
  1246  			Kernel:         "pc-kernel-other",
  1247  			KernelRevision: "2345",
  1248  			KernelCmdlines: []string{`snapd_recovery_mode=run foo`},
  1249  		}, {
  1250  			BrandID:        "mybrand",
  1251  			Model:          "foo",
  1252  			Grade:          "dangerous",
  1253  			ModelSignKeyID: "my-key-id",
  1254  			AssetChain: []boot.BootAsset{
  1255  				// hashes will be sorted
  1256  				{Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"y", "x"}},
  1257  				{Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"c", "d"}},
  1258  			},
  1259  			Kernel:         "pc-kernel-recovery",
  1260  			KernelRevision: "1234",
  1261  			KernelCmdlines: []string{`snapd_recovery_mode=recover foo`},
  1262  		},
  1263  	}
  1264  
  1265  	pbc := boot.ToPredictableBootChains(chains)
  1266  
  1267  	rootdir := c.MkDir()
  1268  	err := boot.WriteBootChains(pbc, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), 2)
  1269  	c.Assert(err, IsNil)
  1270  
  1271  	needed, _, err := boot.IsResealNeeded(pbc, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), false)
  1272  	c.Assert(err, IsNil)
  1273  	c.Check(needed, Equals, false)
  1274  
  1275  	otherchain := []boot.BootChain{pbc[0]}
  1276  	needed, cnt, err := boot.IsResealNeeded(otherchain, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), false)
  1277  	c.Assert(err, IsNil)
  1278  	// chains are different
  1279  	c.Check(needed, Equals, true)
  1280  	c.Check(cnt, Equals, 3)
  1281  
  1282  	// boot-chains does not exist, we cannot compare so advise to reseal
  1283  	otherRootdir := c.MkDir()
  1284  	needed, cnt, err = boot.IsResealNeeded(otherchain, filepath.Join(dirs.SnapFDEDirUnder(otherRootdir), "boot-chains"), false)
  1285  	c.Assert(err, IsNil)
  1286  	c.Check(needed, Equals, true)
  1287  	c.Check(cnt, Equals, 1)
  1288  
  1289  	// exists but cannot be read
  1290  	c.Assert(os.Chmod(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), 0000), IsNil)
  1291  	defer os.Chmod(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), 0755)
  1292  	needed, _, err = boot.IsResealNeeded(otherchain, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), false)
  1293  	c.Assert(err, ErrorMatches, "cannot open existing boot chains data file: open .*/boot-chains: permission denied")
  1294  	c.Check(needed, Equals, false)
  1295  
  1296  	// unrevisioned kernel chain
  1297  	unrevchain := []boot.BootChain{pbc[0], pbc[1]}
  1298  	unrevchain[1].KernelRevision = ""
  1299  	// write on disk
  1300  	bootChainsFile := filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains")
  1301  	err = boot.WriteBootChains(unrevchain, bootChainsFile, 2)
  1302  	c.Assert(err, IsNil)
  1303  
  1304  	needed, cnt, err = boot.IsResealNeeded(pbc, bootChainsFile, false)
  1305  	c.Assert(err, IsNil)
  1306  	c.Check(needed, Equals, true)
  1307  	c.Check(cnt, Equals, 3)
  1308  
  1309  	// cases falling back to expectReseal
  1310  	needed, _, err = boot.IsResealNeeded(unrevchain, bootChainsFile, false)
  1311  	c.Assert(err, IsNil)
  1312  	c.Check(needed, Equals, false)
  1313  
  1314  	needed, cnt, err = boot.IsResealNeeded(unrevchain, bootChainsFile, true)
  1315  	c.Assert(err, IsNil)
  1316  	c.Check(needed, Equals, true)
  1317  	c.Check(cnt, Equals, 3)
  1318  }
  1319  
  1320  func (s *sealSuite) TestSealToModeenvWithFdeHookHappy(c *C) {
  1321  	rootdir := c.MkDir()
  1322  	dirs.SetRootDir(rootdir)
  1323  	defer dirs.SetRootDir("")
  1324  
  1325  	restore := boot.MockHasFDESetupHook(func() (bool, error) {
  1326  		return true, nil
  1327  	})
  1328  	defer restore()
  1329  
  1330  	n := 0
  1331  	var runFDESetupHookParams []*boot.FDESetupHookParams
  1332  	restore = boot.MockRunFDESetupHook(func(op string, params *boot.FDESetupHookParams) ([]byte, error) {
  1333  		n++
  1334  		c.Assert(op, Equals, "initial-setup")
  1335  		runFDESetupHookParams = append(runFDESetupHookParams, params)
  1336  		return []byte("sealed-key: " + strconv.Itoa(n)), nil
  1337  	})
  1338  	defer restore()
  1339  
  1340  	modeenv := &boot.Modeenv{
  1341  		RecoverySystem: "20200825",
  1342  	}
  1343  	key := secboot.EncryptionKey{1, 2, 3, 4}
  1344  	saveKey := secboot.EncryptionKey{5, 6, 7, 8}
  1345  
  1346  	model := boottest.MakeMockUC20Model()
  1347  	err := boot.SealKeyToModeenv(key, saveKey, model, modeenv)
  1348  	c.Assert(err, IsNil)
  1349  	// check that runFDESetupHook was called the expected way
  1350  	c.Check(runFDESetupHookParams, DeepEquals, []*boot.FDESetupHookParams{
  1351  		{Key: secboot.EncryptionKey{1, 2, 3, 4}, KeyName: "ubuntu-data", Models: []*asserts.Model{model}},
  1352  		{Key: secboot.EncryptionKey{1, 2, 3, 4}, KeyName: "ubuntu-data", Models: []*asserts.Model{model}},
  1353  		{Key: secboot.EncryptionKey{5, 6, 7, 8}, KeyName: "ubuntu-save", Models: []*asserts.Model{model}},
  1354  	})
  1355  	// check that the sealed keys got written to the expected places
  1356  	for i, p := range []string{
  1357  		filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
  1358  		filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
  1359  		filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
  1360  	} {
  1361  		c.Check(p, testutil.FileEquals, "sealed-key: "+strconv.Itoa(i+1))
  1362  	}
  1363  	marker := filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "sealed-keys")
  1364  	c.Check(marker, testutil.FileEquals, "fde-setup-hook")
  1365  }
  1366  
  1367  func (s *sealSuite) TestSealToModeenvWithFdeHookSad(c *C) {
  1368  	rootdir := c.MkDir()
  1369  	dirs.SetRootDir(rootdir)
  1370  	defer dirs.SetRootDir("")
  1371  
  1372  	restore := boot.MockHasFDESetupHook(func() (bool, error) {
  1373  		return true, nil
  1374  	})
  1375  	defer restore()
  1376  
  1377  	restore = boot.MockRunFDESetupHook(func(op string, params *boot.FDESetupHookParams) ([]byte, error) {
  1378  		return nil, fmt.Errorf("hook failed")
  1379  	})
  1380  	defer restore()
  1381  
  1382  	modeenv := &boot.Modeenv{
  1383  		RecoverySystem: "20200825",
  1384  	}
  1385  	key := secboot.EncryptionKey{1, 2, 3, 4}
  1386  	saveKey := secboot.EncryptionKey{5, 6, 7, 8}
  1387  
  1388  	model := boottest.MakeMockUC20Model()
  1389  	err := boot.SealKeyToModeenv(key, saveKey, model, modeenv)
  1390  	c.Assert(err, ErrorMatches, "hook failed")
  1391  	marker := filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "sealed-keys")
  1392  	c.Check(marker, testutil.FileAbsent)
  1393  }
  1394  
  1395  func (s *sealSuite) TestResealKeyToModeenvWithFdeHookCalled(c *C) {
  1396  	rootdir := c.MkDir()
  1397  	dirs.SetRootDir(rootdir)
  1398  	defer dirs.SetRootDir("")
  1399  
  1400  	resealKeyToModeenvUsingFDESetupHookCalled := 0
  1401  	restore := boot.MockResealKeyToModeenvUsingFDESetupHook(func(string, *asserts.Model, *boot.Modeenv, bool) error {
  1402  		resealKeyToModeenvUsingFDESetupHookCalled++
  1403  		return nil
  1404  	})
  1405  	defer restore()
  1406  
  1407  	// TODO: this simulates that the hook is not available yet
  1408  	//       because of e.g. seeding. Longer term there will be
  1409  	//       more, see TODO in resealKeyToModeenvUsingFDESetupHookImpl
  1410  	restore = boot.MockHasFDESetupHook(func() (bool, error) {
  1411  		return false, fmt.Errorf("hook not available yet because e.g. seeding")
  1412  	})
  1413  	defer restore()
  1414  
  1415  	marker := filepath.Join(dirs.SnapFDEDirUnder(rootdir), "sealed-keys")
  1416  	err := os.MkdirAll(filepath.Dir(marker), 0755)
  1417  	c.Assert(err, IsNil)
  1418  	err = ioutil.WriteFile(marker, []byte("fde-setup-hook"), 0644)
  1419  	c.Assert(err, IsNil)
  1420  
  1421  	modeenv := &boot.Modeenv{
  1422  		RecoverySystem: "20200825",
  1423  	}
  1424  
  1425  	model := boottest.MakeMockUC20Model()
  1426  	expectReseal := false
  1427  	err = boot.ResealKeyToModeenv(rootdir, model, modeenv, expectReseal)
  1428  	c.Assert(err, IsNil)
  1429  	c.Check(resealKeyToModeenvUsingFDESetupHookCalled, Equals, 1)
  1430  }
  1431  
  1432  func (s *sealSuite) TestResealKeyToModeenvWithFdeHookVerySad(c *C) {
  1433  	rootdir := c.MkDir()
  1434  	dirs.SetRootDir(rootdir)
  1435  	defer dirs.SetRootDir("")
  1436  
  1437  	resealKeyToModeenvUsingFDESetupHookCalled := 0
  1438  	restore := boot.MockResealKeyToModeenvUsingFDESetupHook(func(string, *asserts.Model, *boot.Modeenv, bool) error {
  1439  		resealKeyToModeenvUsingFDESetupHookCalled++
  1440  		return fmt.Errorf("fde setup hook failed")
  1441  	})
  1442  	defer restore()
  1443  
  1444  	marker := filepath.Join(dirs.SnapFDEDirUnder(rootdir), "sealed-keys")
  1445  	err := os.MkdirAll(filepath.Dir(marker), 0755)
  1446  	c.Assert(err, IsNil)
  1447  	err = ioutil.WriteFile(marker, []byte("fde-setup-hook"), 0644)
  1448  	c.Assert(err, IsNil)
  1449  
  1450  	modeenv := &boot.Modeenv{
  1451  		RecoverySystem: "20200825",
  1452  	}
  1453  
  1454  	model := boottest.MakeMockUC20Model()
  1455  	expectReseal := false
  1456  	err = boot.ResealKeyToModeenv(rootdir, model, modeenv, expectReseal)
  1457  	c.Assert(err, ErrorMatches, "fde setup hook failed")
  1458  	c.Check(resealKeyToModeenvUsingFDESetupHookCalled, Equals, 1)
  1459  }