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, ¶ms); 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 }