From 46eb39ee3c2885a3edb721868411b83a3eb4ff76 Mon Sep 17 00:00:00 2001 From: Valentin David Date: Tue, 17 Sep 2024 14:14:02 +0200 Subject: [PATCH] overlord/fdestate: keep FDE state up to date StartUp() initializes the empty profiles, and reseal updates them. --- boot/assets_test.go | 16 +- boot/boot_test.go | 48 ++-- boot/export_test.go | 2 +- boot/model_test.go | 24 +- boot/seal.go | 12 +- boot/seal_test.go | 12 +- boot/systems_test.go | 26 +- osutil/disks/disks.go | 21 ++ osutil/disks/disks_linux.go | 5 +- osutil/disks/disks_linux_test.go | 4 +- overlord/fdestate/backend/reseal.go | 69 ++++- overlord/fdestate/backend/reseal_test.go | 327 +++++++++++++++-------- overlord/fdestate/export_test.go | 2 + overlord/fdestate/fdemgr.go | 24 +- overlord/fdestate/fdemgr_test.go | 122 ++++++++- overlord/fdestate/fdestate.go | 299 +++++++++++++++++++++ overlord/managers_test.go | 33 ++- secboot/secboot.go | 14 +- secboot/secboot_dummy.go | 13 + secboot/secboot_sb.go | 32 +++ secboot/secboot_sb_test.go | 130 +++++---- secboot/secboot_tpm.go | 25 +- 22 files changed, 1006 insertions(+), 254 deletions(-) diff --git a/boot/assets_test.go b/boot/assets_test.go index cbe56350d97..f1a96562afb 100644 --- a/boot/assets_test.go +++ b/boot/assets_test.go @@ -53,7 +53,7 @@ var _ = Suite(&assetsSuite{}) func (s *assetsSuite) SetUpTest(c *C) { s.baseBootenvSuite.SetUpTest(c) - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { return nil }) s.AddCleanup(restore) @@ -788,7 +788,7 @@ func (s *assetsSuite) testUpdateObserverUpdateMockedWithReseal(c *C, seedRole st // everything is set up, trigger a reseal resealCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ return nil }) @@ -893,7 +893,7 @@ func (s *assetsSuite) TestUpdateObserverUpdateExistingAssetMocked(c *C) { // everything is set up, trigger reseal resealCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ return nil }) @@ -1649,7 +1649,7 @@ func (s *assetsSuite) TestUpdateObserverCanceledSimpleAfterBackupMocked(c *C) { "shim": []string{shimHash}, }) resealCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ return nil }) @@ -1809,7 +1809,7 @@ func (s *assetsSuite) TestUpdateObserverCanceledNoActionsMocked(c *C) { obs, _ := s.uc20UpdateObserverEncryptedSystemMockedBootloader(c) resealCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ return nil }) @@ -2561,7 +2561,7 @@ func (s *assetsSuite) TestUpdateObserverReseal(c *C) { // everything is set up, trigger a reseal resealCalls := 0 - restore = boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore = boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ c.Assert(params.RunModeBootChains, HasLen, 1) @@ -2713,7 +2713,7 @@ func (s *assetsSuite) TestUpdateObserverCanceledReseal(c *C) { resealCalls := 0 - restore = boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore = boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ c.Assert(params.RunModeBootChains, HasLen, 1) @@ -2846,7 +2846,7 @@ func (s *assetsSuite) TestUpdateObserverUpdateMockedNonEncryption(c *C) { // make sure that no reseal is triggered resealCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ return nil }) diff --git a/boot/boot_test.go b/boot/boot_test.go index 3a0e8809cb0..c57585af6e8 100644 --- a/boot/boot_test.go +++ b/boot/boot_test.go @@ -137,7 +137,7 @@ type baseBootenv20Suite struct { func (s *baseBootenv20Suite) SetUpTest(c *C) { s.baseBootenvSuite.SetUpTest(c) - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { return nil }) s.AddCleanup(restore) @@ -1121,7 +1121,7 @@ func (s *bootenv20Suite) TestCoreParticipant20SetNextNewKernelSnapWithReseal(c * defer r() resealCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ c.Assert(params.RunModeBootChains, HasLen, 2) @@ -1241,7 +1241,7 @@ func (s *bootenv20Suite) TestCoreParticipant20SetNextNewUnassertedKernelSnapWith defer r() resealCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ c.Assert(params.RunModeBootChains, HasLen, 2) @@ -1362,7 +1362,7 @@ func (s *bootenv20Suite) TestCoreParticipant20SetNextSameKernelSnapNoReseal(c *C defer r() resealCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ return nil }) @@ -1459,7 +1459,7 @@ func (s *bootenv20Suite) TestCoreParticipant20SetNextSameUnassertedKernelSnapNoR defer r() resealCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ return nil }) @@ -2058,7 +2058,7 @@ func (s *bootenv20Suite) TestMarkBootSuccessful20KernelUpdateWithReseal(c *C) { defer r() resealCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ c.Assert(params.RunModeBootChains, HasLen, 1) @@ -2292,7 +2292,7 @@ func (s *bootenv20Suite) TestMarkBootSuccessful20BootAssetsUpdateHappy(c *C) { c.Assert(coreDev.HasModeenv(), Equals, true) resealCalls := 0 - restore = boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore = boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ c.Assert(params.RunModeBootChains, HasLen, 1) @@ -2453,7 +2453,7 @@ func (s *bootenv20Suite) TestMarkBootSuccessful20BootAssetsStableStateHappy(c *C c.Assert(coreDev.HasModeenv(), Equals, true) resealCalls := 0 - restore = boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore = boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ return nil }) @@ -2567,7 +2567,7 @@ func (s *bootenv20Suite) TestMarkBootSuccessful20BootUnassertedKernelAssetsStabl c.Assert(coreDev.HasModeenv(), Equals, true) resealCalls := 0 - restore = boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore = boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ return nil }) @@ -3156,7 +3156,7 @@ var _ = Suite(&bootConfigSuite{}) func (s *bootConfigSuite) SetUpTest(c *C) { s.baseBootenvSuite.SetUpTest(c) - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { return nil }) s.AddCleanup(restore) @@ -3193,7 +3193,7 @@ func (s *bootConfigSuite) TestBootConfigUpdateHappyNoKeysNoReseal(c *C) { c.Assert(m.WriteTo(""), IsNil) resealCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ return nil }) @@ -3245,7 +3245,7 @@ func (s *bootConfigSuite) testBootConfigUpdateHappyWithReseal(c *C, cmdlineAppen newCmdline := strutil.JoinNonEmpty([]string{ "snapd_recovery_mode=run mocked candidate panic=-1", cmdlineAppend}, " ") resealCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ c.Assert(params, NotNil) @@ -3305,7 +3305,7 @@ func (s *bootConfigSuite) testBootConfigUpdateHappyNoChange(c *C, cmdlineAppend c.Assert(m.WriteTo(""), IsNil) resealCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ return nil }) @@ -3470,7 +3470,7 @@ volumes: c.Assert(m.WriteTo(""), IsNil) resealCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ c.Assert(params, NotNil) @@ -3536,7 +3536,7 @@ volumes: // reseal does not happen, because the gadget overrides the static // command line which is part of boot config, thus there's no resulting // change in the command lines tracked in modeenv and no need to reseal - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ return fmt.Errorf("unexpected call") }) @@ -3573,7 +3573,7 @@ var _ = Suite(&bootKernelCommandLineSuite{}) func (s *bootKernelCommandLineSuite) SetUpTest(c *C) { s.baseBootenvSuite.SetUpTest(c) - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { return nil }) s.AddCleanup(restore) @@ -3633,7 +3633,7 @@ func (s *bootKernelCommandLineSuite) SetUpTest(c *C) { s.resealCommandLines = nil s.resealCalls = 0 - restore = boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore = boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { s.resealCalls++ c.Assert(params, NotNil) c.Assert(params.RunModeBootChains, HasLen, 0) @@ -3909,7 +3909,7 @@ volumes: c.Assert(s.modeenvWithEncryption.WriteTo(""), IsNil) resealCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ return fmt.Errorf("reseal fails") }) @@ -4053,7 +4053,7 @@ func (s *bootKernelCommandLineSuite) TestCommandLineUpdateUC20OverSpuriousReboot s.stampSealedKeys(c, dirs.GlobalRootDir) resealPanic := false - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { s.resealCalls++ c.Logf("reseal call %v", s.resealCalls) c.Assert(params, NotNil) @@ -4636,7 +4636,7 @@ func (s *bootenv20Suite) TestCoreParticipant20UndoKernelSnapInstallNewWithReseal defer r() resealCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ c.Assert(params.RunModeBootChains, HasLen, 1) @@ -4747,7 +4747,7 @@ func (s *bootenv20Suite) TestCoreParticipant20UndoUnassertedKernelSnapInstallNew defer r() resealCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ c.Assert(params.RunModeBootChains, HasLen, 1) @@ -4859,7 +4859,7 @@ func (s *bootenv20Suite) TestCoreParticipant20UndoKernelSnapInstallSameNoReseal( defer r() resealCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ return nil }) @@ -4956,7 +4956,7 @@ func (s *bootenv20Suite) TestCoreParticipant20UndoUnassertedKernelSnapInstallSam defer r() resealCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ return nil }) @@ -5092,7 +5092,7 @@ func (s *bootenv20Suite) TestCoreParticipant20UndoBaseSnapInstallNewNoReseal(c * model := coreDev.Model() resealCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ return nil }) diff --git a/boot/export_test.go b/boot/export_test.go index 0b77742562c..51e500e5515 100644 --- a/boot/export_test.go +++ b/boot/export_test.go @@ -226,7 +226,7 @@ func EnableTestingRebootFunction() (restore func()) { return func() { testingRebootItself = false } } -func MockResealKeyForBootChains(f func(method device.SealingMethod, rootdir string, params *ResealKeyForBootChainsParams, expectReseal bool) error) (restore func()) { +func MockResealKeyForBootChains(f func(unlocker Unlocker, method device.SealingMethod, rootdir string, params *ResealKeyForBootChainsParams, expectReseal bool) error) (restore func()) { old := ResealKeyForBootChains ResealKeyForBootChains = f return func() { diff --git a/boot/model_test.go b/boot/model_test.go index 61643e2fe1e..d78c1e8e9b8 100644 --- a/boot/model_test.go +++ b/boot/model_test.go @@ -89,7 +89,7 @@ func makeEncodableModel(signingAccounts *assertstest.SigningAccounts, overrides func (s *modelSuite) SetUpTest(c *C) { s.baseBootenvSuite.SetUpTest(c) - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { return nil }) s.AddCleanup(restore) @@ -204,7 +204,7 @@ func (s *modelSuite) TestDeviceChangeHappy(c *C) { "model: my-model-uc20\n") resealKeysCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealKeysCalls++ m, err := boot.ReadModeenv("") c.Assert(err, IsNil) @@ -242,7 +242,7 @@ func (s *modelSuite) TestDeviceChangeHappy(c *C) { err = boot.DeviceChange(s.oldUc20dev, s.newUc20dev, u.unlocker) c.Assert(err, IsNil) c.Assert(resealKeysCalls, Equals, 2) - c.Check(u.unlocked, Equals, 2) + c.Check(u.unlocked, Equals, 0) c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, "model: my-new-model-uc20\n") @@ -267,7 +267,7 @@ func (s *modelSuite) TestDeviceChangeUnhappyFirstReseal(c *C) { "model: my-model-uc20\n") resealKeysCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealKeysCalls++ m, err := boot.ReadModeenv("") c.Assert(err, IsNil) @@ -318,7 +318,7 @@ func (s *modelSuite) TestDeviceChangeUnhappyFirstSwapModelFile(c *C) { "model: my-model-uc20\n") resealKeysCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealKeysCalls++ m, err := boot.ReadModeenv("") c.Assert(err, IsNil) @@ -371,7 +371,7 @@ func (s *modelSuite) TestDeviceChangeUnhappySecondReseal(c *C) { "model: my-model-uc20\n") resealKeysCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealKeysCalls++ m, err := boot.ReadModeenv("") c.Assert(err, IsNil) @@ -467,7 +467,7 @@ func (s *modelSuite) TestDeviceChangeRebootBeforeNewModel(c *C) { "model: my-model-uc20\n") resealKeysCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealKeysCalls++ c.Logf("reseal key call: %v", resealKeysCalls) m, err := boot.ReadModeenv("") @@ -586,7 +586,7 @@ func (s *modelSuite) TestDeviceChangeRebootAfterNewModelFileWrite(c *C) { "model: my-model-uc20\n") resealKeysCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealKeysCalls++ // timeline & calls: // 1 - pre reboot, run & recovery keys, try model set @@ -706,7 +706,7 @@ func (s *modelSuite) TestDeviceChangeRebootPostSameModel(c *C) { "model: my-model-uc20\n") resealKeysCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealKeysCalls++ c.Logf("reseal key call: %v", resealKeysCalls) m, err := boot.ReadModeenv("") @@ -849,7 +849,7 @@ func (s *modelSuite) testDeviceChangeUnhappyMockedWriteModelToBoot(c *C, tc unha writeModelToBootCalls := 0 resealKeysCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealKeysCalls++ m, err := boot.ReadModeenv("") c.Assert(err, IsNil) @@ -972,7 +972,7 @@ func (s *modelSuite) TestDeviceChangeUnhappyFailReseaWithSwappedModelMockedWrite writeModelToBootCalls := 0 resealKeysCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealKeysCalls++ if resealKeysCalls == 2 { m, err := boot.ReadModeenv("") @@ -1059,7 +1059,7 @@ func (s *modelSuite) TestDeviceChangeRebootRestoreModelKeyChangeMockedWriteModel })) resealKeysCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealKeysCalls++ c.Logf("reseal key call: %v", resealKeysCalls) m, err := boot.ReadModeenv("") diff --git a/boot/seal.go b/boot/seal.go index 05e89c8f017..c3b68b4b69a 100644 --- a/boot/seal.go +++ b/boot/seal.go @@ -251,11 +251,7 @@ func resealKeyToModeenvImpl(rootdir string, modeenv *Modeenv, expectReseal bool, return err } - if unlocker != nil { - // unlock/relock global state - defer unlocker()() - } - return resealKeyToModeenvForMethod(method, rootdir, modeenv, expectReseal) + return resealKeyToModeenvForMethod(unlocker, method, rootdir, modeenv, expectReseal) } type ResealKeyForBootChainsParams struct { @@ -270,7 +266,7 @@ type ResealKeyForBootChainsParams struct { RoleToBlName map[bootloader.Role]string } -func resealKeyToModeenvForMethod(method device.SealingMethod, rootdir string, modeenv *Modeenv, expectReseal bool) error { +func resealKeyToModeenvForMethod(unlocker Unlocker, method device.SealingMethod, rootdir string, modeenv *Modeenv, expectReseal bool) error { // this is just optimization. If the backend does not need it, we should not calculate it. requiresBootChains := true switch method { @@ -348,10 +344,10 @@ func resealKeyToModeenvForMethod(method device.SealingMethod, rootdir string, mo } } - return ResealKeyForBootChains(method, rootdir, params, expectReseal) + return ResealKeyForBootChains(unlocker, method, rootdir, params, expectReseal) } -func resealKeyForBootChainsImpl(method device.SealingMethod, rootdir string, params *ResealKeyForBootChainsParams, expectReseal bool) error { +func resealKeyForBootChainsImpl(unlocker Unlocker, method device.SealingMethod, rootdir string, params *ResealKeyForBootChainsParams, expectReseal bool) error { return fmt.Errorf("FDE manager was not started") } diff --git a/boot/seal_test.go b/boot/seal_test.go index d43a643cfe4..3e6761a8d6e 100644 --- a/boot/seal_test.go +++ b/boot/seal_test.go @@ -560,7 +560,7 @@ func (s *sealSuite) TestResealKeyToModeenvWithSystemFallback(c *C) { // set mock key resealing resealKeysCalls := 0 - restore = boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdirArg string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore = boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdirArg string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealKeysCalls++ c.Check(method, Equals, device.SealingMethodTPM) @@ -654,7 +654,7 @@ func (s *sealSuite) TestResealKeyToModeenvRecoveryKeysForGoodSystemsOnly(c *C) { // set mock key resealing resealKeysCalls := 0 - restore = boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdirArg string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore = boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdirArg string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealKeysCalls++ c.Check(method, Equals, device.SealingMethodTPM) @@ -869,7 +869,7 @@ func (s *sealSuite) TestResealKeyToModeenvFallbackCmdline(c *C) { // set mock key resealing resealKeysCalls := 0 - restore = boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdirArg string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore = boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdirArg string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { c.Check(rootdirArg, Equals, rootdir) c.Check(method, Equals, device.SealingMethodTPM) c.Check(expectReseal, Equals, false) @@ -1713,7 +1713,7 @@ func (s *sealSuite) TestResealKeyToModeenvWithFdeHookCalled(c *C) { defer dirs.SetRootDir("") mockResealKeyForBootChainsCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdirArg string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdirArg string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { c.Check(rootdirArg, Equals, rootdir) c.Check(method, Equals, device.SealingMethodFDESetupHook) c.Check(expectReseal, Equals, false) @@ -1759,7 +1759,7 @@ func (s *sealSuite) TestResealKeyToModeenvWithFdeHookVerySad(c *C) { defer dirs.SetRootDir("") mockResealKeyForBootChainsCalls := 0 - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdirArg string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdirArg string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { c.Check(rootdirArg, Equals, rootdir) c.Check(method, Equals, device.SealingMethodFDESetupHook) c.Check(expectReseal, Equals, false) @@ -1871,7 +1871,7 @@ func (s *sealSuite) testResealKeyToModeenvWithTryModel(c *C, shimId, grubId stri // set mock key resealing resealKeysCalls := 0 - restore = boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdirArg string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore = boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdirArg string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { c.Check(rootdirArg, Equals, rootdir) c.Check(method, Equals, device.SealingMethodTPM) c.Check(expectReseal, Equals, false) diff --git a/boot/systems_test.go b/boot/systems_test.go index 6b1859753ae..53fc8ad5b93 100644 --- a/boot/systems_test.go +++ b/boot/systems_test.go @@ -83,7 +83,7 @@ func (s *systemsSuite) mockTrustedBootloaderWithAssetAndChains(c *C, runKernelBf func (s *systemsSuite) SetUpTest(c *C) { s.baseBootenvSuite.SetUpTest(c) - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { return nil }) s.AddCleanup(restore) @@ -145,7 +145,7 @@ func (s *systemsSuite) TestSetTryRecoverySystemEncrypted(c *C) { defer restore() resealCalls := 0 - restore = boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore = boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ // bootloader variables have already been modified c.Check(mtbl.SetBootVarsCalls, Equals, 1) @@ -270,7 +270,7 @@ func (s *systemsSuite) TestSetTryRecoverySystemRemodelEncrypted(c *C) { defer restore() resealCalls := 0 - restore = boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore = boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ // bootloader variables have already been modified c.Check(mtbl.SetBootVarsCalls, Equals, 1) @@ -375,7 +375,7 @@ func (s *systemsSuite) TestSetTryRecoverySystemSimple(c *C) { } c.Assert(modeenv.WriteTo(""), IsNil) - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { return fmt.Errorf("unexpected call") }) s.AddCleanup(restore) @@ -416,7 +416,7 @@ func (s *systemsSuite) TestSetTryRecoverySystemSetBootVarsErr(c *C) { } c.Assert(modeenv.WriteTo(""), IsNil) - restore := boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore := boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { return fmt.Errorf("unexpected call") }) s.AddCleanup(restore) @@ -527,7 +527,7 @@ func (s *systemsSuite) TestSetTryRecoverySystemCleanupOnErrorBeforeReseal(c *C) defer restore() resealCalls := 0 - restore = boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore = boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ if cleanupTriggered { return nil @@ -636,7 +636,7 @@ func (s *systemsSuite) TestSetTryRecoverySystemCleanupOnErrorAfterReseal(c *C) { defer restore() resealCalls := 0 - restore = boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore = boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ switch resealCalls { case 1: @@ -737,7 +737,7 @@ func (s *systemsSuite) TestSetTryRecoverySystemCleanupError(c *C) { defer restore() resealCalls := 0 - restore = boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore = boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ switch resealCalls { case 1: @@ -784,7 +784,7 @@ func (s *systemsSuite) testInspectRecoverySystemOutcomeHappy(c *C, mtbl *bootloa }) defer restore() - restore = boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore = boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { return fmt.Errorf("unexpected call") }) defer restore() @@ -980,7 +980,7 @@ func (s *systemsSuite) testClearRecoverySystem(c *C, mtbl *bootloadertest.MockTr defer restore() resealCalls := 0 - restore = boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore = boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ switch resealCalls { case 1: @@ -1235,7 +1235,7 @@ func (s *systemsSuite) TestClearRecoverySystemReboot(c *C) { defer restore() resealCalls := 0 - restore = boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore = boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ switch resealCalls { case 1: @@ -1365,7 +1365,7 @@ func (s *systemsSuite) testPromoteTriedRecoverySystem(c *C, systemLabel string, defer restore() resealCalls := 0 - restore = boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore = boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ switch resealCalls { case 1: @@ -1629,7 +1629,7 @@ func (s *systemsSuite) testDropRecoverySystem(c *C, systemLabel string, tc recov defer restore() resealCalls := 0 - restore = boot.MockResealKeyForBootChains(func(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + restore = boot.MockResealKeyForBootChains(func(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { resealCalls++ switch resealCalls { case 1: diff --git a/osutil/disks/disks.go b/osutil/disks/disks.go index 905a5e1dbc0..943f6eda1b6 100644 --- a/osutil/disks/disks.go +++ b/osutil/disks/disks.go @@ -20,6 +20,7 @@ package disks import ( + "errors" "fmt" "strings" ) @@ -237,3 +238,23 @@ func RegisterDeviceMapperBackResolver(name string, f func(dmUUID, dmName []byte) func unregisterDeviceMapperBackResolver(name string) { delete(deviceMapperBackResolvers, name) } + +// ErrNoDmUUID is return by DMCryptUUIDFromMountPoint when the device +// at the mount point is not a device mapper device +var ErrNoDmUUID = errors.New("device has no DM_UUID") + +// ErrMountPointNotFound is return by DMCryptUUIDFromMountPoint a path +// is not a mount point +var ErrMountPointNotFound = errors.New("cannot find mount point") + +type errMountPointNotFoundImpl struct { + path string +} + +func (e errMountPointNotFoundImpl) Error() string { + return fmt.Sprintf("cannot find mountpoint %q", e.path) +} + +func (e errMountPointNotFoundImpl) Unwrap() error { + return ErrMountPointNotFound +} diff --git a/osutil/disks/disks_linux.go b/osutil/disks/disks_linux.go index 57516ab5f92..2d0ae4ad201 100644 --- a/osutil/disks/disks_linux.go +++ b/osutil/disks/disks_linux.go @@ -610,7 +610,7 @@ func partitionPropsFromMountPoint(mountpoint string) (source string, props map[s } } if source == "" { - return "", nil, fmt.Errorf("cannot find mountpoint %q", mountpoint) + return "", nil, errMountPointNotFoundImpl{path: mountpoint} } // now we have the partition for this mountpoint, we need to tie that back @@ -976,8 +976,9 @@ func DMCryptUUIDFromMountPoint(mountpoint string) (string, error) { dmUUID, hasDmUUID := props["DM_UUID"] if !hasDmUUID { - return "", fmt.Errorf("device has no DM_UUID") + return "", ErrNoDmUUID } + match := dmUUIDRe.FindStringSubmatchIndex(dmUUID) if match == nil { return "", fmt.Errorf("value of DM_UUID is not recognized") diff --git a/osutil/disks/disks_linux_test.go b/osutil/disks/disks_linux_test.go index 11890b947ec..56580891360 100644 --- a/osutil/disks/disks_linux_test.go +++ b/osutil/disks/disks_linux_test.go @@ -1854,7 +1854,8 @@ func (s *diskSuite) TestDMCryptUUIDFromMountPointErrs(c *C) { defer restore() _, err := disks.DMCryptUUIDFromMountPoint("/run/mnt/blah") - c.Assert(err, ErrorMatches, "cannot find mountpoint \"/run/mnt/blah\"") + c.Assert(err, ErrorMatches, `cannot find mountpoint "/run/mnt/blah"`) + c.Assert(errors.Is(err, disks.ErrMountPointNotFound), Equals, true) restore = osutil.MockMountInfo(`130 30 42:1 / /run/mnt/point rw,relatime shared:54 - ext4 /dev/vda4 rw `) @@ -1872,6 +1873,7 @@ func (s *diskSuite) TestDMCryptUUIDFromMountPointErrs(c *C) { _, err = disks.DMCryptUUIDFromMountPoint("/run/mnt/point") c.Assert(err, ErrorMatches, "device has no DM_UUID") + c.Assert(err, Equals, disks.ErrNoDmUUID) restore = disks.MockUdevPropertiesForDevice(func(typeOpt, dev string) (map[string]string, error) { c.Assert(typeOpt, Equals, "--name") diff --git a/overlord/fdestate/backend/reseal.go b/overlord/fdestate/backend/reseal.go index 7387d9acfb7..7c688f5f132 100644 --- a/overlord/fdestate/backend/reseal.go +++ b/overlord/fdestate/backend/reseal.go @@ -34,7 +34,8 @@ import ( ) var ( - secbootResealKeys = secboot.ResealKeys + secbootResealKeys = secboot.ResealKeys + secbootBuildPCRProtectionProfile = secboot.BuildPCRProtectionProfile ) // MockSecbootResealKeys is only useful in testing. Note that this is a very low @@ -48,8 +49,19 @@ func MockSecbootResealKeys(f func(params *secboot.ResealKeysParams) error) (rest } } +func MockSecbootBuildPCRProtectionProfile(f func(modelParams []*secboot.SealKeyModelParams) (secboot.SerializedPCRProfile, error)) (restore func()) { + osutil.MustBeTestBinary("secbootBuildPCRProtectionProfile only can be mocked in tests") + old := secbootBuildPCRProtectionProfile + secbootBuildPCRProtectionProfile = f + return func() { + secbootBuildPCRProtectionProfile = old + } +} + +type stateUpdater func(role string, containerRole string, bootModes []string, models []secboot.ModelForSealing, tpmPCRProfile []byte) error + // ResealKeyForBootChains reseals disk encryption keys with the given bootchains. -func ResealKeyForBootChains(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { +func ResealKeyForBootChains(updateState stateUpdater, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { switch method { case device.SealingMethodFDESetupHook: // FIXME: do something @@ -73,9 +85,10 @@ func ResealKeyForBootChains(method device.SealingMethod, rootdir string, params pbcJSON, _ := json.Marshal(pbc) logger.Debugf("resealing (%d) to boot chains: %s", nextCount, pbcJSON) - if err := resealRunObjectKeys(pbc, authKeyFile, params.RoleToBlName); err != nil { + if err := resealRunObjectKeys(updateState, pbc, authKeyFile, params.RoleToBlName); err != nil { return err } + logger.Debugf("resealing (%d) succeeded", nextCount) bootChainsPath := BootChainsFileUnder(rootdir) @@ -98,7 +111,7 @@ func ResealKeyForBootChains(method device.SealingMethod, rootdir string, params rpbcJSON, _ := json.Marshal(rpbc) logger.Debugf("resealing (%d) to recovery boot chains: %s", nextFallbackCount, rpbcJSON) - if err := resealFallbackObjectKeys(rpbc, authKeyFile, params.RoleToBlName); err != nil { + if err := resealFallbackObjectKeys(updateState, rpbc, authKeyFile, params.RoleToBlName); err != nil { return err } logger.Debugf("fallback resealing (%d) succeeded", nextFallbackCount) @@ -114,18 +127,33 @@ func ResealKeyForBootChains(method device.SealingMethod, rootdir string, params return nil } -func resealRunObjectKeys(pbc boot.PredictableBootChains, authKeyFile string, roleToBlName map[bootloader.Role]string) error { +func resealRunObjectKeys(updateState stateUpdater, pbc boot.PredictableBootChains, authKeyFile string, roleToBlName map[bootloader.Role]string) error { // get model parameters from bootchains modelParams, err := boot.SealKeyModelParams(pbc, roleToBlName) if err != nil { return fmt.Errorf("cannot prepare for key resealing: %v", err) } + numModels := len(modelParams) + if numModels < 1 { + return fmt.Errorf("at least one set of model-specific parameters is required") + } + + pcrProfile, err := secbootBuildPCRProtectionProfile(modelParams) + if err != nil { + return err + } + + var models []secboot.ModelForSealing + for _, m := range modelParams { + models = append(models, m.Model) + } + // list all the key files to reseal keyFiles := []string{device.DataSealedKeyUnder(boot.InitramfsBootEncryptionKeyDir)} resealKeyParams := &secboot.ResealKeysParams{ - ModelParams: modelParams, + PCRProfile: pcrProfile, KeyFiles: keyFiles, TPMPolicyAuthKeyFile: authKeyFile, } @@ -133,16 +161,36 @@ func resealRunObjectKeys(pbc boot.PredictableBootChains, authKeyFile string, rol return fmt.Errorf("cannot reseal the encryption key: %v", err) } + // TODO: use constants for "run+recover" and "all" + if err := updateState("run+recover", "all", []string{"run", "recover"}, models, pcrProfile); err != nil { + return err + } + return nil } -func resealFallbackObjectKeys(pbc boot.PredictableBootChains, authKeyFile string, roleToBlName map[bootloader.Role]string) error { +func resealFallbackObjectKeys(updateState stateUpdater, pbc boot.PredictableBootChains, authKeyFile string, roleToBlName map[bootloader.Role]string) error { // get model parameters from bootchains modelParams, err := boot.SealKeyModelParams(pbc, roleToBlName) if err != nil { return fmt.Errorf("cannot prepare for fallback key resealing: %v", err) } + numModels := len(modelParams) + if numModels < 1 { + return fmt.Errorf("at least one set of model-specific parameters is required") + } + + pcrProfile, err := secbootBuildPCRProtectionProfile(modelParams) + if err != nil { + return err + } + + var models []secboot.ModelForSealing + for _, m := range modelParams { + models = append(models, m.Model) + } + // list all the key files to reseal keyFiles := []string{ device.FallbackDataSealedKeyUnder(boot.InitramfsSeedEncryptionKeyDir), @@ -150,7 +198,7 @@ func resealFallbackObjectKeys(pbc boot.PredictableBootChains, authKeyFile string } resealKeyParams := &secboot.ResealKeysParams{ - ModelParams: modelParams, + PCRProfile: pcrProfile, KeyFiles: keyFiles, TPMPolicyAuthKeyFile: authKeyFile, } @@ -158,5 +206,10 @@ func resealFallbackObjectKeys(pbc boot.PredictableBootChains, authKeyFile string return fmt.Errorf("cannot reseal the fallback encryption keys: %v", err) } + // TODO: use constants for "recover" (the first parameter) and "system-save" + if err := updateState("recover", "system-save", []string{"recover", "factory-reset"}, models, pcrProfile); err != nil { + return err + } + return nil } diff --git a/overlord/fdestate/backend/reseal_test.go b/overlord/fdestate/backend/reseal_test.go index ec18d908104..75710fedcb1 100644 --- a/overlord/fdestate/backend/reseal_test.go +++ b/overlord/fdestate/backend/reseal_test.go @@ -266,21 +266,15 @@ func (s *resealTestSuite) TestTPMResealHappy(c *C) { }, } - resealCalls := 0 - restore := fdeBackend.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { - resealCalls++ - - c.Check(params.TPMPolicyAuthKeyFile, Equals, filepath.Join(dirs.SnapSaveDir, "device/fde", "tpm-policy-auth-key")) + buildProfileCalls := 0 + restore := fdeBackend.MockSecbootBuildPCRProtectionProfile(func(modelParams []*secboot.SealKeyModelParams) (secboot.SerializedPCRProfile, error) { + buildProfileCalls++ - c.Assert(params.ModelParams, HasLen, 1) - mp := params.ModelParams[0] + c.Assert(modelParams, HasLen, 1) + mp := modelParams[0] c.Check(mp.Model.Model(), Equals, model.Model()) - switch resealCalls { + switch buildProfileCalls { case 1: - // Resealing the run+recover key for data partition - c.Check(params.KeyFiles, DeepEquals, []string{ - filepath.Join(s.rootdir, "run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key"), - }) c.Check(mp.EFILoadChains, DeepEquals, []*secboot.LoadChain{ secboot.NewLoadChain(shimBf, secboot.NewLoadChain(assetBf, @@ -291,26 +285,49 @@ func (s *resealTestSuite) TestTPMResealHappy(c *C) { secboot.NewLoadChain(runKernel)))), }) case 2: - // Resealing the recovery key for both data and save partitions - c.Check(params.KeyFiles, DeepEquals, []string{ - filepath.Join(s.rootdir, "run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key"), - filepath.Join(s.rootdir, "run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key"), - }) c.Check(mp.EFILoadChains, DeepEquals, []*secboot.LoadChain{ secboot.NewLoadChain(shimBf, secboot.NewLoadChain(assetBf, secboot.NewLoadChain(recoveryKernel))), }) + default: + c.Errorf("unexpected additional call to secboot.BuildPCRProtectionProfile (call # %d)", buildProfileCalls) + } + return []byte(`"serialized-pcr-profile"`), nil + }) + defer restore() + + resealCalls := 0 + restore = fdeBackend.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { + resealCalls++ + + c.Check(params.TPMPolicyAuthKeyFile, Equals, filepath.Join(dirs.SnapSaveDir, "device/fde", "tpm-policy-auth-key")) + + c.Check(params.PCRProfile, DeepEquals, secboot.SerializedPCRProfile(`"serialized-pcr-profile"`)) + switch resealCalls { + case 1: + // Resealing the run+recover key for data partition + c.Check(params.KeyFiles, DeepEquals, []string{ + filepath.Join(s.rootdir, "run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key"), + }) + case 2: + // Resealing the recovery key for both data and save partitions + c.Check(params.KeyFiles, DeepEquals, []string{ + filepath.Join(s.rootdir, "run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key"), + filepath.Join(s.rootdir, "run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key"), + }) default: c.Errorf("unexpected additional call to secboot.ResealKey (call # %d)", resealCalls) } return nil }) - defer restore() + updateState := func(role string, containerRole string, bootModes []string, models []secboot.ModelForSealing, tpmPCRProfile []byte) error { + return nil + } const expectReseal = true - err := fdeBackend.ResealKeyForBootChains(device.SealingMethodTPM, s.rootdir, params, expectReseal) + err := fdeBackend.ResealKeyForBootChains(updateState, device.SealingMethodTPM, s.rootdir, params, expectReseal) c.Assert(err, IsNil) c.Check(resealCalls, Equals, 2) @@ -411,16 +428,13 @@ func (s *resealTestSuite) TestResealKeyForBootchainsWithSystemFallback(c *C) { // mock asset cache mockAssetsCache(c, rootdir, "grub", expectedCache) - // set mock key resealing - resealKeysCalls := 0 - restore := fdeBackend.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { - c.Check(params.TPMPolicyAuthKeyFile, Equals, filepath.Join(dirs.SnapSaveDir, "device/fde", "tpm-policy-auth-key")) - - resealKeysCalls++ - c.Assert(params.ModelParams, HasLen, 1) + buildProfileCalls := 0 + restore := fdeBackend.MockSecbootBuildPCRProtectionProfile(func(modelParams []*secboot.SealKeyModelParams) (secboot.SerializedPCRProfile, error) { + buildProfileCalls++ + c.Assert(modelParams, HasLen, 1) // shared parameters - c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") + c.Assert(modelParams[0].Model.Model(), Equals, "my-model-uc20") // recovery parameters shim := bootloader.NewBootFile("", filepath.Join(rootdir, fmt.Sprintf("var/lib/snapd/boot-assets/grub/%s-shim-hash-1", shimId)), bootloader.RoleRecovery) @@ -513,33 +527,70 @@ func (s *resealTestSuite) TestResealKeyForBootchainsWithSystemFallback(c *C) { } checkRunParams := func() { - c.Check(params.KeyFiles, DeepEquals, []string{ - filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), - }) - c.Check(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ + c.Check(modelParams[0].KernelCmdlines, DeepEquals, []string{ "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", }) for _, chain := range possibleChains { - c.Check(params.ModelParams[0].EFILoadChains, ContainsChain, chain) + c.Check(modelParams[0].EFILoadChains, ContainsChain, chain) } for _, chain := range possibleRecoveryChains { - c.Check(params.ModelParams[0].EFILoadChains, ContainsChain, chain) + c.Check(modelParams[0].EFILoadChains, ContainsChain, chain) } } checkRecoveryParams := func() { - c.Check(params.KeyFiles, DeepEquals, []string{ - filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), - filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), - }) - c.Check(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ + c.Check(modelParams[0].KernelCmdlines, DeepEquals, []string{ "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", }) for _, chain := range possibleRecoveryChains { - c.Check(params.ModelParams[0].EFILoadChains, ContainsChain, chain) + c.Check(modelParams[0].EFILoadChains, ContainsChain, chain) + } + } + + switch buildProfileCalls { + case 1: + if !tc.reuseRunPbc { + checkRunParams() + } else if !tc.reuseRecoveryPbc { + checkRecoveryParams() + } else { + c.Errorf("unexpected call to secboot.BuildPCRProtectionProfile (call # %d)", buildProfileCalls) + } + case 2: + if !tc.reuseRecoveryPbc { + checkRecoveryParams() + } else { + c.Errorf("unexpected call to secboot.BuildPCRProtectionProfile (call # %d)", buildProfileCalls) } + default: + c.Errorf("unexpected additional call to secboot.BuildPCRProtectionProfile (call # %d)", buildProfileCalls) + } + + return []byte(`"serialized-pcr-profile"`), nil + }) + defer restore() + + // set mock key resealing + resealKeysCalls := 0 + restore = fdeBackend.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { + c.Check(params.TPMPolicyAuthKeyFile, Equals, filepath.Join(dirs.SnapSaveDir, "device/fde", "tpm-policy-auth-key")) + + resealKeysCalls++ + c.Check(params.PCRProfile, DeepEquals, secboot.SerializedPCRProfile(`"serialized-pcr-profile"`)) + + checkRunParams := func() { + c.Check(params.KeyFiles, DeepEquals, []string{ + filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), + }) + } + + checkRecoveryParams := func() { + c.Check(params.KeyFiles, DeepEquals, []string{ + filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), + filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), + }) } switch resealKeysCalls { @@ -830,8 +881,11 @@ func (s *resealTestSuite) TestResealKeyForBootchainsWithSystemFallback(c *C) { }, } + updateState := func(role string, containerRole string, bootModes []string, models []secboot.ModelForSealing, tpmPCRProfile []byte) error { + return nil + } const expectReseal = false - err := fdeBackend.ResealKeyForBootChains(device.SealingMethodTPM, rootdir, params, expectReseal) + err := fdeBackend.ResealKeyForBootChains(updateState, device.SealingMethodTPM, rootdir, params, expectReseal) if tc.reuseRunPbc && tc.reuseRecoveryPbc { // did nothing c.Assert(err, IsNil) @@ -889,42 +943,32 @@ func (s *resealTestSuite) TestResealKeyForBootchainsRecoveryKeysForGoodSystemsOn "grubx64.efi-run-grub-hash", }) - // set mock key resealing - resealKeysCalls := 0 - restore := fdeBackend.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { - c.Check(params.TPMPolicyAuthKeyFile, Equals, filepath.Join(dirs.SnapSaveDir, "device/fde", "tpm-policy-auth-key")) - - resealKeysCalls++ - c.Assert(params.ModelParams, HasLen, 1) + buildProfileCalls := 0 + restore := fdeBackend.MockSecbootBuildPCRProtectionProfile(func(modelParams []*secboot.SealKeyModelParams) (secboot.SerializedPCRProfile, error) { + buildProfileCalls++ // shared parameters - c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") - switch resealKeysCalls { + c.Assert(modelParams[0].Model.Model(), Equals, "my-model-uc20") + + switch buildProfileCalls { case 1: // run key - c.Assert(params.KeyFiles, DeepEquals, []string{ - filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), - }) - c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ + c.Assert(modelParams[0].KernelCmdlines, DeepEquals, []string{ "snapd_recovery_mode=factory-reset snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", "snapd_recovery_mode=recover snapd_recovery_system=1234 console=ttyS0 console=tty1 panic=-1", "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", }) // load chains - c.Assert(params.ModelParams[0].EFILoadChains, HasLen, 3) + c.Assert(modelParams[0].EFILoadChains, HasLen, 3) case 2: // recovery keys - c.Assert(params.KeyFiles, DeepEquals, []string{ - filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), - filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), - }) - c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ + c.Assert(modelParams[0].KernelCmdlines, DeepEquals, []string{ "snapd_recovery_mode=factory-reset snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", }) // load chains - c.Assert(params.ModelParams[0].EFILoadChains, HasLen, 1) + c.Assert(modelParams[0].EFILoadChains, HasLen, 1) default: - c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls) + c.Errorf("unexpected additional call to secboot.BuildPCRProtectionProfile (call # %d)", buildProfileCalls) } // recovery parameters @@ -937,9 +981,9 @@ func (s *resealTestSuite) TestResealKeyForBootchainsRecoveryKeysForGoodSystemsOn runGrub := bootloader.NewBootFile("", filepath.Join(s.rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-run-grub-hash"), bootloader.RoleRunMode) runKernel := bootloader.NewBootFile(filepath.Join(s.rootdir, "var/lib/snapd/snaps/pc-kernel_500.snap"), "kernel.efi", bootloader.RoleRunMode) - switch resealKeysCalls { + switch buildProfileCalls { case 1: // run load chain - c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ + c.Assert(modelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(kernelGoodRecovery), @@ -955,7 +999,7 @@ func (s *resealTestSuite) TestResealKeyForBootchainsRecoveryKeysForGoodSystemsOn )), }) case 2: // recovery load chains - c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ + c.Assert(modelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(kernelGoodRecovery), @@ -963,6 +1007,32 @@ func (s *resealTestSuite) TestResealKeyForBootchainsRecoveryKeysForGoodSystemsOn }) } + return []byte(`"serialized-pcr-profile"`), nil + }) + defer restore() + + // set mock key resealing + resealKeysCalls := 0 + restore = fdeBackend.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { + c.Check(params.TPMPolicyAuthKeyFile, Equals, filepath.Join(dirs.SnapSaveDir, "device/fde", "tpm-policy-auth-key")) + + resealKeysCalls++ + c.Check(params.PCRProfile, DeepEquals, secboot.SerializedPCRProfile(`"serialized-pcr-profile"`)) + + switch resealKeysCalls { + case 1: // run key + c.Assert(params.KeyFiles, DeepEquals, []string{ + filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), + }) + case 2: // recovery keys + c.Assert(params.KeyFiles, DeepEquals, []string{ + filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), + filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), + }) + default: + c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls) + } + return nil }) defer restore() @@ -1094,8 +1164,11 @@ func (s *resealTestSuite) TestResealKeyForBootchainsRecoveryKeysForGoodSystemsOn }, } + updateState := func(role string, containerRole string, bootModes []string, models []secboot.ModelForSealing, tpmPCRProfile []byte) error { + return nil + } const expectReseal = false - err := fdeBackend.ResealKeyForBootChains(device.SealingMethodTPM, s.rootdir, params, expectReseal) + err := fdeBackend.ResealKeyForBootChains(updateState, device.SealingMethodTPM, s.rootdir, params, expectReseal) c.Assert(err, IsNil) c.Assert(resealKeysCalls, Equals, 2) @@ -1119,48 +1192,38 @@ func (s *resealTestSuite) testResealKeyForBootchainsWithTryModel(c *C, shimId, g "grubx64.efi-run-grub-hash", }) - // set mock key resealing - resealKeysCalls := 0 - restore := fdeBackend.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { - c.Check(params.TPMPolicyAuthKeyFile, Equals, filepath.Join(dirs.SnapSaveDir, "device/fde", "tpm-policy-auth-key")) - - resealKeysCalls++ + buildProfileCalls := 0 + restore := fdeBackend.MockSecbootBuildPCRProtectionProfile(func(modelParams []*secboot.SealKeyModelParams) (secboot.SerializedPCRProfile, error) { + buildProfileCalls++ - switch resealKeysCalls { + switch buildProfileCalls { case 1: // run key - c.Assert(params.KeyFiles, DeepEquals, []string{ - filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), - }) // 2 models, one current and one try model - c.Assert(params.ModelParams, HasLen, 2) + c.Assert(modelParams, HasLen, 2) // shared parameters - c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") - c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ + c.Assert(modelParams[0].Model.Model(), Equals, "my-model-uc20") + c.Assert(modelParams[0].KernelCmdlines, DeepEquals, []string{ "snapd_recovery_mode=factory-reset snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", }) // 2 load chains (bootloader + run kernel, bootloader + recovery kernel) - c.Assert(params.ModelParams[0].EFILoadChains, HasLen, 2) + c.Assert(modelParams[0].EFILoadChains, HasLen, 2) - c.Assert(params.ModelParams[1].Model.Model(), Equals, "try-my-model-uc20") - c.Assert(params.ModelParams[1].KernelCmdlines, DeepEquals, []string{ + c.Assert(modelParams[1].Model.Model(), Equals, "try-my-model-uc20") + c.Assert(modelParams[1].KernelCmdlines, DeepEquals, []string{ "snapd_recovery_mode=factory-reset snapd_recovery_system=1234 console=ttyS0 console=tty1 panic=-1", "snapd_recovery_mode=recover snapd_recovery_system=1234 console=ttyS0 console=tty1 panic=-1", "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", }) // 2 load chains (bootloader + run kernel, bootloader + recovery kernel) - c.Assert(params.ModelParams[1].EFILoadChains, HasLen, 2) + c.Assert(modelParams[1].EFILoadChains, HasLen, 2) case 2: // recovery keys - c.Assert(params.KeyFiles, DeepEquals, []string{ - filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), - filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), - }) // only the current model - c.Assert(params.ModelParams, HasLen, 1) + c.Assert(modelParams, HasLen, 1) // shared parameters - c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") - for _, mp := range params.ModelParams { + c.Assert(modelParams[0].Model.Model(), Equals, "my-model-uc20") + for _, mp := range modelParams { c.Assert(mp.KernelCmdlines, DeepEquals, []string{ "snapd_recovery_mode=factory-reset snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", @@ -1169,7 +1232,7 @@ func (s *resealTestSuite) testResealKeyForBootchainsWithTryModel(c *C, shimId, g c.Assert(mp.EFILoadChains, HasLen, 1) } default: - c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls) + c.Errorf("unexpected additional call to secboot.BuildPCRProtectionProfile (call # %d)", buildProfileCalls) } // recovery parameters @@ -1183,13 +1246,13 @@ func (s *resealTestSuite) testResealKeyForBootchainsWithTryModel(c *C, shimId, g runKernel := bootloader.NewBootFile(filepath.Join(s.rootdir, "var/lib/snapd/snaps/pc-kernel_500.snap"), "kernel.efi", bootloader.RoleRunMode) // verify the load chains, which are identical for both models - switch resealKeysCalls { + switch buildProfileCalls { case 1: // run load chain for 2 models, current and a try model - c.Assert(params.ModelParams, HasLen, 2) + c.Assert(modelParams, HasLen, 2) // each load chain has either the run kernel (shared for // both), or the kernel of the respective recovery // system - c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ + c.Assert(modelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(kernelOldRecovery), @@ -1200,7 +1263,7 @@ func (s *resealTestSuite) testResealKeyForBootchainsWithTryModel(c *C, shimId, g secboot.NewLoadChain(runKernel)), )), }) - c.Assert(params.ModelParams[1].EFILoadChains, DeepEquals, []*secboot.LoadChain{ + c.Assert(modelParams[1].EFILoadChains, DeepEquals, []*secboot.LoadChain{ secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(kernelNewRecovery), @@ -1212,10 +1275,10 @@ func (s *resealTestSuite) testResealKeyForBootchainsWithTryModel(c *C, shimId, g )), }) case 2: // recovery load chains, only for the current model - c.Assert(params.ModelParams, HasLen, 1) + c.Assert(modelParams, HasLen, 1) // load chain with a kernel from a recovery system that // matches the current model only - c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ + c.Assert(modelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(kernelOldRecovery), @@ -1223,6 +1286,32 @@ func (s *resealTestSuite) testResealKeyForBootchainsWithTryModel(c *C, shimId, g }) } + return []byte(`"serialized-pcr-profile"`), nil + }) + defer restore() + + // set mock key resealing + resealKeysCalls := 0 + restore = fdeBackend.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { + c.Check(params.TPMPolicyAuthKeyFile, Equals, filepath.Join(dirs.SnapSaveDir, "device/fde", "tpm-policy-auth-key")) + + resealKeysCalls++ + c.Check(params.PCRProfile, DeepEquals, secboot.SerializedPCRProfile(`"serialized-pcr-profile"`)) + + switch resealKeysCalls { + case 1: // run key + c.Assert(params.KeyFiles, DeepEquals, []string{ + filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), + }) + case 2: // recovery keys + c.Assert(params.KeyFiles, DeepEquals, []string{ + filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), + filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), + }) + default: + c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls) + } + return nil }) defer restore() @@ -1344,8 +1433,11 @@ func (s *resealTestSuite) testResealKeyForBootchainsWithTryModel(c *C, shimId, g }, } + updateState := func(role string, containerRole string, bootModes []string, models []secboot.ModelForSealing, tpmPCRProfile []byte) error { + return nil + } const expectReseal = false - err := fdeBackend.ResealKeyForBootChains(device.SealingMethodTPM, s.rootdir, params, expectReseal) + err := fdeBackend.ResealKeyForBootChains(updateState, device.SealingMethodTPM, s.rootdir, params, expectReseal) c.Assert(err, IsNil) c.Assert(resealKeysCalls, Equals, 2) @@ -1397,22 +1489,40 @@ func (s *resealTestSuite) TestResealKeyForBootchainsFallbackCmdline(c *C) { bootloader.Force(mtbl) defer bootloader.Force(nil) - // set mock key resealing - resealKeysCalls := 0 - restore := fdeBackend.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { - resealKeysCalls++ - c.Assert(params.ModelParams, HasLen, 1) - c.Logf("reseal: %+v", params) - switch resealKeysCalls { + buildProfileCalls := 0 + restore := fdeBackend.MockSecbootBuildPCRProtectionProfile(func(modelParams []*secboot.SealKeyModelParams) (secboot.SerializedPCRProfile, error) { + buildProfileCalls++ + + c.Assert(modelParams, HasLen, 1) + + switch buildProfileCalls { case 1: - c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ + c.Assert(modelParams[0].KernelCmdlines, DeepEquals, []string{ "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", "snapd_recovery_mode=run static cmdline", }) case 2: - c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ + c.Assert(modelParams[0].KernelCmdlines, DeepEquals, []string{ "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", }) + default: + c.Fatalf("unexpected number of build profile calls, %v", modelParams) + } + + return []byte(`"serialized-pcr-profile"`), nil + }) + defer restore() + + // set mock key resealing + resealKeysCalls := 0 + restore = fdeBackend.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { + resealKeysCalls++ + + c.Check(params.PCRProfile, DeepEquals, secboot.SerializedPCRProfile(`"serialized-pcr-profile"`)) + c.Logf("reseal: %+v", params) + switch resealKeysCalls { + case 1: + case 2: default: c.Fatalf("unexpected number of reseal calls, %v", params) } @@ -1496,8 +1606,11 @@ func (s *resealTestSuite) TestResealKeyForBootchainsFallbackCmdline(c *C) { }, } + updateState := func(role string, containerRole string, bootModes []string, models []secboot.ModelForSealing, tpmPCRProfile []byte) error { + return nil + } const expectReseal = false - err = fdeBackend.ResealKeyForBootChains(device.SealingMethodTPM, s.rootdir, params, expectReseal) + err = fdeBackend.ResealKeyForBootChains(updateState, device.SealingMethodTPM, s.rootdir, params, expectReseal) c.Assert(err, IsNil) c.Assert(resealKeysCalls, Equals, 2) diff --git a/overlord/fdestate/export_test.go b/overlord/fdestate/export_test.go index fa91b2c3cba..ae6e884b402 100644 --- a/overlord/fdestate/export_test.go +++ b/overlord/fdestate/export_test.go @@ -20,3 +20,5 @@ package fdestate var FdeMgr = fdeMgr + +var UpdateParameters = updateParameters diff --git a/overlord/fdestate/fdemgr.go b/overlord/fdestate/fdemgr.go index c97680973db..ec477f6d4f4 100644 --- a/overlord/fdestate/fdemgr.go +++ b/overlord/fdestate/fdemgr.go @@ -26,6 +26,7 @@ import ( "github.com/snapcore/snapd/gadget/device" "github.com/snapcore/snapd/overlord/fdestate/backend" "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/secboot" ) // FDEManager is responsible for managing full disk encryption keys. @@ -49,12 +50,31 @@ func Manager(st *state.State, runner *state.TaskRunner) *FDEManager { return m } +// Ensure implements StateManager.Ensure func (m *FDEManager) Ensure() error { return nil } -func (m *FDEManager) resealKeyForBootChains(method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { - return backend.ResealKeyForBootChains(method, rootdir, params, expectReseal) +// StartUp implements StateStarterUp.Startup +func (m *FDEManager) StartUp() error { + m.state.Lock() + defer m.state.Unlock() + return initializeState(m.state) +} + +func (m *FDEManager) resealKeyForBootChains(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error { + doUpdate := func(role string, containerRole string, bootModes []string, models []secboot.ModelForSealing, tpmPCRProfile []byte) error { + if unlocker != nil { + m.state.Lock() + defer m.state.Unlock() + } + return updateParameters(m.state, role, containerRole, bootModes, models, tpmPCRProfile) + } + if unlocker != nil { + locker := unlocker() + defer locker() + } + return backend.ResealKeyForBootChains(doUpdate, method, rootdir, params, expectReseal) } func fdeMgr(st *state.State) *FDEManager { diff --git a/overlord/fdestate/fdemgr_test.go b/overlord/fdestate/fdemgr_test.go index 3f53e2b17ce..cb90cf1a516 100644 --- a/overlord/fdestate/fdemgr_test.go +++ b/overlord/fdestate/fdemgr_test.go @@ -20,12 +20,17 @@ package fdestate_test import ( + "crypto" + "fmt" "testing" . "gopkg.in/check.v1" + "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/overlord/fdestate" "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/secboot" ) func TestFDE(t *testing.T) { TestingT(t) } @@ -37,11 +42,126 @@ var _ = Suite(&fdeMgrSuite{}) func (s *fdeMgrSuite) TestGetManagerFromState(c *C) { st := state.New(nil) + defer fdestate.MockDMCryptUUIDFromMountPoint(func(mountpoint string) (string, error) { + switch mountpoint { + case dirs.GlobalRootDir: + return "aaa", nil + case dirs.SnapSaveDir: + return "bbb", nil + } + return "", fmt.Errorf("unknown mount point") + })() + + defer fdestate.MockGetPrimaryKeyHash(func(devicePath string, alg crypto.Hash) ([]byte, []byte, error) { + c.Check(devicePath, Equals, "/dev/disk/by-uuid/aaa") + c.Check(alg, Equals, crypto.Hash(crypto.SHA256)) + return []byte{1, 2, 3, 4}, []byte{5, 6, 7, 8}, nil + })() + + defer fdestate.MockVerifyPrimaryKeyHash(func(devicePath string, alg crypto.Hash, salt, digest []byte) (bool, error) { + c.Check(devicePath, Equals, "/dev/disk/by-uuid/bbb") + c.Check(alg, Equals, crypto.Hash(crypto.SHA256)) + c.Check(salt, DeepEquals, []byte{1, 2, 3, 4}) + c.Check(digest, DeepEquals, []byte{5, 6, 7, 8}) + return true, nil + })() + runner := state.NewTaskRunner(st) manager := fdestate.Manager(st, runner) - c.Assert(manager.Ensure(), IsNil) + c.Assert(manager.StartUp(), IsNil) st.Lock() defer st.Unlock() foundManager := fdestate.FdeMgr(st) c.Check(foundManager, Equals, manager) + + var fdeSt fdestate.FdeState + err := st.Get("fde", &fdeSt) + c.Assert(err, IsNil) + primaryKey, hasPrimaryKey := fdeSt.PrimaryKeys[0] + c.Assert(hasPrimaryKey, Equals, true) + c.Check(crypto.Hash(primaryKey.Digest.Algorithm), Equals, crypto.Hash(crypto.SHA256)) + c.Check(primaryKey.Digest.Salt, DeepEquals, []byte{1, 2, 3, 4}) + c.Check(primaryKey.Digest.Digest, DeepEquals, []byte{5, 6, 7, 8}) +} + +type mockModel struct { +} + +func (m *mockModel) Series() string { + return "mock-series" +} + +func (m *mockModel) BrandID() string { + return "mock-brand" +} + +func (m *mockModel) Model() string { + return "mock-model" +} + +func (m *mockModel) Classic() bool { + return false +} + +func (m *mockModel) Grade() asserts.ModelGrade { + return asserts.ModelSigned +} + +func (m *mockModel) SignKeyID() string { + return "mock-key" +} + +func (s *fdeMgrSuite) TestUpdateState(c *C) { + st := state.New(nil) + + defer fdestate.MockDMCryptUUIDFromMountPoint(func(mountpoint string) (string, error) { + switch mountpoint { + case dirs.GlobalRootDir: + return "aaa", nil + case dirs.SnapSaveDir: + return "bbb", nil + } + return "", fmt.Errorf("unknown mount point") + })() + + defer fdestate.MockGetPrimaryKeyHash(func(devicePath string, alg crypto.Hash) ([]byte, []byte, error) { + c.Check(devicePath, Equals, "/dev/disk/by-uuid/aaa") + c.Check(alg, Equals, crypto.Hash(crypto.SHA256)) + return []byte{1, 2, 3, 4}, []byte{5, 6, 7, 8}, nil + })() + + defer fdestate.MockVerifyPrimaryKeyHash(func(devicePath string, alg crypto.Hash, salt, digest []byte) (bool, error) { + c.Check(devicePath, Equals, "/dev/disk/by-uuid/bbb") + c.Check(alg, Equals, crypto.Hash(crypto.SHA256)) + c.Check(salt, DeepEquals, []byte{1, 2, 3, 4}) + c.Check(digest, DeepEquals, []byte{5, 6, 7, 8}) + return true, nil + })() + + runner := state.NewTaskRunner(st) + manager := fdestate.Manager(st, runner) + c.Assert(manager.StartUp(), IsNil) + st.Lock() + defer st.Unlock() + foundManager := fdestate.FdeMgr(st) + c.Check(foundManager, Equals, manager) + + models := []secboot.ModelForSealing{ + &mockModel{}, + } + + fdestate.UpdateParameters(st, "run+recover", "container-role", []string{"run"}, models, secboot.SerializedPCRProfile(`"serialized-profile"`)) + + var fdeSt fdestate.FdeState + err := st.Get("fde", &fdeSt) + c.Assert(err, IsNil) + runRecoverRole, hasRunRecoverRole := fdeSt.KeyslotRoles["run+recover"] + c.Assert(hasRunRecoverRole, Equals, true) + containerRole, hasContainerRole := runRecoverRole.Parameters["container-role"] + c.Assert(hasContainerRole, Equals, true) + + c.Assert(containerRole.Models, HasLen, 1) + c.Check(containerRole.Models[0].Model(), Equals, "mock-model") + c.Check(containerRole.BootModes, DeepEquals, []string{"run"}) + c.Check(containerRole.TPM2PCRProfile, DeepEquals, secboot.SerializedPCRProfile(`"serialized-profile"`)) } diff --git a/overlord/fdestate/fdestate.go b/overlord/fdestate/fdestate.go index c67ae71e11b..43929e8c3f8 100644 --- a/overlord/fdestate/fdestate.go +++ b/overlord/fdestate/fdestate.go @@ -19,15 +19,28 @@ package fdestate import ( + "crypto" + "encoding/json" "errors" + "fmt" + "github.com/snapcore/snapd/asserts" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/gadget/device" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/osutil/disks" "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/secboot" ) var errNotImplemented = errors.New("not implemented") +var ( + disksDMCryptUUIDFromMountPoint = disks.DMCryptUUIDFromMountPoint + secbootGetPrimaryKeyHash = secboot.GetPrimaryKeyHash + secbootVerifyPrimaryKeyHash = secboot.VerifyPrimaryKeyHash +) + // EFISecureBootDBManagerStartup indicates that the local EFI key database // manager has started. func EFISecureBootDBManagerStartup(st *state.State) error { @@ -66,3 +79,289 @@ func EFISecureBootDBUpdateCleanup(st *state.State) error { return errNotImplemented } + +// Model is a json serializable secboot.ModelForSealing +type Model struct { + SeriesValue string `json:"series"` + BrandIDValue string `json:"brand-id"` + ModelValue string `json:"model"` + ClassicValue bool `json:"classic"` + GradeValue asserts.ModelGrade `json:"grade"` + SignKeyIDValue string `json:"sign-key-id"` +} + +// Series implements secboot.ModelForSealing.Series +func (m *Model) Series() string { + return m.SeriesValue +} + +// BrandID implements secboot.ModelForSealing.BrandID +func (m *Model) BrandID() string { + return m.BrandIDValue +} + +// Model implements secboot.ModelForSealing.Model +func (m *Model) Model() string { + return m.ModelValue +} + +// Classic implements secboot.ModelForSealing.Classic +func (m *Model) Classic() bool { + return m.ClassicValue +} + +// Grade implements secboot.ModelForSealing.Grade +func (m *Model) Grade() asserts.ModelGrade { + return m.GradeValue +} + +// SignKeyID implements secboot.ModelForSealing.SignKeyID +func (m *Model) SignKeyID() string { + return m.SignKeyIDValue +} + +func newModel(m secboot.ModelForSealing) Model { + return Model{ + SeriesValue: m.Series(), + BrandIDValue: m.BrandID(), + ModelValue: m.Model(), + ClassicValue: m.Classic(), + GradeValue: m.Grade(), + SignKeyIDValue: m.SignKeyID(), + } +} + +var _ secboot.ModelForSealing = (*Model)(nil) + +// KeyslotRoleParameters stores upgradeable parameters for a keyslot role +type KeyslotRoleParameters struct { + // Models are the optional list of approved models + Models []Model `json:"models,omitempty"` + // BootModes are the optional list of approved modes (run, recover, ...) + BootModes []string `json:"boot-modes,omitempty"` + // TPM2PCRProfile is an optional serialized PCR profile + TPM2PCRProfile secboot.SerializedPCRProfile `json:"tpm2-pcr-profile,omitempty"` +} + +// KeyslotRoleInfo stores information about a key slot role +type KeyslotRoleInfo struct { + // PrimaryKeyID is the ID for the primary key found in + // PrimaryKeys field of FdeState + PrimaryKeyID int `json:"primary-key-id"` + // Parameters is indexed by container role name + Parameters map[string]KeyslotRoleParameters `json:"params,omitempty"` + // TPM2PCRPolicyRevocationCounter is the handle for the TPM + // policy revocation counter. A value of 0 means it is not + // set. + TPM2PCRPolicyRevocationCounter uint32 `json:"tpm2-pcr-policy-revocation-counter,omitempty"` +} + +type hashAlg crypto.Hash + +// UnmarshalJSON implements json.Unmarshaler.UnmarshalJSON +func (h *hashAlg) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + switch s { + case "sha256": + *h = hashAlg(crypto.SHA256) + default: + return fmt.Errorf("Unknown algorithm %s", s) + } + + return nil +} + +// MarshalJSON implements json.Marshaler.MarshalJSON +func (h hashAlg) MarshalJSON() ([]byte, error) { + switch crypto.Hash(h) { + case crypto.SHA256: + return json.Marshal("sha256") + default: + return nil, fmt.Errorf("Unknown algorithm %v", h) + } +} + +// KeyDigest stores a HMAC(key, salt) of a key +type KeyDigest struct { + // Algorithm is the algorithm for + Algorithm hashAlg `json:"alg"` + // Salt is the salt for the HMAC digest + Salt []byte `json:"salt"` + // Digest is the result of `HMAC(key, salt)` + Digest []byte `json:"digest"` +} + +const defaultHashAlg = crypto.SHA256 + +func getPrimaryKeyDigest(devicePath string) (KeyDigest, error) { + salt, digest, err := secbootGetPrimaryKeyHash(devicePath, crypto.Hash(defaultHashAlg)) + if err != nil { + return KeyDigest{}, err + } + + return KeyDigest{ + Algorithm: hashAlg(defaultHashAlg), + Salt: salt, + Digest: digest, + }, nil +} + +func (kd *KeyDigest) verifyPrimaryKeyDigest(devicePath string) (bool, error) { + return secbootVerifyPrimaryKeyHash(devicePath, crypto.Hash(kd.Algorithm), kd.Salt, kd.Digest) +} + +// PrimaryKeyInfo provides information about a primary key that is used to manage key slots +type PrimaryKeyInfo struct { + Digest KeyDigest `json:"digest"` +} + +// FdeState is the root persistent FDE state +type FdeState struct { + // PrimaryKeys are the keys on the system. Key with ID 0 is + // reserved for snapd and is populated on first boot. Other + // IDs are for externally managed keys. + PrimaryKeys map[int]PrimaryKeyInfo `json:"primary-keys"` + + // KeyslotRoles are all keyslot roles indexed by the role name + KeyslotRoles map[string]KeyslotRoleInfo `json:"keyslot-roles"` +} + +const fdeStateKey = "fde" + +func initializeState(st *state.State) error { + var s FdeState + err := st.Get(fdeStateKey, &s) + if err == nil { + // TODO: Do we need to do something in recover? + return nil + } + + if !errors.Is(err, state.ErrNoState) { + return err + } + + dataUUID, dataErr := disksDMCryptUUIDFromMountPoint(dirs.GlobalRootDir) + saveUUID, saveErr := disksDMCryptUUIDFromMountPoint(dirs.SnapSaveDir) + if errors.Is(saveErr, disks.ErrMountPointNotFound) { + // TODO: do we need to care about old cases where there is no save partition? + return nil + } + + if errors.Is(dataErr, disks.ErrNoDmUUID) && errors.Is(saveErr, disks.ErrNoDmUUID) { + // There is no encryption, so we ignore it. + // TODO: we should verify the device "sealed key method" + return nil + } + if dataErr != nil { + return dataErr + } + if saveErr != nil { + return saveErr + } + + digest, err := getPrimaryKeyDigest(fmt.Sprintf("/dev/disk/by-uuid/%s", dataUUID)) + if err != nil { + return err + } + sameDigest, err := digest.verifyPrimaryKeyDigest(fmt.Sprintf("/dev/disk/by-uuid/%s", saveUUID)) + if err != nil { + return err + } + if !sameDigest { + return fmt.Errorf("primary key for data and save partition are not the same") + } + + s.PrimaryKeys = map[int]PrimaryKeyInfo{ + 0: { + Digest: digest, + }, + } + + // Note that Parameters will be updated on first update + s.KeyslotRoles = map[string]KeyslotRoleInfo{ + // TODO: use a constant + "run+recover": { + PrimaryKeyID: 0, + // FIXME: this might be + // AltRunObjectPCRPolicyCounterHandle after + // factory reset, but this is supposed to be + // removed. + TPM2PCRPolicyRevocationCounter: secboot.RunObjectPCRPolicyCounterHandle, + }, + // TODO: use a constant + "recover": { + PrimaryKeyID: 0, + TPM2PCRPolicyRevocationCounter: secboot.FallbackObjectPCRPolicyCounterHandle, + }, + } + + st.Set(fdeStateKey, s) + + return nil +} + +func updateParameters(st *state.State, role string, containerRole string, bootModes []string, models []secboot.ModelForSealing, tpmPCRProfile []byte) error { + var s FdeState + err := st.Get(fdeStateKey, &s) + if err != nil { + return err + } + + roleInfo, hasRole := s.KeyslotRoles[role] + if !hasRole { + return fmt.Errorf("Cannot find role %s", role) + } + + var convertedModels []Model + for _, model := range models { + convertedModels = append(convertedModels, newModel(model)) + } + + if roleInfo.Parameters == nil { + roleInfo.Parameters = make(map[string]KeyslotRoleParameters) + } + roleInfo.Parameters[containerRole] = KeyslotRoleParameters{ + Models: convertedModels, + BootModes: bootModes, + TPM2PCRProfile: tpmPCRProfile, + } + + s.KeyslotRoles[role] = roleInfo + + st.Set(fdeStateKey, s) + + return nil +} + +func MockDMCryptUUIDFromMountPoint(f func(mountpoint string) (string, error)) (restore func()) { + osutil.MustBeTestBinary("mocking DMCryptUUIDFromMountPoint can be done only from tests") + + old := disksDMCryptUUIDFromMountPoint + disksDMCryptUUIDFromMountPoint = f + return func() { + disksDMCryptUUIDFromMountPoint = old + } +} + +func MockGetPrimaryKeyHash(f func(devicePath string, alg crypto.Hash) ([]byte, []byte, error)) (restore func()) { + osutil.MustBeTestBinary("mocking GetPrimaryKeyHash can be done only from tests") + + old := secbootGetPrimaryKeyHash + secbootGetPrimaryKeyHash = f + return func() { + secbootGetPrimaryKeyHash = old + } +} + +func MockVerifyPrimaryKeyHash(f func(devicePath string, alg crypto.Hash, salt []byte, digest []byte) (bool, error)) (restore func()) { + osutil.MustBeTestBinary("mocking VerifyPrimaryKeyHash can be done only from tests") + + old := secbootVerifyPrimaryKeyHash + secbootVerifyPrimaryKeyHash = f + return func() { + secbootVerifyPrimaryKeyHash = old + } +} diff --git a/overlord/managers_test.go b/overlord/managers_test.go index 8ed14c9f5aa..0a4e98034da 100644 --- a/overlord/managers_test.go +++ b/overlord/managers_test.go @@ -24,6 +24,7 @@ package overlord_test import ( "bytes" "context" + "crypto" "encoding/json" "errors" "fmt" @@ -74,6 +75,7 @@ import ( "github.com/snapcore/snapd/overlord/configstate/config" "github.com/snapcore/snapd/overlord/devicestate" "github.com/snapcore/snapd/overlord/devicestate/devicestatetest" + "github.com/snapcore/snapd/overlord/fdestate" fdeBackend "github.com/snapcore/snapd/overlord/fdestate/backend" "github.com/snapcore/snapd/overlord/hookstate" "github.com/snapcore/snapd/overlord/hookstate/ctlcmd" @@ -7427,6 +7429,35 @@ func (s *mgrsSuiteCore) testRemodelUC20WithRecoverySystem(c *C, encrypted bool) mockServer := s.mockStore(c) defer mockServer.Close() + restore = fdeBackend.MockSecbootBuildPCRProtectionProfile(func(modelParams []*secboot.SealKeyModelParams) (secboot.SerializedPCRProfile, error) { + return []byte(`"some-profile"`), nil + }) + defer restore() + + restore = fdestate.MockDMCryptUUIDFromMountPoint(func(mountpoint string) (string, error) { + if mountpoint == dirs.GlobalRootDir { + return "root-uuid", nil + } else { + return "save-uuid", nil + } + }) + defer restore() + + restore = fdestate.MockGetPrimaryKeyHash(func(devicePath string, alg crypto.Hash) ([]byte, []byte, error) { + return []byte("aaaa"), []byte("bbbb"), nil + }) + defer restore() + + restore = fdestate.MockVerifyPrimaryKeyHash(func(devicePath string, alg crypto.Hash, salt []byte, digest []byte) (bool, error) { + return true, nil + }) + defer restore() + + fdemgr := s.o.FDEManager() + c.Assert(fdemgr, NotNil) + err := fdemgr.StartUp() + c.Assert(err, IsNil) + st := s.o.State() st.Lock() defer st.Unlock() @@ -7443,7 +7474,7 @@ func (s *mgrsSuiteCore) testRemodelUC20WithRecoverySystem(c *C, encrypted bool) s.serveSnap(snapPath, "22") } - err := assertstate.Add(st, s.devAcct) + err = assertstate.Add(st, s.devAcct) c.Assert(err, IsNil) // snaps in state pcInfo := s.makeInstalledSnapInStateForRemodel(c, "pc", snap.R(1), "20/stable") diff --git a/secboot/secboot.go b/secboot/secboot.go index b488939f0b5..278a3804634 100644 --- a/secboot/secboot.go +++ b/secboot/secboot.go @@ -25,6 +25,8 @@ package secboot // Debian does run "go list" without any support for passing -tags. import ( + "encoding/json" + "github.com/snapcore/snapd/asserts" "github.com/snapcore/snapd/bootloader" "github.com/snapcore/snapd/dirs" @@ -132,9 +134,19 @@ type SealKeysWithFDESetupHookParams struct { AuxKeyFile string } +type SerializedPCRProfile json.RawMessage + +func (s SerializedPCRProfile) MarshalJSON() ([]byte, error) { + return json.RawMessage(s).MarshalJSON() +} + +func (s *SerializedPCRProfile) UnmarshalJSON(data []byte) error { + return (*json.RawMessage)(s).UnmarshalJSON(data) +} + type ResealKeysParams struct { // The snap model parameters - ModelParams []*SealKeyModelParams + PCRProfile SerializedPCRProfile // The path to the sealed key files KeyFiles []string // The path to the authorization policy update key file (only relevant for TPM) diff --git a/secboot/secboot_dummy.go b/secboot/secboot_dummy.go index 5f16ce81fae..c69881b0a93 100644 --- a/secboot/secboot_dummy.go +++ b/secboot/secboot_dummy.go @@ -21,6 +21,7 @@ package secboot import ( + "crypto" "errors" "github.com/snapcore/snapd/kernel/fde" @@ -85,3 +86,15 @@ func RenameOrDeleteKeys(node string, renames map[string]string) error { func DeleteKeys(node string, matches map[string]bool) error { return errBuildWithoutSecboot } + +func BuildPCRProtectionProfile(modelParams []*SealKeyModelParams) (SerializedPCRProfile, error) { + return nil, errBuildWithoutSecboot +} + +func GetPrimaryKeyHash(devicePath string, alg crypto.Hash) ([]byte, []byte, error) { + return nil, nil, errBuildWithoutSecboot +} + +func VerifyPrimaryKeyHash(devicePath string, alg crypto.Hash, salt []byte, digest []byte) (bool, error) { + return false, errBuildWithoutSecboot +} diff --git a/secboot/secboot_sb.go b/secboot/secboot_sb.go index d09c4e56fdf..ba410743566 100644 --- a/secboot/secboot_sb.go +++ b/secboot/secboot_sb.go @@ -21,6 +21,9 @@ package secboot import ( + "crypto" + "crypto/hmac" + "crypto/rand" "errors" "fmt" "path/filepath" @@ -291,3 +294,32 @@ func DeleteKeys(node string, matches map[string]bool) error { return nil } + +func GetPrimaryKeyHash(devicePath string, alg crypto.Hash) (salt []byte, digest []byte, err error) { + const remove = false + p, err := sb.GetPrimaryKeyFromKernel(keyringPrefix, devicePath, remove) + if err != nil { + return nil, nil, err + } + + var saltArray [32]byte + if _, err := rand.Read(saltArray[:]); err != nil { + return nil, nil, err + } + + h := hmac.New(alg.New, salt[:]) + h.Write(p) + return saltArray[:], h.Sum(nil), nil +} + +func VerifyPrimaryKeyHash(devicePath string, alg crypto.Hash, salt []byte, digest []byte) (bool, error) { + const remove = false + p, err := sb.GetPrimaryKeyFromKernel(keyringPrefix, devicePath, remove) + if err != nil { + return false, err + } + + h := hmac.New(alg.New, salt[:]) + h.Write(p) + return hmac.Equal(h.Sum(nil), digest), nil +} diff --git a/secboot/secboot_sb_test.go b/secboot/secboot_sb_test.go index 7f555951d57..bbf45432ee0 100644 --- a/secboot/secboot_sb_test.go +++ b/secboot/secboot_sb_test.go @@ -1125,6 +1125,7 @@ func (s *secbootSuite) TestResealKey(c *C) { revokeCalls int expectedErr string oldKeyFiles bool + buildProfileErr string }{ // happy case {tpmEnabled: true, resealCalls: 1, expectedErr: ""}, @@ -1134,10 +1135,10 @@ func (s *secbootSuite) TestResealKey(c *C) { // unhappy cases {tpmErr: mockErr, expectedErr: "cannot connect to TPM: some error"}, {tpmEnabled: false, expectedErr: "TPM device is not enabled"}, - {tpmEnabled: true, missingFile: true, expectedErr: "cannot build new PCR protection profile: cannot build EFI image load sequences: file .*\\/file.efi does not exist"}, - {tpmEnabled: true, addPCRProfileErr: mockErr, expectedErr: "cannot build new PCR protection profile: cannot add EFI secure boot and boot manager policy profiles: some error"}, - {tpmEnabled: true, addSystemdEFIStubErr: mockErr, expectedErr: "cannot build new PCR protection profile: cannot add systemd EFI stub profile: some error"}, - {tpmEnabled: true, addSnapModelErr: mockErr, expectedErr: "cannot build new PCR protection profile: cannot add snap model profile: some error"}, + {tpmEnabled: true, missingFile: true, buildProfileErr: `cannot build EFI image load sequences: file .*\/file.efi does not exist`}, + {tpmEnabled: true, addPCRProfileErr: mockErr, buildProfileErr: `cannot add EFI secure boot and boot manager policy profiles: some error`}, + {tpmEnabled: true, addSystemdEFIStubErr: mockErr, buildProfileErr: `cannot add systemd EFI stub profile: some error`}, + {tpmEnabled: true, addSnapModelErr: mockErr, buildProfileErr: `cannot add snap model profile: some error`}, {tpmEnabled: true, readSealedKeyObjectErr: mockErr, expectedErr: "cannot read key file .*: some error"}, {tpmEnabled: true, resealErr: mockErr, resealCalls: 1, expectedErr: "cannot update legacy PCR protection policy: some error", oldKeyFiles: true}, {tpmEnabled: true, resealErr: mockErr, resealCalls: 1, expectedErr: "cannot update PCR protection policy: some error"}, @@ -1155,17 +1156,66 @@ func (s *secbootSuite) TestResealKey(c *C) { c.Assert(err, IsNil) } + modelParams := []*secboot.SealKeyModelParams{ + { + EFILoadChains: []*secboot.LoadChain{secboot.NewLoadChain(mockEFI)}, + KernelCmdlines: []string{"cmdline"}, + Model: &asserts.Model{}, + }, + } + + sequences := sb_efi.NewImageLoadSequences().Append( + sb_efi.NewImageLoadActivity( + sb_efi.NewFileImage(mockEFI.Path), + ), + ) + + addPCRProfileCalls := 0 + restore := secboot.MockSbEfiAddPCRProfile(func(pcrAlg tpm2.HashAlgorithmId, branch *sb_tpm2.PCRProtectionProfileBranch, loadSequences *sb_efi.ImageLoadSequences, options ...sb_efi.PCRProfileOption) error { + addPCRProfileCalls++ + c.Assert(pcrAlg, Equals, tpm2.HashAlgorithmSHA256) + c.Assert(loadSequences, DeepEquals, sequences) + return tc.addPCRProfileErr + }) + defer restore() + + // mock adding snap model profile + addSnapModelCalls := 0 + restore = secboot.MockSbAddSnapModelProfile(func(profile *sb_tpm2.PCRProtectionProfileBranch, params *sb_tpm2.SnapModelProfileParams) error { + addSnapModelCalls++ + //c.Assert(profile, Equals, pcrProfile) + c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) + c.Assert(params.PCRIndex, Equals, 12) + c.Assert(params.Models[0], DeepEquals, modelParams[0].Model) + return tc.addSnapModelErr + }) + defer restore() + + // mock adding systemd EFI stub profile + addSystemdEfiStubCalls := 0 + restore = secboot.MockSbEfiAddSystemdStubProfile(func(profile *sb_tpm2.PCRProtectionProfileBranch, params *sb_efi.SystemdStubProfileParams) error { + addSystemdEfiStubCalls++ + //c.Assert(profile, Equals, pcrProfile) + c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) + c.Assert(params.PCRIndex, Equals, 12) + c.Assert(params.KernelCmdlines, DeepEquals, modelParams[0].KernelCmdlines) + return tc.addSystemdEFIStubErr + }) + defer restore() + + pcrProfile, err := secboot.BuildPCRProtectionProfile(modelParams) + if len(tc.buildProfileErr) > 0 { + c.Assert(err, ErrorMatches, tc.buildProfileErr) + continue + } else { + c.Assert(err, IsNil) + } + tmpdir := c.MkDir() keyFile := filepath.Join(tmpdir, "keyfile") keyFile2 := filepath.Join(tmpdir, "keyfile2") myParams := &secboot.ResealKeysParams{ - ModelParams: []*secboot.SealKeyModelParams{ - { - EFILoadChains: []*secboot.LoadChain{secboot.NewLoadChain(mockEFI)}, - KernelCmdlines: []string{"cmdline"}, - Model: &asserts.Model{}, - }, - }, + PCRProfile: pcrProfile, KeyFiles: []string{keyFile, keyFile2}, TPMPolicyAuthKeyFile: mockTPMPolicyAuthKeyFile, } @@ -1194,12 +1244,6 @@ func (s *secbootSuite) TestResealKey(c *C) { } } - sequences := sb_efi.NewImageLoadSequences().Append( - sb_efi.NewImageLoadActivity( - sb_efi.NewFileImage(mockEFI.Path), - ), - ) - // mock TPM connection tpm, restore := mockSbTPMConnection(c, tc.tpmErr) defer restore() @@ -1210,39 +1254,6 @@ func (s *secbootSuite) TestResealKey(c *C) { }) defer restore() - addPCRProfileCalls := 0 - restore = secboot.MockSbEfiAddPCRProfile(func(pcrAlg tpm2.HashAlgorithmId, branch *sb_tpm2.PCRProtectionProfileBranch, loadSequences *sb_efi.ImageLoadSequences, options ...sb_efi.PCRProfileOption) error { - addPCRProfileCalls++ - c.Assert(pcrAlg, Equals, tpm2.HashAlgorithmSHA256) - c.Assert(loadSequences, DeepEquals, sequences) - return tc.addPCRProfileErr - }) - defer restore() - - // mock adding systemd EFI stub profile - addSystemdEfiStubCalls := 0 - restore = secboot.MockSbEfiAddSystemdStubProfile(func(profile *sb_tpm2.PCRProtectionProfileBranch, params *sb_efi.SystemdStubProfileParams) error { - addSystemdEfiStubCalls++ - //c.Assert(profile, Equals, pcrProfile) - c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) - c.Assert(params.PCRIndex, Equals, 12) - c.Assert(params.KernelCmdlines, DeepEquals, myParams.ModelParams[0].KernelCmdlines) - return tc.addSystemdEFIStubErr - }) - defer restore() - - // mock adding snap model profile - addSnapModelCalls := 0 - restore = secboot.MockSbAddSnapModelProfile(func(profile *sb_tpm2.PCRProtectionProfileBranch, params *sb_tpm2.SnapModelProfileParams) error { - addSnapModelCalls++ - //c.Assert(profile, Equals, pcrProfile) - c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) - c.Assert(params.PCRIndex, Equals, 12) - c.Assert(params.Models[0], DeepEquals, myParams.ModelParams[0].Model) - return tc.addSnapModelErr - }) - defer restore() - // mock ReadSealedKeyObject readSealedKeyObjectCalls := 0 restore = secboot.MockReadKeyFile(func(keyfile string) (*sb.KeyData, *sb_tpm2.SealedKeyObject, error) { @@ -2548,3 +2559,22 @@ func (s *secbootSuite) TestDeleteKeysErrorDelete(c *C) { err := secboot.DeleteKeys("/dev/foo", toRemove) c.Assert(err, ErrorMatches, `cannot remove old container key: some error`) } + +type SomeStructure struct { + TPM2PCRProfile secboot.SerializedPCRProfile `json:"tpm2-pcr-profile"` +} + +func (s *secbootSuite) TestSerializedProfile(c *C) { + krp := SomeStructure{ + TPM2PCRProfile: secboot.SerializedPCRProfile(`"serialized-profile"`), + } + + data, err := json.Marshal(krp) + c.Assert(err, IsNil) + var raw map[string]any + err = json.Unmarshal(data, &raw) + c.Assert(err, IsNil) + c.Check(raw, DeepEquals, map[string]any{ + "tpm2-pcr-profile": "serialized-profile", + }) +} diff --git a/secboot/secboot_tpm.go b/secboot/secboot_tpm.go index 398a7a8de2f..294c4595b9d 100644 --- a/secboot/secboot_tpm.go +++ b/secboot/secboot_tpm.go @@ -23,6 +23,7 @@ package secboot import ( "bytes" "crypto/rand" + "encoding/json" "errors" "fmt" "io" @@ -485,10 +486,6 @@ func SealKeys(keys []SealKeyRequest, params *SealKeysParams) ([]byte, error) { // ResealKeys updates the PCR protection policy for the sealed encryption keys // according to the specified parameters. func ResealKeys(params *ResealKeysParams) error { - numModels := len(params.ModelParams) - if numModels < 1 { - return fmt.Errorf("at least one set of model-specific parameters is required") - } numSealedKeyObjects := len(params.KeyFiles) if numSealedKeyObjects < 1 { return fmt.Errorf("at least one key file is required") @@ -503,9 +500,9 @@ func ResealKeys(params *ResealKeysParams) error { return fmt.Errorf("TPM device is not enabled") } - pcrProfile, err := buildPCRProtectionProfile(params.ModelParams) - if err != nil { - return fmt.Errorf("cannot build new PCR protection profile: %w", err) + var pcrProfile sb_tpm2.PCRProtectionProfile + if err := json.Unmarshal(params.PCRProfile, &pcrProfile); err != nil { + return err } authKey, err := os.ReadFile(params.TPMPolicyAuthKeyFile) @@ -537,7 +534,7 @@ func ResealKeys(params *ResealKeysParams) error { } if hasOldObject { - if err := sbUpdateKeyPCRProtectionPolicyMultiple(tpm, sealedKeyObjects, authKey, pcrProfile); err != nil { + if err := sbUpdateKeyPCRProtectionPolicyMultiple(tpm, sealedKeyObjects, authKey, &pcrProfile); err != nil { return fmt.Errorf("cannot update legacy PCR protection policy: %w", err) } @@ -555,7 +552,7 @@ func ResealKeys(params *ResealKeysParams) error { } } else { // TODO: find out which context when revocation should happen - if err := sbUpdateKeyDataPCRProtectionPolicy(tpm, authKey, pcrProfile, sb_tpm2.NoNewPCRPolicyVersion, keyDatas...); err != nil { + if err := sbUpdateKeyDataPCRProtectionPolicy(tpm, authKey, &pcrProfile, sb_tpm2.NoNewPCRPolicyVersion, keyDatas...); err != nil { return fmt.Errorf("cannot update PCR protection policy: %w", err) } @@ -628,6 +625,16 @@ func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb_tpm2.PCRP return pcrProfile, nil } +// BuildPCRProtectionProfile builds and serializes a PCR profile from +// a list of SealKeyModelParams +func BuildPCRProtectionProfile(modelParams []*SealKeyModelParams) (SerializedPCRProfile, error) { + pcrProfile, err := buildPCRProtectionProfile(modelParams) + if err != nil { + return nil, err + } + return json.Marshal(pcrProfile) +} + func tpmProvision(tpm *sb_tpm2.Connection, mode TPMProvisionMode, lockoutAuthFile string) error { var currentLockoutAuth []byte if mode == TPMPartialReprovision {