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 }