github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/boot/seal.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2020 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  	"errors"
    28  	"fmt"
    29  	"io/ioutil"
    30  	"os"
    31  	"path/filepath"
    32  
    33  	"github.com/snapcore/snapd/asserts"
    34  	"github.com/snapcore/snapd/bootloader"
    35  	"github.com/snapcore/snapd/dirs"
    36  	"github.com/snapcore/snapd/kernel/fde"
    37  	"github.com/snapcore/snapd/logger"
    38  	"github.com/snapcore/snapd/osutil"
    39  	"github.com/snapcore/snapd/secboot"
    40  	"github.com/snapcore/snapd/seed"
    41  	"github.com/snapcore/snapd/snap"
    42  	"github.com/snapcore/snapd/strutil"
    43  	"github.com/snapcore/snapd/timings"
    44  )
    45  
    46  var (
    47  	secbootSealKeys                 = secboot.SealKeys
    48  	secbootSealKeysWithFDESetupHook = secboot.SealKeysWithFDESetupHook
    49  	secbootResealKeys               = secboot.ResealKeys
    50  
    51  	seedReadSystemEssential = seed.ReadSystemEssential
    52  )
    53  
    54  // Hook functions setup by devicestate to support device-specific full
    55  // disk encryption implementations. The state must be locked when these
    56  // functions are called.
    57  var (
    58  	HasFDESetupHook = func() (bool, error) {
    59  		return false, nil
    60  	}
    61  	RunFDESetupHook fde.RunSetupHookFunc = func(req *fde.SetupRequest) ([]byte, error) {
    62  		return nil, fmt.Errorf("internal error: RunFDESetupHook not set yet")
    63  	}
    64  )
    65  
    66  type sealingMethod string
    67  
    68  const (
    69  	sealingMethodLegacyTPM    = sealingMethod("")
    70  	sealingMethodTPM          = sealingMethod("tpm")
    71  	sealingMethodFDESetupHook = sealingMethod("fde-setup-hook")
    72  )
    73  
    74  // MockSecbootResealKeys is only useful in testing. Note that this is a very low
    75  // level call and may need significant environment setup.
    76  func MockSecbootResealKeys(f func(params *secboot.ResealKeysParams) error) (restore func()) {
    77  	osutil.MustBeTestBinary("secbootResealKeys only can be mocked in tests")
    78  	old := secbootResealKeys
    79  	secbootResealKeys = f
    80  	return func() {
    81  		secbootResealKeys = old
    82  	}
    83  }
    84  
    85  // MockResealKeyToModeenv is only useful in testing.
    86  func MockResealKeyToModeenv(f func(rootdir string, modeenv *Modeenv, expectReseal bool) error) (restore func()) {
    87  	osutil.MustBeTestBinary("resealKeyToModeenv only can be mocked in tests")
    88  	old := resealKeyToModeenv
    89  	resealKeyToModeenv = f
    90  	return func() {
    91  		resealKeyToModeenv = old
    92  	}
    93  }
    94  
    95  func bootChainsFileUnder(rootdir string) string {
    96  	return filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains")
    97  }
    98  
    99  func recoveryBootChainsFileUnder(rootdir string) string {
   100  	return filepath.Join(dirs.SnapFDEDirUnder(rootdir), "recovery-boot-chains")
   101  }
   102  
   103  // sealKeyToModeenv seals the supplied keys to the parameters specified
   104  // in modeenv.
   105  // It assumes to be invoked in install mode.
   106  func sealKeyToModeenv(key, saveKey secboot.EncryptionKey, model *asserts.Model, modeenv *Modeenv) error {
   107  	// make sure relevant locations exist
   108  	for _, p := range []string{
   109  		InitramfsSeedEncryptionKeyDir,
   110  		InitramfsBootEncryptionKeyDir,
   111  		InstallHostFDEDataDir,
   112  		InstallHostFDESaveDir,
   113  	} {
   114  		// XXX: should that be 0700 ?
   115  		if err := os.MkdirAll(p, 0755); err != nil {
   116  			return err
   117  		}
   118  	}
   119  
   120  	hasHook, err := HasFDESetupHook()
   121  	if err != nil {
   122  		return fmt.Errorf("cannot check for fde-setup hook %v", err)
   123  	}
   124  	if hasHook {
   125  		return sealKeyToModeenvUsingFDESetupHook(key, saveKey, modeenv)
   126  	}
   127  
   128  	return sealKeyToModeenvUsingSecboot(key, saveKey, modeenv)
   129  }
   130  
   131  func runKeySealRequests(key secboot.EncryptionKey) []secboot.SealKeyRequest {
   132  	return []secboot.SealKeyRequest{
   133  		{
   134  			Key:     key,
   135  			KeyName: "ubuntu-data",
   136  			KeyFile: filepath.Join(InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
   137  		},
   138  	}
   139  }
   140  
   141  func fallbackKeySealRequests(key, saveKey secboot.EncryptionKey) []secboot.SealKeyRequest {
   142  	return []secboot.SealKeyRequest{
   143  		{
   144  			Key:     key,
   145  			KeyName: "ubuntu-data",
   146  			KeyFile: filepath.Join(InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
   147  		},
   148  		{
   149  			Key:     saveKey,
   150  			KeyName: "ubuntu-save",
   151  			KeyFile: filepath.Join(InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
   152  		},
   153  	}
   154  }
   155  
   156  func sealKeyToModeenvUsingFDESetupHook(key, saveKey secboot.EncryptionKey, modeenv *Modeenv) error {
   157  	// XXX: Move the auxKey creation to a more generic place, see
   158  	// PR#10123 for a possible way of doing this. However given
   159  	// that the equivalent key for the TPM case is also created in
   160  	// sealKeyToModeenvUsingTPM more symetric to create the auxKey
   161  	// here and when we also move TPM to use the auxKey to move
   162  	// the creation of it.
   163  	auxKey, err := secboot.NewAuxKey()
   164  	if err != nil {
   165  		return fmt.Errorf("cannot create aux key: %v", err)
   166  	}
   167  	params := secboot.SealKeysWithFDESetupHookParams{
   168  		Model:      modeenv.ModelForSealing(),
   169  		AuxKey:     auxKey,
   170  		AuxKeyFile: filepath.Join(InstallHostFDESaveDir, "aux-key"),
   171  	}
   172  	skrs := append(runKeySealRequests(key), fallbackKeySealRequests(key, saveKey)...)
   173  	if err := secbootSealKeysWithFDESetupHook(RunFDESetupHook, skrs, &params); err != nil {
   174  		return err
   175  	}
   176  
   177  	if err := stampSealedKeys(InstallHostWritableDir, "fde-setup-hook"); err != nil {
   178  		return err
   179  	}
   180  
   181  	return nil
   182  }
   183  
   184  func sealKeyToModeenvUsingSecboot(key, saveKey secboot.EncryptionKey, modeenv *Modeenv) error {
   185  	// build the recovery mode boot chain
   186  	rbl, err := bootloader.Find(InitramfsUbuntuSeedDir, &bootloader.Options{
   187  		Role: bootloader.RoleRecovery,
   188  	})
   189  	if err != nil {
   190  		return fmt.Errorf("cannot find the recovery bootloader: %v", err)
   191  	}
   192  	tbl, ok := rbl.(bootloader.TrustedAssetsBootloader)
   193  	if !ok {
   194  		// TODO:UC20: later the exact kind of bootloaders we expect here might change
   195  		return fmt.Errorf("internal error: cannot seal keys without a trusted assets bootloader")
   196  	}
   197  
   198  	includeTryModel := false
   199  	recoveryBootChains, err := recoveryBootChainsForSystems([]string{modeenv.RecoverySystem}, tbl, modeenv, includeTryModel)
   200  	if err != nil {
   201  		return fmt.Errorf("cannot compose recovery boot chains: %v", err)
   202  	}
   203  
   204  	// build the run mode boot chains
   205  	bl, err := bootloader.Find(InitramfsUbuntuBootDir, &bootloader.Options{
   206  		Role:        bootloader.RoleRunMode,
   207  		NoSlashBoot: true,
   208  	})
   209  	if err != nil {
   210  		return fmt.Errorf("cannot find the bootloader: %v", err)
   211  	}
   212  
   213  	// kernel command lines are filled during install
   214  	cmdlines := modeenv.CurrentKernelCommandLines
   215  	runModeBootChains, err := runModeBootChains(rbl, bl, modeenv, cmdlines)
   216  	if err != nil {
   217  		return fmt.Errorf("cannot compose run mode boot chains: %v", err)
   218  	}
   219  
   220  	pbc := toPredictableBootChains(append(runModeBootChains, recoveryBootChains...))
   221  
   222  	roleToBlName := map[bootloader.Role]string{
   223  		bootloader.RoleRecovery: rbl.Name(),
   224  		bootloader.RoleRunMode:  bl.Name(),
   225  	}
   226  
   227  	// the boot chains we seal the fallback object to
   228  	rpbc := toPredictableBootChains(recoveryBootChains)
   229  
   230  	// gets written to a file by sealRunObjectKeys()
   231  	authKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
   232  	if err != nil {
   233  		return fmt.Errorf("cannot generate key for signing dynamic authorization policies: %v", err)
   234  	}
   235  
   236  	if err := sealRunObjectKeys(key, pbc, authKey, roleToBlName); err != nil {
   237  		return err
   238  	}
   239  
   240  	if err := sealFallbackObjectKeys(key, saveKey, rpbc, authKey, roleToBlName); err != nil {
   241  		return err
   242  	}
   243  
   244  	if err := stampSealedKeys(InstallHostWritableDir, sealingMethodTPM); err != nil {
   245  		return err
   246  	}
   247  
   248  	installBootChainsPath := bootChainsFileUnder(InstallHostWritableDir)
   249  	if err := writeBootChains(pbc, installBootChainsPath, 0); err != nil {
   250  		return err
   251  	}
   252  
   253  	installRecoveryBootChainsPath := recoveryBootChainsFileUnder(InstallHostWritableDir)
   254  	if err := writeBootChains(rpbc, installRecoveryBootChainsPath, 0); err != nil {
   255  		return err
   256  	}
   257  
   258  	return nil
   259  }
   260  
   261  func sealRunObjectKeys(key secboot.EncryptionKey, pbc predictableBootChains, authKey *ecdsa.PrivateKey, roleToBlName map[bootloader.Role]string) error {
   262  	modelParams, err := sealKeyModelParams(pbc, roleToBlName)
   263  	if err != nil {
   264  		return fmt.Errorf("cannot prepare for key sealing: %v", err)
   265  	}
   266  
   267  	sealKeyParams := &secboot.SealKeysParams{
   268  		ModelParams:            modelParams,
   269  		TPMPolicyAuthKey:       authKey,
   270  		TPMPolicyAuthKeyFile:   filepath.Join(InstallHostFDESaveDir, "tpm-policy-auth-key"),
   271  		TPMLockoutAuthFile:     filepath.Join(InstallHostFDESaveDir, "tpm-lockout-auth"),
   272  		TPMProvision:           true,
   273  		PCRPolicyCounterHandle: secboot.RunObjectPCRPolicyCounterHandle,
   274  	}
   275  	// The run object contains only the ubuntu-data key; the ubuntu-save key
   276  	// is then stored inside the encrypted data partition, so that the normal run
   277  	// path only unseals one object because unsealing is expensive.
   278  	// Furthermore, the run object key is stored on ubuntu-boot so that we do not
   279  	// need to continually write/read keys from ubuntu-seed.
   280  	if err := secbootSealKeys(runKeySealRequests(key), sealKeyParams); err != nil {
   281  		return fmt.Errorf("cannot seal the encryption keys: %v", err)
   282  	}
   283  
   284  	return nil
   285  }
   286  
   287  func sealFallbackObjectKeys(key, saveKey secboot.EncryptionKey, pbc predictableBootChains, authKey *ecdsa.PrivateKey, roleToBlName map[bootloader.Role]string) error {
   288  	// also seal the keys to the recovery bootchains as a fallback
   289  	modelParams, err := sealKeyModelParams(pbc, roleToBlName)
   290  	if err != nil {
   291  		return fmt.Errorf("cannot prepare for fallback key sealing: %v", err)
   292  	}
   293  	sealKeyParams := &secboot.SealKeysParams{
   294  		ModelParams:            modelParams,
   295  		TPMPolicyAuthKey:       authKey,
   296  		PCRPolicyCounterHandle: secboot.FallbackObjectPCRPolicyCounterHandle,
   297  	}
   298  	// The fallback object contains the ubuntu-data and ubuntu-save keys. The
   299  	// key files are stored on ubuntu-seed, separate from ubuntu-data so they
   300  	// can be used if ubuntu-data and ubuntu-boot are corrupted or unavailable.
   301  	if err := secbootSealKeys(fallbackKeySealRequests(key, saveKey), sealKeyParams); err != nil {
   302  		return fmt.Errorf("cannot seal the fallback encryption keys: %v", err)
   303  	}
   304  
   305  	return nil
   306  }
   307  
   308  func stampSealedKeys(rootdir string, content sealingMethod) error {
   309  	stamp := filepath.Join(dirs.SnapFDEDirUnder(rootdir), "sealed-keys")
   310  	if err := os.MkdirAll(filepath.Dir(stamp), 0755); err != nil {
   311  		return fmt.Errorf("cannot create device fde state directory: %v", err)
   312  	}
   313  
   314  	if err := osutil.AtomicWriteFile(stamp, []byte(content), 0644, 0); err != nil {
   315  		return fmt.Errorf("cannot create fde sealed keys stamp file: %v", err)
   316  	}
   317  	return nil
   318  }
   319  
   320  var errNoSealedKeys = errors.New("no sealed keys")
   321  
   322  // sealedKeysMethod return whether any keys were sealed at all
   323  func sealedKeysMethod(rootdir string) (sm sealingMethod, err error) {
   324  	// TODO:UC20: consider more than the marker for cases where we reseal
   325  	// outside of run mode
   326  	stamp := filepath.Join(dirs.SnapFDEDirUnder(rootdir), "sealed-keys")
   327  	content, err := ioutil.ReadFile(stamp)
   328  	if os.IsNotExist(err) {
   329  		return sm, errNoSealedKeys
   330  	}
   331  	return sealingMethod(content), err
   332  }
   333  
   334  var resealKeyToModeenv = resealKeyToModeenvImpl
   335  
   336  // resealKeyToModeenv reseals the existing encryption key to the
   337  // parameters specified in modeenv.
   338  func resealKeyToModeenvImpl(rootdir string, modeenv *Modeenv, expectReseal bool) error {
   339  	method, err := sealedKeysMethod(rootdir)
   340  	if err == errNoSealedKeys {
   341  		// nothing to do
   342  		return nil
   343  	}
   344  	if err != nil {
   345  		return err
   346  	}
   347  	switch method {
   348  	case sealingMethodFDESetupHook:
   349  		return resealKeyToModeenvUsingFDESetupHook(rootdir, modeenv, expectReseal)
   350  	case sealingMethodTPM, sealingMethodLegacyTPM:
   351  		return resealKeyToModeenvSecboot(rootdir, modeenv, expectReseal)
   352  	default:
   353  		return fmt.Errorf("unknown key sealing method: %q", method)
   354  	}
   355  }
   356  
   357  var resealKeyToModeenvUsingFDESetupHook = resealKeyToModeenvUsingFDESetupHookImpl
   358  
   359  func resealKeyToModeenvUsingFDESetupHookImpl(rootdir string, modeenv *Modeenv, expectReseal bool) error {
   360  	// TODO: we need to implement reseal at least in terms of
   361  	//       rebinding the keys to models on remodeling
   362  
   363  	// TODO: If we have situations that do TPM-like full sealing then:
   364  	//       Implement reseal using the fde-setup hook. This will
   365  	//       require a helper like "FDEShouldResealUsingSetupHook"
   366  	//       that will be set by devicestate and returns (bool,
   367  	//       error).  It needs to return "false" during seeding
   368  	//       because then there is no kernel available yet.  It
   369  	//       can though return true as soon as there's an active
   370  	//       kernel if seeded is false
   371  	//
   372  	//       It will also need to run HasFDESetupHook internally
   373  	//       and return an error if the hook goes missing
   374  	//       (e.g. because a kernel refresh losses the hook by
   375  	//       accident). It could also run features directly and
   376  	//       check for "reseal" in features.
   377  	return nil
   378  }
   379  
   380  // TODO:UC20: allow more than one model to accommodate the remodel scenario
   381  func resealKeyToModeenvSecboot(rootdir string, modeenv *Modeenv, expectReseal bool) error {
   382  	// build the recovery mode boot chain
   383  	rbl, err := bootloader.Find(InitramfsUbuntuSeedDir, &bootloader.Options{
   384  		Role: bootloader.RoleRecovery,
   385  	})
   386  	if err != nil {
   387  		return fmt.Errorf("cannot find the recovery bootloader: %v", err)
   388  	}
   389  	tbl, ok := rbl.(bootloader.TrustedAssetsBootloader)
   390  	if !ok {
   391  		// TODO:UC20: later the exact kind of bootloaders we expect here might change
   392  		return fmt.Errorf("internal error: sealed keys but not a trusted assets bootloader")
   393  	}
   394  
   395  	// the recovery boot chains for the run key are generated for all
   396  	// recovery systems, including those that are being tried; since this is
   397  	// a run key, the boot chains are generated for both models to
   398  	// accommodate the dynamics of a remodel
   399  	includeTryModel := true
   400  	recoveryBootChainsForRunKey, err := recoveryBootChainsForSystems(modeenv.CurrentRecoverySystems, tbl,
   401  		modeenv, includeTryModel)
   402  	if err != nil {
   403  		return fmt.Errorf("cannot compose recovery boot chains for run key: %v", err)
   404  	}
   405  
   406  	// the boot chains for recovery keys include only those system that were
   407  	// tested and are known to be good
   408  	testedRecoverySystems := modeenv.GoodRecoverySystems
   409  	if len(testedRecoverySystems) == 0 && len(modeenv.CurrentRecoverySystems) > 0 {
   410  		// compatibility for systems where good recovery systems list
   411  		// has not been populated yet
   412  		testedRecoverySystems = modeenv.CurrentRecoverySystems[:1]
   413  		logger.Noticef("no good recovery systems for reseal, fallback to known current system %v",
   414  			testedRecoverySystems[0])
   415  	}
   416  	// use the current model as the recovery keys are not expected to be
   417  	// used during a remodel
   418  	includeTryModel = false
   419  	recoveryBootChains, err := recoveryBootChainsForSystems(testedRecoverySystems, tbl, modeenv, includeTryModel)
   420  	if err != nil {
   421  		return fmt.Errorf("cannot compose recovery boot chains: %v", err)
   422  	}
   423  
   424  	// build the run mode boot chains
   425  	bl, err := bootloader.Find(InitramfsUbuntuBootDir, &bootloader.Options{
   426  		Role:        bootloader.RoleRunMode,
   427  		NoSlashBoot: true,
   428  	})
   429  	if err != nil {
   430  		return fmt.Errorf("cannot find the bootloader: %v", err)
   431  	}
   432  	cmdlines, err := kernelCommandLinesForResealWithFallback(modeenv)
   433  	if err != nil {
   434  		return err
   435  	}
   436  	runModeBootChains, err := runModeBootChains(rbl, bl, modeenv, cmdlines)
   437  	if err != nil {
   438  		return fmt.Errorf("cannot compose run mode boot chains: %v", err)
   439  	}
   440  
   441  	roleToBlName := map[bootloader.Role]string{
   442  		bootloader.RoleRecovery: rbl.Name(),
   443  		bootloader.RoleRunMode:  bl.Name(),
   444  	}
   445  	saveFDEDir := dirs.SnapFDEDirUnderSave(dirs.SnapSaveDirUnder(rootdir))
   446  	authKeyFile := filepath.Join(saveFDEDir, "tpm-policy-auth-key")
   447  
   448  	// reseal the run object
   449  	pbc := toPredictableBootChains(append(runModeBootChains, recoveryBootChainsForRunKey...))
   450  
   451  	needed, nextCount, err := isResealNeeded(pbc, bootChainsFileUnder(rootdir), expectReseal)
   452  	if err != nil {
   453  		return err
   454  	}
   455  	if needed {
   456  		pbcJSON, _ := json.Marshal(pbc)
   457  		logger.Debugf("resealing (%d) to boot chains: %s", nextCount, pbcJSON)
   458  
   459  		if err := resealRunObjectKeys(pbc, authKeyFile, roleToBlName); err != nil {
   460  			return err
   461  		}
   462  		logger.Debugf("resealing (%d) succeeded", nextCount)
   463  
   464  		bootChainsPath := bootChainsFileUnder(rootdir)
   465  		if err := writeBootChains(pbc, bootChainsPath, nextCount); err != nil {
   466  			return err
   467  		}
   468  	} else {
   469  		logger.Debugf("reseal not necessary")
   470  	}
   471  
   472  	// reseal the fallback object
   473  	rpbc := toPredictableBootChains(recoveryBootChains)
   474  
   475  	var nextFallbackCount int
   476  	needed, nextFallbackCount, err = isResealNeeded(rpbc, recoveryBootChainsFileUnder(rootdir), expectReseal)
   477  	if err != nil {
   478  		return err
   479  	}
   480  	if needed {
   481  		rpbcJSON, _ := json.Marshal(rpbc)
   482  		logger.Debugf("resealing (%d) to recovery boot chains: %s", nextFallbackCount, rpbcJSON)
   483  
   484  		if err := resealFallbackObjectKeys(rpbc, authKeyFile, roleToBlName); err != nil {
   485  			return err
   486  		}
   487  		logger.Debugf("fallback resealing (%d) succeeded", nextFallbackCount)
   488  
   489  		recoveryBootChainsPath := recoveryBootChainsFileUnder(rootdir)
   490  		if err := writeBootChains(rpbc, recoveryBootChainsPath, nextFallbackCount); err != nil {
   491  			return err
   492  		}
   493  	} else {
   494  		logger.Debugf("fallback reseal not necessary")
   495  	}
   496  
   497  	return nil
   498  }
   499  
   500  func resealRunObjectKeys(pbc predictableBootChains, authKeyFile string, roleToBlName map[bootloader.Role]string) error {
   501  	// get model parameters from bootchains
   502  	modelParams, err := sealKeyModelParams(pbc, roleToBlName)
   503  	if err != nil {
   504  		return fmt.Errorf("cannot prepare for key resealing: %v", err)
   505  	}
   506  
   507  	// list all the key files to reseal
   508  	keyFiles := []string{
   509  		filepath.Join(InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
   510  	}
   511  
   512  	resealKeyParams := &secboot.ResealKeysParams{
   513  		ModelParams:          modelParams,
   514  		KeyFiles:             keyFiles,
   515  		TPMPolicyAuthKeyFile: authKeyFile,
   516  	}
   517  	if err := secbootResealKeys(resealKeyParams); err != nil {
   518  		return fmt.Errorf("cannot reseal the encryption key: %v", err)
   519  	}
   520  
   521  	return nil
   522  }
   523  
   524  func resealFallbackObjectKeys(pbc predictableBootChains, authKeyFile string, roleToBlName map[bootloader.Role]string) error {
   525  	// get model parameters from bootchains
   526  	modelParams, err := sealKeyModelParams(pbc, roleToBlName)
   527  	if err != nil {
   528  		return fmt.Errorf("cannot prepare for fallback key resealing: %v", err)
   529  	}
   530  
   531  	// list all the key files to reseal
   532  	keyFiles := []string{
   533  		filepath.Join(InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
   534  		filepath.Join(InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
   535  	}
   536  
   537  	resealKeyParams := &secboot.ResealKeysParams{
   538  		ModelParams:          modelParams,
   539  		KeyFiles:             keyFiles,
   540  		TPMPolicyAuthKeyFile: authKeyFile,
   541  	}
   542  	if err := secbootResealKeys(resealKeyParams); err != nil {
   543  		return fmt.Errorf("cannot reseal the fallback encryption keys: %v", err)
   544  	}
   545  
   546  	return nil
   547  }
   548  
   549  // TODO:UC20: this needs to take more than one model to accommodate the remodel
   550  // scenario
   551  func recoveryBootChainsForSystems(systems []string, trbl bootloader.TrustedAssetsBootloader, modeenv *Modeenv, includeTryModel bool) (chains []bootChain, err error) {
   552  
   553  	chainsForModel := func(model secboot.ModelForSealing) error {
   554  		modelID := modelUniqueID(model)
   555  		for _, system := range systems {
   556  			// get kernel and gadget information from seed
   557  			perf := timings.New(nil)
   558  			seedSystemModel, snaps, err := seedReadSystemEssential(dirs.SnapSeedDir, system, []snap.Type{snap.TypeKernel, snap.TypeGadget}, perf)
   559  			if err != nil {
   560  				return fmt.Errorf("cannot read system %q seed: %v", system, err)
   561  			}
   562  			if len(snaps) != 2 {
   563  				return fmt.Errorf("cannot obtain recovery system snaps")
   564  			}
   565  			seedModelID := modelUniqueID(seedSystemModel)
   566  			// TODO: the generated unique ID contains the model's
   567  			// sign key ID, consider relaxing this to ignore the key
   568  			// ID when matching models, OTOH we would need to
   569  			// properly take into account key expiration and
   570  			// revocation
   571  			if seedModelID != modelID {
   572  				// could be an incompatible recovery system that
   573  				// is still currently tracked in modeenv
   574  				continue
   575  			}
   576  			seedKernel, seedGadget := snaps[0], snaps[1]
   577  			if snaps[0].EssentialType == snap.TypeGadget {
   578  				seedKernel, seedGadget = seedGadget, seedKernel
   579  			}
   580  
   581  			// get the command line
   582  			cmdline, err := composeCommandLine(currentEdition, ModeRecover, system, seedGadget.Path)
   583  			if err != nil {
   584  				return fmt.Errorf("cannot obtain recovery kernel command line: %v", err)
   585  			}
   586  
   587  			var kernelRev string
   588  			if seedKernel.SideInfo.Revision.Store() {
   589  				kernelRev = seedKernel.SideInfo.Revision.String()
   590  			}
   591  
   592  			recoveryBootChain, err := trbl.RecoveryBootChain(seedKernel.Path)
   593  			if err != nil {
   594  				return err
   595  			}
   596  
   597  			// get asset chains
   598  			assetChain, kbf, err := buildBootAssets(recoveryBootChain, modeenv)
   599  			if err != nil {
   600  				return err
   601  			}
   602  
   603  			chains = append(chains, bootChain{
   604  				BrandID:        model.BrandID(),
   605  				Model:          model.Model(),
   606  				Grade:          model.Grade(),
   607  				ModelSignKeyID: model.SignKeyID(),
   608  				AssetChain:     assetChain,
   609  				Kernel:         seedKernel.SnapName(),
   610  				KernelRevision: kernelRev,
   611  				KernelCmdlines: []string{cmdline},
   612  				kernelBootFile: kbf,
   613  			})
   614  		}
   615  		return nil
   616  	}
   617  
   618  	if err := chainsForModel(modeenv.ModelForSealing()); err != nil {
   619  		return nil, err
   620  	}
   621  
   622  	if modeenv.TryModel != "" && includeTryModel {
   623  		if err := chainsForModel(modeenv.TryModelForSealing()); err != nil {
   624  			return nil, err
   625  		}
   626  	}
   627  
   628  	return chains, nil
   629  }
   630  
   631  func runModeBootChains(rbl, bl bootloader.Bootloader, modeenv *Modeenv, cmdlines []string) ([]bootChain, error) {
   632  	tbl, ok := rbl.(bootloader.TrustedAssetsBootloader)
   633  	if !ok {
   634  		return nil, fmt.Errorf("recovery bootloader doesn't support trusted assets")
   635  	}
   636  	chains := make([]bootChain, 0, len(modeenv.CurrentKernels))
   637  
   638  	chainsForModel := func(model secboot.ModelForSealing) error {
   639  		for _, k := range modeenv.CurrentKernels {
   640  			info, err := snap.ParsePlaceInfoFromSnapFileName(k)
   641  			if err != nil {
   642  				return err
   643  			}
   644  			runModeBootChain, err := tbl.BootChain(bl, info.MountFile())
   645  			if err != nil {
   646  				return err
   647  			}
   648  
   649  			// get asset chains
   650  			assetChain, kbf, err := buildBootAssets(runModeBootChain, modeenv)
   651  			if err != nil {
   652  				return err
   653  			}
   654  			var kernelRev string
   655  			if info.SnapRevision().Store() {
   656  				kernelRev = info.SnapRevision().String()
   657  			}
   658  			chains = append(chains, bootChain{
   659  				BrandID:        model.BrandID(),
   660  				Model:          model.Model(),
   661  				Grade:          model.Grade(),
   662  				ModelSignKeyID: model.SignKeyID(),
   663  				AssetChain:     assetChain,
   664  				Kernel:         info.SnapName(),
   665  				KernelRevision: kernelRev,
   666  				KernelCmdlines: cmdlines,
   667  				kernelBootFile: kbf,
   668  			})
   669  		}
   670  		return nil
   671  	}
   672  	if err := chainsForModel(modeenv.ModelForSealing()); err != nil {
   673  		return nil, err
   674  	}
   675  
   676  	if modeenv.TryModel != "" {
   677  		if err := chainsForModel(modeenv.TryModelForSealing()); err != nil {
   678  			return nil, err
   679  		}
   680  	}
   681  	return chains, nil
   682  }
   683  
   684  // buildBootAssets takes the BootFiles of a bootloader boot chain and
   685  // produces corresponding bootAssets with the matching current asset
   686  // hashes from modeenv plus it returns separately the last BootFile
   687  // which is for the kernel.
   688  func buildBootAssets(bootFiles []bootloader.BootFile, modeenv *Modeenv) (assets []bootAsset, kernel bootloader.BootFile, err error) {
   689  	if len(bootFiles) == 0 {
   690  		// useful in testing, when mocking is insufficient
   691  		return nil, bootloader.BootFile{}, fmt.Errorf("internal error: cannot build boot assets without boot files")
   692  	}
   693  	assets = make([]bootAsset, len(bootFiles)-1)
   694  
   695  	// the last element is the kernel which is not a boot asset
   696  	for i, bf := range bootFiles[:len(bootFiles)-1] {
   697  		name := filepath.Base(bf.Path)
   698  		var hashes []string
   699  		var ok bool
   700  		if bf.Role == bootloader.RoleRecovery {
   701  			hashes, ok = modeenv.CurrentTrustedRecoveryBootAssets[name]
   702  		} else {
   703  			hashes, ok = modeenv.CurrentTrustedBootAssets[name]
   704  		}
   705  		if !ok {
   706  			return nil, kernel, fmt.Errorf("cannot find expected boot asset %s in modeenv", name)
   707  		}
   708  		assets[i] = bootAsset{
   709  			Role:   bf.Role,
   710  			Name:   name,
   711  			Hashes: hashes,
   712  		}
   713  	}
   714  
   715  	return assets, bootFiles[len(bootFiles)-1], nil
   716  }
   717  
   718  func sealKeyModelParams(pbc predictableBootChains, roleToBlName map[bootloader.Role]string) ([]*secboot.SealKeyModelParams, error) {
   719  	// seal parameters keyed by unique model ID
   720  	modelToParams := map[string]*secboot.SealKeyModelParams{}
   721  	modelParams := make([]*secboot.SealKeyModelParams, 0, len(pbc))
   722  
   723  	for _, bc := range pbc {
   724  		modelForSealing := bc.modelForSealing()
   725  		modelID := modelUniqueID(modelForSealing)
   726  		loadChains, err := bootAssetsToLoadChains(bc.AssetChain, bc.kernelBootFile, roleToBlName)
   727  		if err != nil {
   728  			return nil, fmt.Errorf("cannot build load chains with current boot assets: %s", err)
   729  		}
   730  
   731  		// group parameters by model, reuse an existing SealKeyModelParams
   732  		// if the model is the same.
   733  		if params, ok := modelToParams[modelID]; ok {
   734  			params.KernelCmdlines = strutil.SortedListsUniqueMerge(params.KernelCmdlines, bc.KernelCmdlines)
   735  			params.EFILoadChains = append(params.EFILoadChains, loadChains...)
   736  		} else {
   737  			param := &secboot.SealKeyModelParams{
   738  				Model:          modelForSealing,
   739  				KernelCmdlines: bc.KernelCmdlines,
   740  				EFILoadChains:  loadChains,
   741  			}
   742  			modelParams = append(modelParams, param)
   743  			modelToParams[modelID] = param
   744  		}
   745  	}
   746  
   747  	return modelParams, nil
   748  }
   749  
   750  // isResealNeeded returns true when the predictable boot chains provided as
   751  // input do not match the cached boot chains on disk under rootdir.
   752  // It also returns the next value for the reseal count that is saved
   753  // together with the boot chains.
   754  // A hint expectReseal can be provided, it is used when the matching
   755  // is ambigous because the boot chains contain unrevisioned kernels.
   756  func isResealNeeded(pbc predictableBootChains, bootChainsFile string, expectReseal bool) (ok bool, nextCount int, err error) {
   757  	previousPbc, c, err := readBootChains(bootChainsFile)
   758  	if err != nil {
   759  		return false, 0, err
   760  	}
   761  
   762  	switch predictableBootChainsEqualForReseal(pbc, previousPbc) {
   763  	case bootChainEquivalent:
   764  		return false, c + 1, nil
   765  	case bootChainUnrevisioned:
   766  		return expectReseal, c + 1, nil
   767  	case bootChainDifferent:
   768  	}
   769  	return true, c + 1, nil
   770  }