Skip to content

Commit

Permalink
boot: pass factory-reset flags when sealing keys, explicit PCR handles
Browse files Browse the repository at this point in the history
Seal keys takes flag that indicates a reprovisioning scenario, which happens
during factory reset.

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
  • Loading branch information
bboozzoo committed Jun 3, 2022
1 parent f7efa54 commit d4bab5f
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 51 deletions.
7 changes: 7 additions & 0 deletions boot/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ var (
type BootAssetsMap = bootAssetsMap
type BootCommandLines = bootCommandLines
type TrackedAsset = trackedAsset
type SealKeyToModeenvFlags = sealKeyToModeenvFlags

func (t *TrackedAsset) Equals(blName, name, hash string) error {
equal := t.hash == hash &&
Expand Down Expand Up @@ -134,6 +135,12 @@ func MockSeedReadSystemEssential(f func(seedDir, label string, essentialTypes []
}
}

func MockSecbootPCRHandleOfSealedKey(f func(p string) (uint32, error)) (restore func()) {
restore = testutil.Backup(&secbootPCRHandleOfSealedKey)
secbootPCRHandleOfSealedKey = f
return restore
}

func (o *TrustedAssetsUpdateObserver) InjectChangedAsset(blName, assetName, hash string, recovery bool) {
ta := &trackedAsset{
blName: blName,
Expand Down
39 changes: 28 additions & 11 deletions boot/makebootable.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,16 +290,11 @@ func MakeRecoverySystemBootable(rootdir string, relativeRecoverySystemDir string
return nil
}

// MakeRunnableSystem is like MakeBootableImage in that it sets up a system to
// be able to boot, but is unique in that it is intended to be called from UC20
// install mode and makes the run system bootable (hence it is called
// "runnable").
// Note that this function does not update the recovery bootloader env to
// actually transition to run mode here, that is left to the caller via
// something like boot.EnsureNextBootToRunMode(). This is to enable separately
// setting up a run system and actually transitioning to it, with hooks, etc.
// running in between.
func MakeRunnableSystem(model *asserts.Model, bootWith *BootableSet, sealer *TrustedAssetsInstallObserver) error {
type makeRunnableOptions struct {
AfterReset bool
}

func makeRunnableSystem(model *asserts.Model, bootWith *BootableSet, sealer *TrustedAssetsInstallObserver, makeOpts makeRunnableOptions) error {
if model.Grade() == asserts.ModelGradeUnset {
return fmt.Errorf("internal error: cannot make pre-UC20 system runnable")
}
Expand Down Expand Up @@ -457,8 +452,11 @@ func MakeRunnableSystem(model *asserts.Model, bootWith *BootableSet, sealer *Tru
}

if sealer != nil {
flags := sealKeyToModeenvFlags{
FactoryReset: makeOpts.AfterReset,
}
// seal the encryption key to the parameters specified in modeenv
if err := sealKeyToModeenv(sealer.dataEncryptionKey, sealer.saveEncryptionKey, model, modeenv); err != nil {
if err := sealKeyToModeenv(sealer.dataEncryptionKey, sealer.saveEncryptionKey, model, modeenv, flags); err != nil {
return err
}
}
Expand All @@ -470,3 +468,22 @@ func MakeRunnableSystem(model *asserts.Model, bootWith *BootableSet, sealer *Tru
}
return nil
}

// MakeRunnableSystem is like MakeBootableImage in that it sets up a system to
// be able to boot, but is unique in that it is intended to be called from UC20
// install mode and makes the run system bootable (hence it is called
// "runnable").
// Note that this function does not update the recovery bootloader env to
// actually transition to run mode here, that is left to the caller via
// something like boot.EnsureNextBootToRunMode(). This is to enable separately
// setting up a run system and actually transitioning to it, with hooks, etc.
// running in between.
func MakeRunnableSystem(model *asserts.Model, bootWith *BootableSet, sealer *TrustedAssetsInstallObserver) error {
return makeRunnableSystem(model, bootWith, sealer, makeRunnableOptions{})
}

func MakeRunnableSystemAfterReset(model *asserts.Model, bootWith *BootableSet, sealer *TrustedAssetsInstallObserver) error {
return makeRunnableSystem(model, bootWith, sealer, makeRunnableOptions{
AfterReset: true,
})
}
68 changes: 65 additions & 3 deletions boot/makebootable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ func (s *makeBootable20Suite) TestMakeSystemRunnable16Fails(c *C) {
c.Assert(err, ErrorMatches, `internal error: cannot make pre-UC20 system runnable`)
}

func (s *makeBootable20Suite) TestMakeSystemRunnable20(c *C) {
func (s *makeBootable20Suite) testMakeSystemRunnable20(c *C, factoryReset bool) {
bootloader.Force(nil)

model := boottest.MakeMockUC20Model()
Expand Down Expand Up @@ -584,10 +584,30 @@ version: 5.0
restore = boot.MockSecbootProvisionTPM(func(mode secboot.TPMProvisionMode, lockoutAuthFile string) error {
provisionCalls++
c.Check(lockoutAuthFile, Equals, filepath.Join(boot.InstallHostFDESaveDir, "tpm-lockout-auth"))
c.Check(mode, Equals, secboot.TPMProvisionFull)
if factoryReset {
c.Check(mode, Equals, secboot.TPMPartialReprovision)
} else {
c.Check(mode, Equals, secboot.TPMProvisionFull)
}
return nil
})
defer restore()

pcrHandleOfKeyCalls := 0
restore = boot.MockSecbootPCRHandleOfSealedKey(func(p string) (uint32, error) {
pcrHandleOfKeyCalls++
c.Check(provisionCalls, Equals, 0)
if !factoryReset {
c.Errorf("unexpected call in non-factory-reset scenario")
return 0, fmt.Errorf("unexpected call")
}
c.Check(p, Equals,
filepath.Join(s.rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key"))
// trigger use of alt handles
return secboot.FallbackObjectPCRPolicyCounterHandle, nil
})
defer restore()

// set mock key sealing
sealKeysCalls := 0
restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error {
Expand All @@ -597,10 +617,32 @@ version: 5.0
case 1:
c.Check(keys, HasLen, 1)
c.Check(keys[0].Key, DeepEquals, myKey)
c.Check(keys[0].KeyFile, Equals,
filepath.Join(s.rootdir, "/run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key"))
if factoryReset {
c.Check(params.PCRPolicyCounterHandle, Equals, secboot.AltRunObjectPCRPolicyCounterHandle)
} else {
c.Check(params.PCRPolicyCounterHandle, Equals, secboot.RunObjectPCRPolicyCounterHandle)
}
case 2:
c.Check(keys, HasLen, 2)
c.Check(keys[0].Key, DeepEquals, myKey)
c.Check(keys[1].Key, DeepEquals, myKey2)
c.Check(keys[0].KeyFile, Equals,
filepath.Join(s.rootdir,
"/run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key"))
if factoryReset {
c.Check(params.PCRPolicyCounterHandle, Equals, secboot.AltFallbackObjectPCRPolicyCounterHandle)
c.Check(keys[1].KeyFile, Equals,
filepath.Join(s.rootdir,
"/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key.factory"))

} else {
c.Check(params.PCRPolicyCounterHandle, Equals, secboot.FallbackObjectPCRPolicyCounterHandle)
c.Check(keys[1].KeyFile, Equals,
filepath.Join(s.rootdir,
"/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key"))
}
default:
c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls)
}
Expand Down Expand Up @@ -647,7 +689,11 @@ version: 5.0
})
defer restore()

err = boot.MakeRunnableSystem(model, bootWith, obs)
if !factoryReset {
err = boot.MakeRunnableSystem(model, bootWith, obs)
} else {
err = boot.MakeRunnableSystemAfterReset(model, bootWith, obs)
}
c.Assert(err, IsNil)

// also do the logical thing and make the next boot go to run mode
Expand Down Expand Up @@ -734,6 +780,12 @@ current_kernel_command_lines=["snapd_recovery_mode=run console=ttyS0 console=tty
c.Check(provisionCalls, Equals, 1)
// make sure SealKey was called for the run object and the fallback object
c.Check(sealKeysCalls, Equals, 2)
// PCR handle checks
if factoryReset {
c.Check(pcrHandleOfKeyCalls, Equals, 1)
} else {
c.Check(pcrHandleOfKeyCalls, Equals, 0)
}

// make sure the marker file for sealed key was created
c.Check(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "sealed-keys"), testutil.FilePresent)
Expand All @@ -742,6 +794,16 @@ current_kernel_command_lines=["snapd_recovery_mode=run console=ttyS0 console=tty
c.Check(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "boot-chains"), testutil.FilePresent)
}

func (s *makeBootable20Suite) TestMakeSystemRunnable20Install(c *C) {
const factoryReset = false
s.testMakeSystemRunnable20(c, factoryReset)
}

func (s *makeBootable20Suite) TestMakeSystemRunnable20FactoryReset(c *C) {
const factoryReset = true
s.testMakeSystemRunnable20(c, factoryReset)
}

func (s *makeBootable20Suite) TestMakeRunnableSystem20ModeInstallBootConfigErr(c *C) {
bootloader.Force(nil)

Expand Down
91 changes: 75 additions & 16 deletions boot/seal.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ var (
secbootSealKeys = secboot.SealKeys
secbootSealKeysWithFDESetupHook = secboot.SealKeysWithFDESetupHook
secbootResealKeys = secboot.ResealKeys
secbootPCRHandleOfSealedKey = secboot.PCRHandleOfSealedKey

seedReadSystemEssential = seed.ReadSystemEssential
)
Expand Down Expand Up @@ -102,10 +103,16 @@ func recoveryBootChainsFileUnder(rootdir string) string {
return filepath.Join(dirs.SnapFDEDirUnder(rootdir), "recovery-boot-chains")
}

type sealKeyToModeenvFlags struct {
// FactoryReset indicates that the sealing is happening during factory
// reset.
FactoryReset bool
}

// sealKeyToModeenv seals the supplied keys to the parameters specified
// in modeenv.
// It assumes to be invoked in install mode.
func sealKeyToModeenv(key, saveKey keys.EncryptionKey, model *asserts.Model, modeenv *Modeenv) error {
func sealKeyToModeenv(key, saveKey keys.EncryptionKey, model *asserts.Model, modeenv *Modeenv, flags sealKeyToModeenvFlags) error {
// make sure relevant locations exist
for _, p := range []string{
InitramfsSeedEncryptionKeyDir,
Expand All @@ -124,10 +131,10 @@ func sealKeyToModeenv(key, saveKey keys.EncryptionKey, model *asserts.Model, mod
return fmt.Errorf("cannot check for fde-setup hook %v", err)
}
if hasHook {
return sealKeyToModeenvUsingFDESetupHook(key, saveKey, modeenv)
return sealKeyToModeenvUsingFDESetupHook(key, saveKey, modeenv, flags)
}

return sealKeyToModeenvUsingSecboot(key, saveKey, modeenv)
return sealKeyToModeenvUsingSecboot(key, saveKey, modeenv, flags)
}

func runKeySealRequests(key keys.EncryptionKey) []secboot.SealKeyRequest {
Expand All @@ -140,7 +147,15 @@ func runKeySealRequests(key keys.EncryptionKey) []secboot.SealKeyRequest {
}
}

func fallbackKeySealRequests(key, saveKey keys.EncryptionKey) []secboot.SealKeyRequest {
func fallbackKeySealRequests(key, saveKey keys.EncryptionKey, factoryReset bool) []secboot.SealKeyRequest {
saveFallbackKey := filepath.Join(InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key")

if factoryReset {
// factory reset uses alternative sealed key location, such that
// until we boot into the run mode, both sealed keys are present
// on disk
saveFallbackKey = filepath.Join(InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key.factory")
}
return []secboot.SealKeyRequest{
{
Key: key,
Expand All @@ -150,12 +165,12 @@ func fallbackKeySealRequests(key, saveKey keys.EncryptionKey) []secboot.SealKeyR
{
Key: saveKey,
KeyName: "ubuntu-save",
KeyFile: filepath.Join(InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
KeyFile: saveFallbackKey,
},
}
}

func sealKeyToModeenvUsingFDESetupHook(key, saveKey keys.EncryptionKey, modeenv *Modeenv) error {
func sealKeyToModeenvUsingFDESetupHook(key, saveKey keys.EncryptionKey, modeenv *Modeenv, flags sealKeyToModeenvFlags) error {
// XXX: Move the auxKey creation to a more generic place, see
// PR#10123 for a possible way of doing this. However given
// that the equivalent key for the TPM case is also created in
Expand All @@ -171,7 +186,8 @@ func sealKeyToModeenvUsingFDESetupHook(key, saveKey keys.EncryptionKey, modeenv
AuxKey: auxKey,
AuxKeyFile: filepath.Join(InstallHostFDESaveDir, "aux-key"),
}
skrs := append(runKeySealRequests(key), fallbackKeySealRequests(key, saveKey)...)
factoryReset := flags.FactoryReset
skrs := append(runKeySealRequests(key), fallbackKeySealRequests(key, saveKey, factoryReset)...)
if err := secbootSealKeysWithFDESetupHook(RunFDESetupHook, skrs, &params); err != nil {
return err
}
Expand All @@ -183,7 +199,7 @@ func sealKeyToModeenvUsingFDESetupHook(key, saveKey keys.EncryptionKey, modeenv
return nil
}

func sealKeyToModeenvUsingSecboot(key, saveKey keys.EncryptionKey, modeenv *Modeenv) error {
func sealKeyToModeenvUsingSecboot(key, saveKey keys.EncryptionKey, modeenv *Modeenv, flags sealKeyToModeenvFlags) error {
// build the recovery mode boot chain
rbl, err := bootloader.Find(InitramfsUbuntuSeedDir, &bootloader.Options{
Role: bootloader.RoleRecovery,
Expand Down Expand Up @@ -241,17 +257,43 @@ func sealKeyToModeenvUsingSecboot(key, saveKey keys.EncryptionKey, modeenv *Mode
return fmt.Errorf("cannot generate key for signing dynamic authorization policies: %v", err)
}

runObjectKeyPCRHandle := uint32(secboot.RunObjectPCRPolicyCounterHandle)
fallbackObjectKeyPCRHandle := uint32(secboot.FallbackObjectPCRPolicyCounterHandle)
if flags.FactoryReset {
// during factory reset we may need to rotate the PCR handles,
// seal the new keys using a new set of handles such that the
// old sealed ubuntu-save key is still usable
needAlt, err := needAltPCRHandles()
if err != nil {
return err
}
if needAlt {
logger.Noticef("using alternative PCR handles")
runObjectKeyPCRHandle = secboot.AltRunObjectPCRPolicyCounterHandle
fallbackObjectKeyPCRHandle = secboot.AltFallbackObjectPCRPolicyCounterHandle
}
}

// we are preparing a new system, hence the TPM needs to be provisioned
lockoutAuthFile := filepath.Join(InstallHostFDESaveDir, "tpm-lockout-auth")
if err := secbootProvisionTPM(secboot.TPMProvisionFull, lockoutAuthFile); err != nil {
tpmProvisionMode := secboot.TPMProvisionFull
if flags.FactoryReset {
tpmProvisionMode = secboot.TPMPartialReprovision
}
if err := secbootProvisionTPM(tpmProvisionMode, lockoutAuthFile); err != nil {
return err
}

if err := sealRunObjectKeys(key, pbc, authKey, roleToBlName); err != nil {
// TODO: refactor sealing functions to take a struct instead of so many
// parameters
err = sealRunObjectKeys(key, pbc, authKey, roleToBlName, flags.FactoryReset, runObjectKeyPCRHandle)
if err != nil {
return err
}

if err := sealFallbackObjectKeys(key, saveKey, rpbc, authKey, roleToBlName); err != nil {
err = sealFallbackObjectKeys(key, saveKey, rpbc, authKey, roleToBlName, flags.FactoryReset,
fallbackObjectKeyPCRHandle)
if err != nil {
return err
}

Expand All @@ -272,7 +314,18 @@ func sealKeyToModeenvUsingSecboot(key, saveKey keys.EncryptionKey, modeenv *Mode
return nil
}

func sealRunObjectKeys(key keys.EncryptionKey, pbc predictableBootChains, authKey *ecdsa.PrivateKey, roleToBlName map[bootloader.Role]string) error {
func needAltPCRHandles() (bool, error) {
saveFallbackKey := filepath.Join(InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key")
// inspect the PCR handle of the ubuntu-save fallback key
handle, err := secbootPCRHandleOfSealedKey(saveFallbackKey)
if err != nil {
return false, err
}
logger.Noticef("sealed key %v PCR handle: %#x", saveFallbackKey, handle)
return handle == secboot.FallbackObjectPCRPolicyCounterHandle, nil
}

func sealRunObjectKeys(key keys.EncryptionKey, pbc predictableBootChains, authKey *ecdsa.PrivateKey, roleToBlName map[bootloader.Role]string, partialReprovisionTPM bool, pcrHandle uint32) error {
modelParams, err := sealKeyModelParams(pbc, roleToBlName)
if err != nil {
return fmt.Errorf("cannot prepare for key sealing: %v", err)
Expand All @@ -282,8 +335,10 @@ func sealRunObjectKeys(key keys.EncryptionKey, pbc predictableBootChains, authKe
ModelParams: modelParams,
TPMPolicyAuthKey: authKey,
TPMPolicyAuthKeyFile: filepath.Join(InstallHostFDESaveDir, "tpm-policy-auth-key"),
PCRPolicyCounterHandle: secboot.RunObjectPCRPolicyCounterHandle,
PCRPolicyCounterHandle: pcrHandle,
}

logger.Debugf("sealing run key with PCR handle: %#x", sealKeyParams.PCRPolicyCounterHandle)
// The run object contains only the ubuntu-data key; the ubuntu-save key
// is then stored inside the encrypted data partition, so that the normal run
// path only unseals one object because unsealing is expensive.
Expand All @@ -296,7 +351,7 @@ func sealRunObjectKeys(key keys.EncryptionKey, pbc predictableBootChains, authKe
return nil
}

func sealFallbackObjectKeys(key, saveKey keys.EncryptionKey, pbc predictableBootChains, authKey *ecdsa.PrivateKey, roleToBlName map[bootloader.Role]string) error {
func sealFallbackObjectKeys(key, saveKey keys.EncryptionKey, pbc predictableBootChains, authKey *ecdsa.PrivateKey, roleToBlName map[bootloader.Role]string, partialReprovisionTPM bool, pcrHandle uint32) error {
// also seal the keys to the recovery bootchains as a fallback
modelParams, err := sealKeyModelParams(pbc, roleToBlName)
if err != nil {
Expand All @@ -305,12 +360,16 @@ func sealFallbackObjectKeys(key, saveKey keys.EncryptionKey, pbc predictableBoot
sealKeyParams := &secboot.SealKeysParams{
ModelParams: modelParams,
TPMPolicyAuthKey: authKey,
PCRPolicyCounterHandle: secboot.FallbackObjectPCRPolicyCounterHandle,
PCRPolicyCounterHandle: pcrHandle,
}
logger.Debugf("sealing fallback key with PCR handle: %#x", sealKeyParams.PCRPolicyCounterHandle)
// The fallback object contains the ubuntu-data and ubuntu-save keys. The
// key files are stored on ubuntu-seed, separate from ubuntu-data so they
// can be used if ubuntu-data and ubuntu-boot are corrupted or unavailable.
if err := secbootSealKeys(fallbackKeySealRequests(key, saveKey), sealKeyParams); err != nil {

// XXX find a better name
factoryReset := partialReprovisionTPM
if err := secbootSealKeys(fallbackKeySealRequests(key, saveKey, factoryReset), sealKeyParams); err != nil {
return fmt.Errorf("cannot seal the fallback encryption keys: %v", err)
}

Expand Down
Loading

0 comments on commit d4bab5f

Please sign in to comment.