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