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