From 6cff916698ac20498332cbf52372f53712095229 Mon Sep 17 00:00:00 2001 From: Valentin David Date: Mon, 10 Feb 2025 14:10:09 +0100 Subject: [PATCH] overlord/fdestate/backend: split profiles for data and save partitions There should be 3 different keys for FDE hooks. The run+recover key should be allowed for boot modes "run" and "recover". While recover key on data disk should be allowed on "recover". And finally recovery on save disk should be allowed in "recover" and "factory-reset". Here we split the profiles for "recover" for disks "data" and "save", so that we can set different authorized boot modes. --- overlord/fdestate/backend/reseal.go | 121 ++++++++++++++--------- overlord/fdestate/backend/reseal_test.go | 47 ++++++--- overlord/fdestate/backend/seal.go | 4 +- overlord/fdestate/backend/seal_test.go | 2 +- secboot/secboot.go | 5 + 5 files changed, 115 insertions(+), 64 deletions(-) diff --git a/overlord/fdestate/backend/reseal.go b/overlord/fdestate/backend/reseal.go index 1166b79e78d..d7388432244 100644 --- a/overlord/fdestate/backend/reseal.go +++ b/overlord/fdestate/backend/reseal.go @@ -119,48 +119,67 @@ func getUniqueModels(bootChains []boot.BootChain) []secboot.ModelForSealing { return models } +type resealParamsAndLocation struct { + params *SealingParameters + location secboot.KeyDataLocation +} + func doReseal(manager FDEStateManager, method device.SealingMethod, rootdir string) error { - runParams, err := manager.Get("run+recover", "all") + runParamsData, err := manager.Get("run+recover", "system-data") if err != nil { return err } - recoveryParams, err := manager.Get("recover", "system-save") + recoveryParamsSave, err := manager.Get("recover", "system-save") if err != nil { return err } - runKeys := []secboot.KeyDataLocation{ - { - DevicePath: "/dev/disk/by-partlabel/ubuntu-data", - SlotName: "default", - KeyFile: device.DataSealedKeyUnder(boot.InitramfsBootEncryptionKeyDir), - }, + recoveryParamsData, err := manager.Get("recover", "system-data") + if err != nil { + return err } - recoveryKeys := []secboot.KeyDataLocation{ - { - DevicePath: "/dev/disk/by-partlabel/ubuntu-data", - SlotName: "default-fallback", - KeyFile: device.FallbackDataSealedKeyUnder(boot.InitramfsSeedEncryptionKeyDir), - }, - { - DevicePath: "/dev/disk/by-partlabel/ubuntu-save", - SlotName: "default-fallback", - KeyFile: device.FallbackSaveSealedKeyUnder(boot.InitramfsSeedEncryptionKeyDir), - }, + var keys []resealParamsAndLocation + + if runParamsData != nil { + keys = append(keys, resealParamsAndLocation{ + params: runParamsData, + location: secboot.KeyDataLocation{ + DevicePath: "/dev/disk/by-partlabel/ubuntu-data", + SlotName: "default", + KeyFile: device.DataSealedKeyUnder(boot.InitramfsBootEncryptionKeyDir), + }, + }) + } + + if recoveryParamsData != nil { + keys = append(keys, resealParamsAndLocation{ + params: recoveryParamsData, + location: secboot.KeyDataLocation{ + DevicePath: "/dev/disk/by-partlabel/ubuntu-data", + SlotName: "default-fallback", + KeyFile: device.FallbackDataSealedKeyUnder(boot.InitramfsSeedEncryptionKeyDir), + }, + }) + } + + if recoveryParamsSave != nil { + keys = append(keys, resealParamsAndLocation{ + params: recoveryParamsSave, + location: secboot.KeyDataLocation{ + DevicePath: "/dev/disk/by-partlabel/ubuntu-save", + SlotName: "default-fallback", + KeyFile: device.FallbackSaveSealedKeyUnder(boot.InitramfsSeedEncryptionKeyDir), + }, + }) } switch method { case device.SealingMethodFDESetupHook: primaryKeyFile := filepath.Join(boot.InstallHostFDESaveDir, "aux-key") - if runParams != nil { - if err := secbootResealKeysWithFDESetupHook(runKeys, primaryKeyFile, runParams.Models, runParams.BootModes); err != nil { - return err - } - } - if recoveryParams != nil { - if err := secbootResealKeysWithFDESetupHook(recoveryKeys, primaryKeyFile, recoveryParams.Models, recoveryParams.BootModes); err != nil { + for _, key := range keys { + if err := secbootResealKeysWithFDESetupHook([]secboot.KeyDataLocation{key.location}, primaryKeyFile, key.params.Models, key.params.BootModes); err != nil { return err } } @@ -168,27 +187,17 @@ func doReseal(manager FDEStateManager, method device.SealingMethod, rootdir stri case device.SealingMethodTPM, device.SealingMethodLegacyTPM: saveFDEDir := dirs.SnapFDEDirUnderSave(dirs.SnapSaveDirUnder(rootdir)) authKeyFile := filepath.Join(saveFDEDir, "tpm-policy-auth-key") - if runParams != nil { - runResealKeyParams := &secboot.ResealKeysParams{ - PCRProfile: runParams.TpmPCRProfile, - Keys: runKeys, + for _, key := range keys { + keyParams := &secboot.ResealKeysParams{ + PCRProfile: key.params.TpmPCRProfile, + Keys: []secboot.KeyDataLocation{key.location}, TPMPolicyAuthKeyFile: authKeyFile, } - if err := secbootResealKeys(runResealKeyParams); err != nil { + if err := secbootResealKeys(keyParams); err != nil { return fmt.Errorf("cannot reseal the encryption key: %v", err) } } - if recoveryParams != nil { - recoveryResealKeyParams := &secboot.ResealKeysParams{ - PCRProfile: recoveryParams.TpmPCRProfile, - Keys: recoveryKeys, - TPMPolicyAuthKeyFile: authKeyFile, - } - if err := secbootResealKeys(recoveryResealKeyParams); err != nil { - return fmt.Errorf("cannot reseal the fallback encryption keys: %v", err) - } - } return nil default: return fmt.Errorf("unknown key sealing method: %q", method) @@ -208,11 +217,19 @@ func recalculateParamatersFDEHook(manager FDEStateManager, method device.Sealing return err } - recoveryParams := &SealingParameters{ + recoveryParamsData := &SealingParameters{ + BootModes: []string{"recover"}, + Models: recoveryModels, + } + if err := manager.Update("recover", "system-data", recoveryParamsData); err != nil { + return err + } + + recoveryParamsSave := &SealingParameters{ BootModes: []string{"recover", "factory-reset"}, Models: recoveryModels, } - if err := manager.Update("recover", "system-save", recoveryParams); err != nil { + if err := manager.Update("recover", "system-save", recoveryParamsSave); err != nil { return err } @@ -417,17 +434,23 @@ func updateFallbackProtectionProfile( models = append(models, m.Model) } - // TODO:FDEM:FIX: We are missing recover for system-data, for - // "recover" boot mode. It is different from the run+recover - // as this should only include working models. - - params := &SealingParameters{ + saveParams := &SealingParameters{ BootModes: []string{"recover", "factory-reset"}, Models: models, TpmPCRProfile: pcrProfile, } // TODO:FDEM: use constants for "recover" (the first parameter) and "system-save" - if err := manager.Update("recover", "system-save", params); err != nil { + if err := manager.Update("recover", "system-save", saveParams); err != nil { + return err + } + + dataParams := &SealingParameters{ + BootModes: []string{"recover"}, + Models: models, + TpmPCRProfile: pcrProfile, + } + // TODO:FDEM: use constants for "recover" (the first parameter) and "system-data" + if err := manager.Update("recover", "system-data", dataParams); err != nil { return err } diff --git a/overlord/fdestate/backend/reseal_test.go b/overlord/fdestate/backend/reseal_test.go index 926eabf239b..258ed4b3d05 100644 --- a/overlord/fdestate/backend/reseal_test.go +++ b/overlord/fdestate/backend/reseal_test.go @@ -359,13 +359,17 @@ func (s *resealTestSuite) TestTPMResealHappy(c *C) { }, }) case 2: - // Resealing the recovery key for both data and save partitions + // Resealing the recovery key for both data partition c.Check(params.Keys, DeepEquals, []secboot.KeyDataLocation{ { DevicePath: "/dev/disk/by-partlabel/ubuntu-data", SlotName: "default-fallback", KeyFile: filepath.Join(s.rootdir, "run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key"), }, + }) + case 3: + // Resealing the recovery key for both save partition + c.Check(params.Keys, DeepEquals, []secboot.KeyDataLocation{ { DevicePath: "/dev/disk/by-partlabel/ubuntu-save", SlotName: "default-fallback", @@ -384,7 +388,7 @@ func (s *resealTestSuite) TestTPMResealHappy(c *C) { err := fdeBackend.ResealKeyForBootChains(myState, device.SealingMethodTPM, s.rootdir, params, expectReseal) c.Assert(err, IsNil) - c.Check(resealCalls, Equals, 2) + c.Check(resealCalls, Equals, 3) pbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "boot-chains")) c.Assert(err, IsNil) @@ -663,13 +667,18 @@ func (s *resealTestSuite) TestResealKeyForBootchainsWithSystemFallback(c *C) { }) } - checkRecoveryParams := func() { + checkRecoveryParamsData := func() { c.Check(params.Keys, DeepEquals, []secboot.KeyDataLocation{ { DevicePath: "/dev/disk/by-partlabel/ubuntu-data", SlotName: "default-fallback", KeyFile: filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), }, + }) + } + + checkRecoveryParamsSave := func() { + c.Check(params.Keys, DeepEquals, []secboot.KeyDataLocation{ { DevicePath: "/dev/disk/by-partlabel/ubuntu-save", SlotName: "default-fallback", @@ -682,7 +691,9 @@ func (s *resealTestSuite) TestResealKeyForBootchainsWithSystemFallback(c *C) { case 1: checkRunParams() case 2: - checkRecoveryParams() + checkRecoveryParamsData() + case 3: + checkRecoveryParamsSave() default: c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls) } @@ -967,7 +978,7 @@ func (s *resealTestSuite) TestResealKeyForBootchainsWithSystemFallback(c *C) { // mocked error is returned on first reseal c.Assert(resealKeysCalls, Equals, 1) } else { - c.Assert(resealKeysCalls, Equals, 2) + c.Assert(resealKeysCalls, Equals, 3) } if tc.err != "" { continue @@ -1110,6 +1121,9 @@ func (s *resealTestSuite) TestResealKeyForBootchainsRecoveryKeysForGoodSystemsOn SlotName: "default-fallback", KeyFile: filepath.Join(s.rootdir, "run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key"), }, + }) + case 3: + c.Check(params.Keys, DeepEquals, []secboot.KeyDataLocation{ { DevicePath: "/dev/disk/by-partlabel/ubuntu-save", SlotName: "default-fallback", @@ -1255,7 +1269,7 @@ func (s *resealTestSuite) TestResealKeyForBootchainsRecoveryKeysForGoodSystemsOn const expectReseal = false err := fdeBackend.ResealKeyForBootChains(myState, device.SealingMethodTPM, s.rootdir, params, expectReseal) c.Assert(err, IsNil) - c.Assert(resealKeysCalls, Equals, 2) + c.Assert(resealKeysCalls, Equals, 3) // verify the boot chains data file for run key runPbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "boot-chains")) @@ -1430,6 +1444,9 @@ func (s *resealTestSuite) testResealKeyForBootchainsWithTryModel(c *C, shimId, g SlotName: "default-fallback", KeyFile: filepath.Join(s.rootdir, "run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key"), }, + }) + case 3: + c.Assert(params.Keys, DeepEquals, []secboot.KeyDataLocation{ { DevicePath: "/dev/disk/by-partlabel/ubuntu-save", SlotName: "default-fallback", @@ -1565,7 +1582,7 @@ func (s *resealTestSuite) testResealKeyForBootchainsWithTryModel(c *C, shimId, g const expectReseal = false err := fdeBackend.ResealKeyForBootChains(myState, device.SealingMethodTPM, s.rootdir, params, expectReseal) c.Assert(err, IsNil) - c.Assert(resealKeysCalls, Equals, 2) + c.Assert(resealKeysCalls, Equals, 3) // verify the boot chains data file for run key runPbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "boot-chains")) @@ -1653,6 +1670,7 @@ func (s *resealTestSuite) TestResealKeyForBootchainsFallbackCmdline(c *C) { switch resealKeysCalls { case 1: case 2: + case 3: default: c.Fatalf("unexpected number of reseal calls, %v", params) } @@ -1740,7 +1758,7 @@ func (s *resealTestSuite) TestResealKeyForBootchainsFallbackCmdline(c *C) { const expectReseal = false err = fdeBackend.ResealKeyForBootChains(myState, device.SealingMethodTPM, s.rootdir, params, expectReseal) c.Assert(err, IsNil) - c.Assert(resealKeysCalls, Equals, 2) + c.Assert(resealKeysCalls, Equals, 3) // verify the boot chains data file pbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "boot-chains")) @@ -1810,13 +1828,20 @@ func (s *resealTestSuite) TestHooksResealHappy(c *C) { c.Assert(models, HasLen, 1) c.Check(models[0].Model(), Equals, model.Model()) case 2: - // Resealing the recovery key for both data and save partitions + // Resealing the recovery key for both data partition c.Check(keys, DeepEquals, []secboot.KeyDataLocation{ { DevicePath: "/dev/disk/by-partlabel/ubuntu-data", SlotName: "default-fallback", KeyFile: filepath.Join(s.rootdir, "run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key"), }, + }) + c.Check(primaryKeyFile, Equals, filepath.Join(s.rootdir, "run/mnt/ubuntu-save/device/fde/aux-key")) + c.Assert(models, HasLen, 1) + c.Check(models[0].Model(), Equals, model.Model()) + case 3: + // Resealing the recovery key for both save partition + c.Check(keys, DeepEquals, []secboot.KeyDataLocation{ { DevicePath: "/dev/disk/by-partlabel/ubuntu-save", SlotName: "default-fallback", @@ -1839,7 +1864,7 @@ func (s *resealTestSuite) TestHooksResealHappy(c *C) { err := fdeBackend.ResealKeyForBootChains(myState, device.SealingMethodFDESetupHook, s.rootdir, params, expectReseal) c.Assert(err, IsNil) - c.Check(resealCalls, Equals, 2) + c.Check(resealCalls, Equals, 3) } func (s *resealTestSuite) TestResealKeyForSignatureDBUpdate(c *C) { @@ -1982,5 +2007,5 @@ func (s *resealTestSuite) TestResealKeyForSignatureDBUpdate(c *C) { // reseal was called c.Check(buildProfileCalls, Equals, 3) - c.Check(resealKeysCalls, Equals, 2) + c.Check(resealKeysCalls, Equals, 3) } diff --git a/overlord/fdestate/backend/seal.go b/overlord/fdestate/backend/seal.go index 1714a62bdcb..465c3a3cc0a 100644 --- a/overlord/fdestate/backend/seal.go +++ b/overlord/fdestate/backend/seal.go @@ -84,9 +84,7 @@ func fallbackKeySealRequests(key, saveKey secboot.BootstrappedContainer, factory KeyName: "ubuntu-data", SlotName: "default-fallback", KeyFile: dataFallbackKey, - // TODO:FDEM:FIX we should not not have "factory-reset" here, but for now - // we want to have the same as the pcr profile - BootModes: []string{"recover", "factory-reset"}, + BootModes: []string{"recover"}, }, { BootstrappedContainer: saveKey, diff --git a/overlord/fdestate/backend/seal_test.go b/overlord/fdestate/backend/seal_test.go index d63ef798683..51564a0ddc5 100644 --- a/overlord/fdestate/backend/seal_test.go +++ b/overlord/fdestate/backend/seal_test.go @@ -179,7 +179,7 @@ func (s *sealSuite) TestSealKeyForBootChains(c *C) { // the fallback object seals the ubuntu-data and the ubuntu-save keys c.Check(params.TPMPolicyAuthKeyFile, Equals, "") - expectedDataSKR := secboot.SealKeyRequest{BootstrappedContainer: myKey, KeyName: "ubuntu-data", SlotName: "default-fallback", BootModes: []string{"recover", "factory-reset"}} + expectedDataSKR := secboot.SealKeyRequest{BootstrappedContainer: myKey, KeyName: "ubuntu-data", SlotName: "default-fallback", BootModes: []string{"recover"}} expectedSaveSKR := secboot.SealKeyRequest{BootstrappedContainer: myKey2, KeyName: "ubuntu-save", SlotName: "default-fallback", BootModes: []string{"recover", "factory-reset"}} if tc.disableTokens { expectedDataSKR.KeyFile = filepath.Join(rootdir, "run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key") diff --git a/secboot/secboot.go b/secboot/secboot.go index 7b1fed0bfc5..53d10e9244d 100644 --- a/secboot/secboot.go +++ b/secboot/secboot.go @@ -186,6 +186,11 @@ type ResealKeysParams struct { TPMPolicyAuthKeyFile string } +type ResealKeysParamsForHooks struct { + Models []ModelForSealing + BootModes []string +} + // UnlockVolumeUsingSealedKeyOptions contains options for unlocking encrypted // volumes using keys sealed to the TPM. type UnlockVolumeUsingSealedKeyOptions struct {