gitee.com/mysnapcore/mysnapd@v0.1.0/boot/seal.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2020-2022 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package boot
    21  
    22  import (
    23  	"crypto/ecdsa"
    24  	"crypto/elliptic"
    25  	"crypto/rand"
    26  	"encoding/json"
    27  	"fmt"
    28  	"os"
    29  	"path/filepath"
    30  
    31  	"gitee.com/mysnapcore/mysnapd/asserts"
    32  	"gitee.com/mysnapcore/mysnapd/bootloader"
    33  	"gitee.com/mysnapcore/mysnapd/dirs"
    34  	"gitee.com/mysnapcore/mysnapd/gadget/device"
    35  	"gitee.com/mysnapcore/mysnapd/kernel/fde"
    36  	"gitee.com/mysnapcore/mysnapd/logger"
    37  	"gitee.com/mysnapcore/mysnapd/osutil"
    38  	"gitee.com/mysnapcore/mysnapd/secboot"
    39  	"gitee.com/mysnapcore/mysnapd/secboot/keys"
    40  	"gitee.com/mysnapcore/mysnapd/seed"
    41  	"gitee.com/mysnapcore/mysnapd/snap"
    42  	"gitee.com/mysnapcore/mysnapd/strutil"
    43  	"gitee.com/mysnapcore/mysnapd/timings"
    44  )
    45  
    46  var (
    47  	secbootProvisionTPM              = secboot.ProvisionTPM
    48  	secbootSealKeys                  = secboot.SealKeys
    49  	secbootSealKeysWithFDESetupHook  = secboot.SealKeysWithFDESetupHook
    50  	secbootResealKeys                = secboot.ResealKeys
    51  	secbootPCRHandleOfSealedKey      = secboot.PCRHandleOfSealedKey
    52  	secbootReleasePCRResourceHandles = secboot.ReleasePCRResourceHandles
    53  
    54  	seedReadSystemEssential = seed.ReadSystemEssential
    55  )
    56  
    57  // Hook functions setup by devicestate to support device-specific full
    58  // disk encryption implementations. The state must be locked when these
    59  // functions are called.
    60  var (
    61  	// HasFDESetupHook purpose is to detect if the target kernel has a
    62  	// fde-setup-hook. If kernelInfo is nil the current kernel is checked
    63  	// assuming it is representative` of the target one.
    64  	HasFDESetupHook = func(kernelInfo *snap.Info) (bool, error) {
    65  		return false, nil
    66  	}
    67  	RunFDESetupHook fde.RunSetupHookFunc = func(req *fde.SetupRequest) ([]byte, error) {
    68  		return nil, fmt.Errorf("internal error: RunFDESetupHook not set yet")
    69  	}
    70  )
    71  
    72  // MockSecbootResealKeys is only useful in testing. Note that this is a very low
    73  // level call and may need significant environment setup.
    74  func MockSecbootResealKeys(f func(params *secboot.ResealKeysParams) error) (restore func()) {
    75  	osutil.MustBeTestBinary("secbootResealKeys only can be mocked in tests")
    76  	old := secbootResealKeys
    77  	secbootResealKeys = f
    78  	return func() {
    79  		secbootResealKeys = old
    80  	}
    81  }
    82  
    83  // MockResealKeyToModeenv is only useful in testing.
    84  func MockResealKeyToModeenv(f func(rootdir string, modeenv *Modeenv, expectReseal bool) error) (restore func()) {
    85  	osutil.MustBeTestBinary("resealKeyToModeenv only can be mocked in tests")
    86  	old := resealKeyToModeenv
    87  	resealKeyToModeenv = f
    88  	return func() {
    89  		resealKeyToModeenv = old
    90  	}
    91  }
    92  
    93  func bootChainsFileUnder(rootdir string) string {
    94  	return filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains")
    95  }
    96  
    97  func recoveryBootChainsFileUnder(rootdir string) string {
    98  	return filepath.Join(dirs.SnapFDEDirUnder(rootdir), "recovery-boot-chains")
    99  }
   100  
   101  type sealKeyToModeenvFlags struct {
   102  	// HasFDESetupHook is true if the kernel has a fde-setup hook to use
   103  	HasFDESetupHook bool
   104  	// FactoryReset indicates that the sealing is happening during factory
   105  	// reset.
   106  	FactoryReset bool
   107  	// SnapsDir is set to provide a non-default directory to find
   108  	// run mode snaps in.
   109  	SnapsDir string
   110  }
   111  
   112  // sealKeyToModeenv seals the supplied keys to the parameters specified
   113  // in modeenv.
   114  // It assumes to be invoked in install mode.
   115  func sealKeyToModeenv(key, saveKey keys.EncryptionKey, model *asserts.Model, modeenv *Modeenv, flags sealKeyToModeenvFlags) error {
   116  	// make sure relevant locations exist
   117  	for _, p := range []string{
   118  		InitramfsSeedEncryptionKeyDir,
   119  		InitramfsBootEncryptionKeyDir,
   120  		InstallHostFDEDataDir(model),
   121  		InstallHostFDESaveDir,
   122  	} {
   123  		// XXX: should that be 0700 ?
   124  		if err := os.MkdirAll(p, 0755); err != nil {
   125  			return err
   126  		}
   127  	}
   128  
   129  	if flags.HasFDESetupHook {
   130  		return sealKeyToModeenvUsingFDESetupHook(key, saveKey, model, modeenv, flags)
   131  	}
   132  
   133  	return sealKeyToModeenvUsingSecboot(key, saveKey, model, modeenv, flags)
   134  }
   135  
   136  func runKeySealRequests(key keys.EncryptionKey) []secboot.SealKeyRequest {
   137  	return []secboot.SealKeyRequest{
   138  		{
   139  			Key:     key,
   140  			KeyName: "ubuntu-data",
   141  			KeyFile: device.DataSealedKeyUnder(InitramfsBootEncryptionKeyDir),
   142  		},
   143  	}
   144  }
   145  
   146  func fallbackKeySealRequests(key, saveKey keys.EncryptionKey, factoryReset bool) []secboot.SealKeyRequest {
   147  	saveFallbackKey := device.FallbackSaveSealedKeyUnder(InitramfsSeedEncryptionKeyDir)
   148  
   149  	if factoryReset {
   150  		// factory reset uses alternative sealed key location, such that
   151  		// until we boot into the run mode, both sealed keys are present
   152  		// on disk
   153  		saveFallbackKey = device.FactoryResetFallbackSaveSealedKeyUnder(InitramfsSeedEncryptionKeyDir)
   154  	}
   155  	return []secboot.SealKeyRequest{
   156  		{
   157  			Key:     key,
   158  			KeyName: "ubuntu-data",
   159  			KeyFile: device.FallbackDataSealedKeyUnder(InitramfsSeedEncryptionKeyDir),
   160  		},
   161  		{
   162  			Key:     saveKey,
   163  			KeyName: "ubuntu-save",
   164  			KeyFile: saveFallbackKey,
   165  		},
   166  	}
   167  }
   168  
   169  func sealKeyToModeenvUsingFDESetupHook(key, saveKey keys.EncryptionKey, model *asserts.Model, modeenv *Modeenv, flags sealKeyToModeenvFlags) error {
   170  	// XXX: Move the auxKey creation to a more generic place, see
   171  	// PR#10123 for a possible way of doing this. However given
   172  	// that the equivalent key for the TPM case is also created in
   173  	// sealKeyToModeenvUsingTPM more symetric to create the auxKey
   174  	// here and when we also move TPM to use the auxKey to move
   175  	// the creation of it.
   176  	auxKey, err := keys.NewAuxKey()
   177  	if err != nil {
   178  		return fmt.Errorf("cannot create aux key: %v", err)
   179  	}
   180  	params := secboot.SealKeysWithFDESetupHookParams{
   181  		Model:      modeenv.ModelForSealing(),
   182  		AuxKey:     auxKey,
   183  		AuxKeyFile: filepath.Join(InstallHostFDESaveDir, "aux-key"),
   184  	}
   185  	factoryReset := flags.FactoryReset
   186  	skrs := append(runKeySealRequests(key), fallbackKeySealRequests(key, saveKey, factoryReset)...)
   187  	if err := secbootSealKeysWithFDESetupHook(RunFDESetupHook, skrs, &params); err != nil {
   188  		return err
   189  	}
   190  
   191  	if err := device.StampSealedKeys(InstallHostWritableDir(model), "fde-setup-hook"); err != nil {
   192  		return err
   193  	}
   194  
   195  	return nil
   196  }
   197  
   198  func sealKeyToModeenvUsingSecboot(key, saveKey keys.EncryptionKey, model *asserts.Model, modeenv *Modeenv, flags sealKeyToModeenvFlags) error {
   199  	// build the recovery mode boot chain
   200  	rbl, err := bootloader.Find(InitramfsUbuntuSeedDir, &bootloader.Options{
   201  		Role: bootloader.RoleRecovery,
   202  	})
   203  	if err != nil {
   204  		return fmt.Errorf("cannot find the recovery bootloader: %v", err)
   205  	}
   206  	tbl, ok := rbl.(bootloader.TrustedAssetsBootloader)
   207  	if !ok {
   208  		// TODO:UC20: later the exact kind of bootloaders we expect here might change
   209  		return fmt.Errorf("internal error: cannot seal keys without a trusted assets bootloader")
   210  	}
   211  
   212  	includeTryModel := false
   213  	systems := []string{modeenv.RecoverySystem}
   214  	modes := map[string][]string{
   215  		// the system we are installing from is considered current and
   216  		// tested, hence allow both recover and factory reset modes
   217  		modeenv.RecoverySystem: {ModeRecover, ModeFactoryReset},
   218  	}
   219  	recoveryBootChains, err := recoveryBootChainsForSystems(systems, modes, tbl, modeenv, includeTryModel)
   220  	if err != nil {
   221  		return fmt.Errorf("cannot compose recovery boot chains: %v", err)
   222  	}
   223  	logger.Debugf("recovery bootchain:\n%+v", recoveryBootChains)
   224  
   225  	// build the run mode boot chains
   226  	bl, err := bootloader.Find(InitramfsUbuntuBootDir, &bootloader.Options{
   227  		Role:        bootloader.RoleRunMode,
   228  		NoSlashBoot: true,
   229  	})
   230  	if err != nil {
   231  		return fmt.Errorf("cannot find the bootloader: %v", err)
   232  	}
   233  
   234  	// kernel command lines are filled during install
   235  	cmdlines := modeenv.CurrentKernelCommandLines
   236  	runModeBootChains, err := runModeBootChains(rbl, bl, modeenv, cmdlines, flags.SnapsDir)
   237  	if err != nil {
   238  		return fmt.Errorf("cannot compose run mode boot chains: %v", err)
   239  	}
   240  	logger.Debugf("run mode bootchain:\n%+v", runModeBootChains)
   241  
   242  	pbc := toPredictableBootChains(append(runModeBootChains, recoveryBootChains...))
   243  
   244  	roleToBlName := map[bootloader.Role]string{
   245  		bootloader.RoleRecovery: rbl.Name(),
   246  		bootloader.RoleRunMode:  bl.Name(),
   247  	}
   248  
   249  	// the boot chains we seal the fallback object to
   250  	rpbc := toPredictableBootChains(recoveryBootChains)
   251  
   252  	// gets written to a file by sealRunObjectKeys()
   253  	authKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
   254  	if err != nil {
   255  		return fmt.Errorf("cannot generate key for signing dynamic authorization policies: %v", err)
   256  	}
   257  
   258  	runObjectKeyPCRHandle := uint32(secboot.RunObjectPCRPolicyCounterHandle)
   259  	fallbackObjectKeyPCRHandle := uint32(secboot.FallbackObjectPCRPolicyCounterHandle)
   260  	if flags.FactoryReset {
   261  		// during factory reset we may need to rotate the PCR handles,
   262  		// seal the new keys using a new set of handles such that the
   263  		// old sealed ubuntu-save key is still usable, for this we
   264  		// switch between two sets of handles in a round robin fashion,
   265  		// first looking at the PCR handle used by the current fallback
   266  		// key and then using the other set when sealing the new keys;
   267  		// the currently used handles will be released during the first
   268  		// boot of a new run system
   269  		usesAlt, err := usesAltPCRHandles()
   270  		if err != nil {
   271  			return err
   272  		}
   273  		if !usesAlt {
   274  			logger.Noticef("using alternative PCR handles")
   275  			runObjectKeyPCRHandle = secboot.AltRunObjectPCRPolicyCounterHandle
   276  			fallbackObjectKeyPCRHandle = secboot.AltFallbackObjectPCRPolicyCounterHandle
   277  		}
   278  	}
   279  
   280  	// we are preparing a new system, hence the TPM needs to be provisioned
   281  	lockoutAuthFile := device.TpmLockoutAuthUnder(InstallHostFDESaveDir)
   282  	tpmProvisionMode := secboot.TPMProvisionFull
   283  	if flags.FactoryReset {
   284  		tpmProvisionMode = secboot.TPMPartialReprovision
   285  	}
   286  	if err := secbootProvisionTPM(tpmProvisionMode, lockoutAuthFile); err != nil {
   287  		return err
   288  	}
   289  
   290  	if flags.FactoryReset {
   291  		// it is possible that we are sealing the keys again, after a
   292  		// previously running factory reset was interrupted by a reboot,
   293  		// in which case the PCR handles of the new sealed keys might
   294  		// have already been used
   295  		if err := secbootReleasePCRResourceHandles(runObjectKeyPCRHandle, fallbackObjectKeyPCRHandle); err != nil {
   296  			return err
   297  		}
   298  	}
   299  
   300  	// TODO: refactor sealing functions to take a struct instead of so many
   301  	// parameters
   302  	err = sealRunObjectKeys(key, pbc, authKey, roleToBlName, runObjectKeyPCRHandle)
   303  	if err != nil {
   304  		return err
   305  	}
   306  
   307  	err = sealFallbackObjectKeys(key, saveKey, rpbc, authKey, roleToBlName, flags.FactoryReset,
   308  		fallbackObjectKeyPCRHandle)
   309  	if err != nil {
   310  		return err
   311  	}
   312  
   313  	if err := device.StampSealedKeys(InstallHostWritableDir(model), device.SealingMethodTPM); err != nil {
   314  		return err
   315  	}
   316  
   317  	installBootChainsPath := bootChainsFileUnder(InstallHostWritableDir(model))
   318  	if err := writeBootChains(pbc, installBootChainsPath, 0); err != nil {
   319  		return err
   320  	}
   321  
   322  	installRecoveryBootChainsPath := recoveryBootChainsFileUnder(InstallHostWritableDir(model))
   323  	if err := writeBootChains(rpbc, installRecoveryBootChainsPath, 0); err != nil {
   324  		return err
   325  	}
   326  
   327  	return nil
   328  }
   329  
   330  func usesAltPCRHandles() (bool, error) {
   331  	saveFallbackKey := device.FallbackSaveSealedKeyUnder(InitramfsSeedEncryptionKeyDir)
   332  	// inspect the PCR handle of the ubuntu-save fallback key
   333  	handle, err := secbootPCRHandleOfSealedKey(saveFallbackKey)
   334  	if err != nil {
   335  		return false, err
   336  	}
   337  	logger.Noticef("fallback sealed key %v PCR handle: %#x", saveFallbackKey, handle)
   338  	return handle == secboot.AltFallbackObjectPCRPolicyCounterHandle, nil
   339  }
   340  
   341  func sealRunObjectKeys(key keys.EncryptionKey, pbc predictableBootChains, authKey *ecdsa.PrivateKey, roleToBlName map[bootloader.Role]string, pcrHandle uint32) error {
   342  	modelParams, err := sealKeyModelParams(pbc, roleToBlName)
   343  	if err != nil {
   344  		return fmt.Errorf("cannot prepare for key sealing: %v", err)
   345  	}
   346  
   347  	sealKeyParams := &secboot.SealKeysParams{
   348  		ModelParams:            modelParams,
   349  		TPMPolicyAuthKey:       authKey,
   350  		TPMPolicyAuthKeyFile:   filepath.Join(InstallHostFDESaveDir, "tpm-policy-auth-key"),
   351  		PCRPolicyCounterHandle: pcrHandle,
   352  	}
   353  
   354  	logger.Debugf("sealing run key with PCR handle: %#x", sealKeyParams.PCRPolicyCounterHandle)
   355  	// The run object contains only the ubuntu-data key; the ubuntu-save key
   356  	// is then stored inside the encrypted data partition, so that the normal run
   357  	// path only unseals one object because unsealing is expensive.
   358  	// Furthermore, the run object key is stored on ubuntu-boot so that we do not
   359  	// need to continually write/read keys from ubuntu-seed.
   360  	if err := secbootSealKeys(runKeySealRequests(key), sealKeyParams); err != nil {
   361  		return fmt.Errorf("cannot seal the encryption keys: %v", err)
   362  	}
   363  
   364  	return nil
   365  }
   366  
   367  func sealFallbackObjectKeys(key, saveKey keys.EncryptionKey, pbc predictableBootChains, authKey *ecdsa.PrivateKey, roleToBlName map[bootloader.Role]string, factoryReset bool, pcrHandle uint32) error {
   368  	// also seal the keys to the recovery bootchains as a fallback
   369  	modelParams, err := sealKeyModelParams(pbc, roleToBlName)
   370  	if err != nil {
   371  		return fmt.Errorf("cannot prepare for fallback key sealing: %v", err)
   372  	}
   373  	sealKeyParams := &secboot.SealKeysParams{
   374  		ModelParams:            modelParams,
   375  		TPMPolicyAuthKey:       authKey,
   376  		PCRPolicyCounterHandle: pcrHandle,
   377  	}
   378  	logger.Debugf("sealing fallback key with PCR handle: %#x", sealKeyParams.PCRPolicyCounterHandle)
   379  	// The fallback object contains the ubuntu-data and ubuntu-save keys. The
   380  	// key files are stored on ubuntu-seed, separate from ubuntu-data so they
   381  	// can be used if ubuntu-data and ubuntu-boot are corrupted or unavailable.
   382  
   383  	if err := secbootSealKeys(fallbackKeySealRequests(key, saveKey, factoryReset), sealKeyParams); err != nil {
   384  		return fmt.Errorf("cannot seal the fallback encryption keys: %v", err)
   385  	}
   386  
   387  	return nil
   388  }
   389  
   390  var resealKeyToModeenv = resealKeyToModeenvImpl
   391  
   392  // resealKeyToModeenv reseals the existing encryption key to the
   393  // parameters specified in modeenv.
   394  // It is *very intentional* that resealing takes the modeenv and only
   395  // the modeenv as input. modeenv content is well defined and updated
   396  // atomically.  In particular we want to avoid resealing against
   397  // transient/in-memory information with the risk that successive
   398  // reseals during in-progress operations produce diverging outcomes.
   399  func resealKeyToModeenvImpl(rootdir string, modeenv *Modeenv, expectReseal bool) error {
   400  	method, err := device.SealedKeysMethod(rootdir)
   401  	if err == device.ErrNoSealedKeys {
   402  		// nothing to do
   403  		return nil
   404  	}
   405  	if err != nil {
   406  		return err
   407  	}
   408  	switch method {
   409  	case device.SealingMethodFDESetupHook:
   410  		return resealKeyToModeenvUsingFDESetupHook(rootdir, modeenv, expectReseal)
   411  	case device.SealingMethodTPM, device.SealingMethodLegacyTPM:
   412  		return resealKeyToModeenvSecboot(rootdir, modeenv, expectReseal)
   413  	default:
   414  		return fmt.Errorf("unknown key sealing method: %q", method)
   415  	}
   416  }
   417  
   418  var resealKeyToModeenvUsingFDESetupHook = resealKeyToModeenvUsingFDESetupHookImpl
   419  
   420  func resealKeyToModeenvUsingFDESetupHookImpl(rootdir string, modeenv *Modeenv, expectReseal bool) error {
   421  	// TODO: we need to implement reseal at least in terms of
   422  	//       rebinding the keys to models on remodeling
   423  
   424  	// TODO: If we have situations that do TPM-like full sealing then:
   425  	//       Implement reseal using the fde-setup hook. This will
   426  	//       require a helper like "FDEShouldResealUsingSetupHook"
   427  	//       that will be set by devicestate and returns (bool,
   428  	//       error).  It needs to return "false" during seeding
   429  	//       because then there is no kernel available yet.  It
   430  	//       can though return true as soon as there's an active
   431  	//       kernel if seeded is false
   432  	//
   433  	//       It will also need to run HasFDESetupHook internally
   434  	//       and return an error if the hook goes missing
   435  	//       (e.g. because a kernel refresh losses the hook by
   436  	//       accident). It could also run features directly and
   437  	//       check for "reseal" in features.
   438  	return nil
   439  }
   440  
   441  // TODO:UC20: allow more than one model to accommodate the remodel scenario
   442  func resealKeyToModeenvSecboot(rootdir string, modeenv *Modeenv, expectReseal bool) error {
   443  	// build the recovery mode boot chain
   444  	rbl, err := bootloader.Find(InitramfsUbuntuSeedDir, &bootloader.Options{
   445  		Role: bootloader.RoleRecovery,
   446  	})
   447  	if err != nil {
   448  		return fmt.Errorf("cannot find the recovery bootloader: %v", err)
   449  	}
   450  	tbl, ok := rbl.(bootloader.TrustedAssetsBootloader)
   451  	if !ok {
   452  		// TODO:UC20: later the exact kind of bootloaders we expect here might change
   453  		return fmt.Errorf("internal error: sealed keys but not a trusted assets bootloader")
   454  	}
   455  	// derive the allowed modes for each system mentioned in the modeenv
   456  	modes := modesForSystems(modeenv)
   457  
   458  	// the recovery boot chains for the run key are generated for all
   459  	// recovery systems, including those that are being tried; since this is
   460  	// a run key, the boot chains are generated for both models to
   461  	// accommodate the dynamics of a remodel
   462  	includeTryModel := true
   463  	recoveryBootChainsForRunKey, err := recoveryBootChainsForSystems(modeenv.CurrentRecoverySystems, modes, tbl,
   464  		modeenv, includeTryModel)
   465  	if err != nil {
   466  		return fmt.Errorf("cannot compose recovery boot chains for run key: %v", err)
   467  	}
   468  
   469  	// the boot chains for recovery keys include only those system that were
   470  	// tested and are known to be good
   471  	testedRecoverySystems := modeenv.GoodRecoverySystems
   472  	if len(testedRecoverySystems) == 0 && len(modeenv.CurrentRecoverySystems) > 0 {
   473  		// compatibility for systems where good recovery systems list
   474  		// has not been populated yet
   475  		testedRecoverySystems = modeenv.CurrentRecoverySystems[:1]
   476  		logger.Noticef("no good recovery systems for reseal, fallback to known current system %v",
   477  			testedRecoverySystems[0])
   478  	}
   479  	// use the current model as the recovery keys are not expected to be
   480  	// used during a remodel
   481  	includeTryModel = false
   482  	recoveryBootChains, err := recoveryBootChainsForSystems(testedRecoverySystems, modes, tbl, modeenv, includeTryModel)
   483  	if err != nil {
   484  		return fmt.Errorf("cannot compose recovery boot chains: %v", err)
   485  	}
   486  
   487  	// build the run mode boot chains
   488  	bl, err := bootloader.Find(InitramfsUbuntuBootDir, &bootloader.Options{
   489  		Role:        bootloader.RoleRunMode,
   490  		NoSlashBoot: true,
   491  	})
   492  	if err != nil {
   493  		return fmt.Errorf("cannot find the bootloader: %v", err)
   494  	}
   495  	cmdlines, err := kernelCommandLinesForResealWithFallback(modeenv)
   496  	if err != nil {
   497  		return err
   498  	}
   499  	runModeBootChains, err := runModeBootChains(rbl, bl, modeenv, cmdlines, "")
   500  	if err != nil {
   501  		return fmt.Errorf("cannot compose run mode boot chains: %v", err)
   502  	}
   503  
   504  	roleToBlName := map[bootloader.Role]string{
   505  		bootloader.RoleRecovery: rbl.Name(),
   506  		bootloader.RoleRunMode:  bl.Name(),
   507  	}
   508  	saveFDEDir := dirs.SnapFDEDirUnderSave(dirs.SnapSaveDirUnder(rootdir))
   509  	authKeyFile := filepath.Join(saveFDEDir, "tpm-policy-auth-key")
   510  
   511  	// reseal the run object
   512  	pbc := toPredictableBootChains(append(runModeBootChains, recoveryBootChainsForRunKey...))
   513  
   514  	needed, nextCount, err := isResealNeeded(pbc, bootChainsFileUnder(rootdir), expectReseal)
   515  	if err != nil {
   516  		return err
   517  	}
   518  	if needed {
   519  		pbcJSON, _ := json.Marshal(pbc)
   520  		logger.Debugf("resealing (%d) to boot chains: %s", nextCount, pbcJSON)
   521  
   522  		if err := resealRunObjectKeys(pbc, authKeyFile, roleToBlName); err != nil {
   523  			return err
   524  		}
   525  		logger.Debugf("resealing (%d) succeeded", nextCount)
   526  
   527  		bootChainsPath := bootChainsFileUnder(rootdir)
   528  		if err := writeBootChains(pbc, bootChainsPath, nextCount); err != nil {
   529  			return err
   530  		}
   531  	} else {
   532  		logger.Debugf("reseal not necessary")
   533  	}
   534  
   535  	// reseal the fallback object
   536  	rpbc := toPredictableBootChains(recoveryBootChains)
   537  
   538  	var nextFallbackCount int
   539  	needed, nextFallbackCount, err = isResealNeeded(rpbc, recoveryBootChainsFileUnder(rootdir), expectReseal)
   540  	if err != nil {
   541  		return err
   542  	}
   543  	if needed {
   544  		rpbcJSON, _ := json.Marshal(rpbc)
   545  		logger.Debugf("resealing (%d) to recovery boot chains: %s", nextFallbackCount, rpbcJSON)
   546  
   547  		if err := resealFallbackObjectKeys(rpbc, authKeyFile, roleToBlName); err != nil {
   548  			return err
   549  		}
   550  		logger.Debugf("fallback resealing (%d) succeeded", nextFallbackCount)
   551  
   552  		recoveryBootChainsPath := recoveryBootChainsFileUnder(rootdir)
   553  		if err := writeBootChains(rpbc, recoveryBootChainsPath, nextFallbackCount); err != nil {
   554  			return err
   555  		}
   556  	} else {
   557  		logger.Debugf("fallback reseal not necessary")
   558  	}
   559  
   560  	return nil
   561  }
   562  
   563  func resealRunObjectKeys(pbc predictableBootChains, authKeyFile string, roleToBlName map[bootloader.Role]string) error {
   564  	// get model parameters from bootchains
   565  	modelParams, err := sealKeyModelParams(pbc, roleToBlName)
   566  	if err != nil {
   567  		return fmt.Errorf("cannot prepare for key resealing: %v", err)
   568  	}
   569  
   570  	// list all the key files to reseal
   571  	keyFiles := []string{device.DataSealedKeyUnder(InitramfsBootEncryptionKeyDir)}
   572  
   573  	resealKeyParams := &secboot.ResealKeysParams{
   574  		ModelParams:          modelParams,
   575  		KeyFiles:             keyFiles,
   576  		TPMPolicyAuthKeyFile: authKeyFile,
   577  	}
   578  	if err := secbootResealKeys(resealKeyParams); err != nil {
   579  		return fmt.Errorf("cannot reseal the encryption key: %v", err)
   580  	}
   581  
   582  	return nil
   583  }
   584  
   585  func resealFallbackObjectKeys(pbc predictableBootChains, authKeyFile string, roleToBlName map[bootloader.Role]string) error {
   586  	// get model parameters from bootchains
   587  	modelParams, err := sealKeyModelParams(pbc, roleToBlName)
   588  	if err != nil {
   589  		return fmt.Errorf("cannot prepare for fallback key resealing: %v", err)
   590  	}
   591  
   592  	// list all the key files to reseal
   593  	keyFiles := []string{
   594  		device.FallbackDataSealedKeyUnder(InitramfsSeedEncryptionKeyDir),
   595  		device.FallbackSaveSealedKeyUnder(InitramfsSeedEncryptionKeyDir),
   596  	}
   597  
   598  	resealKeyParams := &secboot.ResealKeysParams{
   599  		ModelParams:          modelParams,
   600  		KeyFiles:             keyFiles,
   601  		TPMPolicyAuthKeyFile: authKeyFile,
   602  	}
   603  	if err := secbootResealKeys(resealKeyParams); err != nil {
   604  		return fmt.Errorf("cannot reseal the fallback encryption keys: %v", err)
   605  	}
   606  
   607  	return nil
   608  }
   609  
   610  // recoveryModesForSystems returns a map for recovery modes for recovery systems
   611  // mentioned in the modeenv. The returned map contains both tested and candidate
   612  // recovery systems
   613  func modesForSystems(modeenv *Modeenv) map[string][]string {
   614  	if len(modeenv.GoodRecoverySystems) == 0 && len(modeenv.CurrentRecoverySystems) == 0 {
   615  		return nil
   616  	}
   617  
   618  	systemToModes := map[string][]string{}
   619  
   620  	// first go through tested recovery systems
   621  	modesForTestedSystem := []string{ModeRecover, ModeFactoryReset}
   622  	// tried systems can only boot to recovery mode
   623  	modesForCandidateSystem := []string{ModeRecover}
   624  
   625  	// go through current recovery systems which can contain both tried
   626  	// systems and candidate ones
   627  	for _, sys := range modeenv.CurrentRecoverySystems {
   628  		systemToModes[sys] = modesForCandidateSystem
   629  	}
   630  	// go through recovery systems that were tested and update their modes
   631  	for _, sys := range modeenv.GoodRecoverySystems {
   632  		systemToModes[sys] = modesForTestedSystem
   633  	}
   634  	return systemToModes
   635  }
   636  
   637  // TODO:UC20: this needs to take more than one model to accommodate the remodel
   638  // scenario
   639  func recoveryBootChainsForSystems(systems []string, modesForSystems map[string][]string, trbl bootloader.TrustedAssetsBootloader, modeenv *Modeenv, includeTryModel bool) (chains []bootChain, err error) {
   640  	chainsForModel := func(model secboot.ModelForSealing) error {
   641  		modelID := modelUniqueID(model)
   642  		for _, system := range systems {
   643  			// get kernel and gadget information from seed
   644  			perf := timings.New(nil)
   645  			seedSystemModel, snaps, err := seedReadSystemEssential(dirs.SnapSeedDir, system, []snap.Type{snap.TypeKernel, snap.TypeGadget}, perf)
   646  			if err != nil {
   647  				return fmt.Errorf("cannot read system %q seed: %v", system, err)
   648  			}
   649  			if len(snaps) != 2 {
   650  				return fmt.Errorf("cannot obtain recovery system snaps")
   651  			}
   652  			seedModelID := modelUniqueID(seedSystemModel)
   653  			// TODO: the generated unique ID contains the model's
   654  			// sign key ID, consider relaxing this to ignore the key
   655  			// ID when matching models, OTOH we would need to
   656  			// properly take into account key expiration and
   657  			// revocation
   658  			if seedModelID != modelID {
   659  				// could be an incompatible recovery system that
   660  				// is still currently tracked in modeenv
   661  				continue
   662  			}
   663  			seedKernel, seedGadget := snaps[0], snaps[1]
   664  			if snaps[0].EssentialType == snap.TypeGadget {
   665  				seedKernel, seedGadget = seedGadget, seedKernel
   666  			}
   667  
   668  			var cmdlines []string
   669  			modes, ok := modesForSystems[system]
   670  			if !ok {
   671  				return fmt.Errorf("internal error: no modes for system %q", system)
   672  			}
   673  			for _, mode := range modes {
   674  				// get the command line for this mode
   675  				cmdline, err := composeCommandLine(currentEdition, mode, system, seedGadget.Path)
   676  				if err != nil {
   677  					return fmt.Errorf("cannot obtain kernel command line for mode %q: %v", mode, err)
   678  				}
   679  				cmdlines = append(cmdlines, cmdline)
   680  			}
   681  
   682  			var kernelRev string
   683  			if seedKernel.SideInfo.Revision.Store() {
   684  				kernelRev = seedKernel.SideInfo.Revision.String()
   685  			}
   686  
   687  			recoveryBootChain, err := trbl.RecoveryBootChain(seedKernel.Path)
   688  			if err != nil {
   689  				return err
   690  			}
   691  
   692  			// get asset chains
   693  			assetChain, kbf, err := buildBootAssets(recoveryBootChain, modeenv)
   694  			if err != nil {
   695  				return err
   696  			}
   697  
   698  			chains = append(chains, bootChain{
   699  				BrandID: model.BrandID(),
   700  				Model:   model.Model(),
   701  				// TODO: test this
   702  				Classic:        model.Classic(),
   703  				Grade:          model.Grade(),
   704  				ModelSignKeyID: model.SignKeyID(),
   705  				AssetChain:     assetChain,
   706  				Kernel:         seedKernel.SnapName(),
   707  				KernelRevision: kernelRev,
   708  				KernelCmdlines: cmdlines,
   709  				kernelBootFile: kbf,
   710  			})
   711  		}
   712  		return nil
   713  	}
   714  
   715  	if err := chainsForModel(modeenv.ModelForSealing()); err != nil {
   716  		return nil, err
   717  	}
   718  
   719  	if modeenv.TryModel != "" && includeTryModel {
   720  		if err := chainsForModel(modeenv.TryModelForSealing()); err != nil {
   721  			return nil, err
   722  		}
   723  	}
   724  
   725  	return chains, nil
   726  }
   727  
   728  func runModeBootChains(rbl, bl bootloader.Bootloader, modeenv *Modeenv, cmdlines []string, runSnapsDir string) ([]bootChain, error) {
   729  	tbl, ok := rbl.(bootloader.TrustedAssetsBootloader)
   730  	if !ok {
   731  		return nil, fmt.Errorf("recovery bootloader doesn't support trusted assets")
   732  	}
   733  	chains := make([]bootChain, 0, len(modeenv.CurrentKernels))
   734  
   735  	chainsForModel := func(model secboot.ModelForSealing) error {
   736  		for _, k := range modeenv.CurrentKernels {
   737  			info, err := snap.ParsePlaceInfoFromSnapFileName(k)
   738  			if err != nil {
   739  				return err
   740  			}
   741  			var kernelPath string
   742  			if runSnapsDir == "" {
   743  				kernelPath = info.MountFile()
   744  			} else {
   745  				kernelPath = filepath.Join(runSnapsDir, info.Filename())
   746  			}
   747  			runModeBootChain, err := tbl.BootChain(bl, kernelPath)
   748  			if err != nil {
   749  				return err
   750  			}
   751  
   752  			// get asset chains
   753  			assetChain, kbf, err := buildBootAssets(runModeBootChain, modeenv)
   754  			if err != nil {
   755  				return err
   756  			}
   757  			var kernelRev string
   758  			if info.SnapRevision().Store() {
   759  				kernelRev = info.SnapRevision().String()
   760  			}
   761  			chains = append(chains, bootChain{
   762  				BrandID: model.BrandID(),
   763  				Model:   model.Model(),
   764  				// TODO: test this
   765  				Classic:        model.Classic(),
   766  				Grade:          model.Grade(),
   767  				ModelSignKeyID: model.SignKeyID(),
   768  				AssetChain:     assetChain,
   769  				Kernel:         info.SnapName(),
   770  				KernelRevision: kernelRev,
   771  				KernelCmdlines: cmdlines,
   772  				kernelBootFile: kbf,
   773  			})
   774  		}
   775  		return nil
   776  	}
   777  	if err := chainsForModel(modeenv.ModelForSealing()); err != nil {
   778  		return nil, err
   779  	}
   780  
   781  	if modeenv.TryModel != "" {
   782  		if err := chainsForModel(modeenv.TryModelForSealing()); err != nil {
   783  			return nil, err
   784  		}
   785  	}
   786  	return chains, nil
   787  }
   788  
   789  // buildBootAssets takes the BootFiles of a bootloader boot chain and
   790  // produces corresponding bootAssets with the matching current asset
   791  // hashes from modeenv plus it returns separately the last BootFile
   792  // which is for the kernel.
   793  func buildBootAssets(bootFiles []bootloader.BootFile, modeenv *Modeenv) (assets []bootAsset, kernel bootloader.BootFile, err error) {
   794  	if len(bootFiles) == 0 {
   795  		// useful in testing, when mocking is insufficient
   796  		return nil, bootloader.BootFile{}, fmt.Errorf("internal error: cannot build boot assets without boot files")
   797  	}
   798  	assets = make([]bootAsset, len(bootFiles)-1)
   799  
   800  	// the last element is the kernel which is not a boot asset
   801  	for i, bf := range bootFiles[:len(bootFiles)-1] {
   802  		name := filepath.Base(bf.Path)
   803  		var hashes []string
   804  		var ok bool
   805  		if bf.Role == bootloader.RoleRecovery {
   806  			hashes, ok = modeenv.CurrentTrustedRecoveryBootAssets[name]
   807  		} else {
   808  			hashes, ok = modeenv.CurrentTrustedBootAssets[name]
   809  		}
   810  		if !ok {
   811  			return nil, kernel, fmt.Errorf("cannot find expected boot asset %s in modeenv", name)
   812  		}
   813  		assets[i] = bootAsset{
   814  			Role:   bf.Role,
   815  			Name:   name,
   816  			Hashes: hashes,
   817  		}
   818  	}
   819  
   820  	return assets, bootFiles[len(bootFiles)-1], nil
   821  }
   822  
   823  func sealKeyModelParams(pbc predictableBootChains, roleToBlName map[bootloader.Role]string) ([]*secboot.SealKeyModelParams, error) {
   824  	// seal parameters keyed by unique model ID
   825  	modelToParams := map[string]*secboot.SealKeyModelParams{}
   826  	modelParams := make([]*secboot.SealKeyModelParams, 0, len(pbc))
   827  
   828  	for _, bc := range pbc {
   829  		modelForSealing := bc.modelForSealing()
   830  		modelID := modelUniqueID(modelForSealing)
   831  		loadChains, err := bootAssetsToLoadChains(bc.AssetChain, bc.kernelBootFile, roleToBlName)
   832  		if err != nil {
   833  			return nil, fmt.Errorf("cannot build load chains with current boot assets: %s", err)
   834  		}
   835  
   836  		// group parameters by model, reuse an existing SealKeyModelParams
   837  		// if the model is the same.
   838  		if params, ok := modelToParams[modelID]; ok {
   839  			params.KernelCmdlines = strutil.SortedListsUniqueMerge(params.KernelCmdlines, bc.KernelCmdlines)
   840  			params.EFILoadChains = append(params.EFILoadChains, loadChains...)
   841  		} else {
   842  			param := &secboot.SealKeyModelParams{
   843  				Model:          modelForSealing,
   844  				KernelCmdlines: bc.KernelCmdlines,
   845  				EFILoadChains:  loadChains,
   846  			}
   847  			modelParams = append(modelParams, param)
   848  			modelToParams[modelID] = param
   849  		}
   850  	}
   851  
   852  	return modelParams, nil
   853  }
   854  
   855  // isResealNeeded returns true when the predictable boot chains provided as
   856  // input do not match the cached boot chains on disk under rootdir.
   857  // It also returns the next value for the reseal count that is saved
   858  // together with the boot chains.
   859  // A hint expectReseal can be provided, it is used when the matching
   860  // is ambigous because the boot chains contain unrevisioned kernels.
   861  func isResealNeeded(pbc predictableBootChains, bootChainsFile string, expectReseal bool) (ok bool, nextCount int, err error) {
   862  	previousPbc, c, err := readBootChains(bootChainsFile)
   863  	if err != nil {
   864  		return false, 0, err
   865  	}
   866  
   867  	switch predictableBootChainsEqualForReseal(pbc, previousPbc) {
   868  	case bootChainEquivalent:
   869  		return false, c + 1, nil
   870  	case bootChainUnrevisioned:
   871  		return expectReseal, c + 1, nil
   872  	case bootChainDifferent:
   873  	}
   874  	return true, c + 1, nil
   875  }
   876  
   877  func postFactoryResetCleanupSecboot() error {
   878  	// we are inspecting a key which was generated during factory reset, in
   879  	// the simplest case the sealed key generated previously used the main
   880  	// handles, while the current key uses alt handles, hence we need to
   881  	// release the main handles corresponding to the old key
   882  	handles := []uint32{secboot.RunObjectPCRPolicyCounterHandle, secboot.FallbackObjectPCRPolicyCounterHandle}
   883  	usesAlt, err := usesAltPCRHandles()
   884  	if err != nil {
   885  		return fmt.Errorf("cannot inspect fallback key: %v", err)
   886  	}
   887  	if !usesAlt {
   888  		// current fallback key using the main handles, which is
   889  		// possible of there were subsequent factory reset steps,
   890  		// release the alt handles associated with the old key
   891  		handles = []uint32{secboot.AltRunObjectPCRPolicyCounterHandle, secboot.AltFallbackObjectPCRPolicyCounterHandle}
   892  	}
   893  	return secbootReleasePCRResourceHandles(handles...)
   894  }
   895  
   896  func postFactoryResetCleanup() error {
   897  	hasHook, err := HasFDESetupHook(nil)
   898  	if err != nil {
   899  		return fmt.Errorf("cannot check for fde-setup hook %v", err)
   900  	}
   901  
   902  	saveFallbackKeyFactory := device.FactoryResetFallbackSaveSealedKeyUnder(InitramfsSeedEncryptionKeyDir)
   903  	saveFallbackKey := device.FallbackSaveSealedKeyUnder(InitramfsSeedEncryptionKeyDir)
   904  	if err := os.Rename(saveFallbackKeyFactory, saveFallbackKey); err != nil {
   905  		// it is possible that the key file was already renamed if we
   906  		// came back here after an unexpected reboot
   907  		if !os.IsNotExist(err) {
   908  			return fmt.Errorf("cannot rotate fallback key: %v", err)
   909  		}
   910  	}
   911  
   912  	if hasHook {
   913  		// TODO: do we need to invoke FDE hook?
   914  		return nil
   915  	}
   916  
   917  	if err := postFactoryResetCleanupSecboot(); err != nil {
   918  		return fmt.Errorf("cannot cleanup secboot state: %v", err)
   919  	}
   920  
   921  	return nil
   922  }
   923  
   924  // resealExpectedByModeenvChange returns true if resealing is expected
   925  // due to modeenv changes, false otherwise. Reseal might not be needed
   926  // if the only change in modeenv is the gadget (if the boot assets
   927  // change that is detected in resealKeyToModeenv() and reseal will
   928  // happen anyway)
   929  func resealExpectedByModeenvChange(m1, m2 *Modeenv) bool {
   930  	auxModeenv := *m2
   931  	auxModeenv.Gadget = m1.Gadget
   932  	return !auxModeenv.deepEqual(m1)
   933  }