Skip to content

Commit

Permalink
overlord/fdestate/backend: split profiles for data and save partitions
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
valentindavid committed Feb 12, 2025
1 parent 685a615 commit 6cff916
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 64 deletions.
121 changes: 72 additions & 49 deletions overlord/fdestate/backend/reseal.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,76 +119,85 @@ 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

Check warning on line 140 in overlord/fdestate/backend/reseal.go

View check run for this annotation

Codecov / codecov/patch

overlord/fdestate/backend/reseal.go#L140

Added line #L140 was not covered by tests
}

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
}
}
return nil
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)
Expand All @@ -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
}

Check warning on line 226 in overlord/fdestate/backend/reseal.go

View check run for this annotation

Codecov / codecov/patch

overlord/fdestate/backend/reseal.go#L225-L226

Added lines #L225 - L226 were not covered by tests

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
}

Expand Down Expand Up @@ -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
}

Check warning on line 445 in overlord/fdestate/backend/reseal.go

View check run for this annotation

Codecov / codecov/patch

overlord/fdestate/backend/reseal.go#L444-L445

Added lines #L444 - L445 were not covered by tests

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
}

Expand Down
47 changes: 36 additions & 11 deletions overlord/fdestate/backend/reseal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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)
Expand Down Expand Up @@ -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",
Expand All @@ -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)
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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"))
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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"))
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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"))
Expand Down Expand Up @@ -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",
Expand All @@ -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) {
Expand Down Expand Up @@ -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)
}
4 changes: 1 addition & 3 deletions overlord/fdestate/backend/seal.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion overlord/fdestate/backend/seal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
5 changes: 5 additions & 0 deletions secboot/secboot.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 6cff916

Please sign in to comment.