github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/secboot/secboot_tpm.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 // +build !nosecboot 3 4 /* 5 * Copyright (C) 2020 Canonical Ltd 6 * 7 * This program is free software: you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License version 3 as 9 * published by the Free Software Foundation. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 * 19 */ 20 21 package secboot 22 23 import ( 24 "crypto/rand" 25 "errors" 26 "fmt" 27 "io/ioutil" 28 "os" 29 30 "github.com/canonical/go-tpm2" 31 "github.com/snapcore/secboot" 32 sb "github.com/snapcore/secboot" 33 "golang.org/x/xerrors" 34 35 "github.com/snapcore/snapd/asserts" 36 "github.com/snapcore/snapd/bootloader" 37 "github.com/snapcore/snapd/bootloader/efi" 38 "github.com/snapcore/snapd/logger" 39 "github.com/snapcore/snapd/osutil" 40 "github.com/snapcore/snapd/randutil" 41 "github.com/snapcore/snapd/snap/snapfile" 42 ) 43 44 const ( 45 keyringPrefix = "ubuntu-fde" 46 ) 47 48 var ( 49 sbConnectToDefaultTPM = sb.ConnectToDefaultTPM 50 sbMeasureSnapSystemEpochToTPM = sb.MeasureSnapSystemEpochToTPM 51 sbMeasureSnapModelToTPM = sb.MeasureSnapModelToTPM 52 sbBlockPCRProtectionPolicies = sb.BlockPCRProtectionPolicies 53 sbActivateVolumeWithTPMSealedKey = sb.ActivateVolumeWithTPMSealedKey 54 sbAddEFISecureBootPolicyProfile = sb.AddEFISecureBootPolicyProfile 55 sbAddEFIBootManagerProfile = sb.AddEFIBootManagerProfile 56 sbAddSystemdEFIStubProfile = sb.AddSystemdEFIStubProfile 57 sbAddSnapModelProfile = sb.AddSnapModelProfile 58 sbSealKeyToTPMMultiple = sb.SealKeyToTPMMultiple 59 sbUpdateKeyPCRProtectionPolicyMultiple = sb.UpdateKeyPCRProtectionPolicyMultiple 60 61 randutilRandomKernelUUID = randutil.RandomKernelUUID 62 63 isTPMEnabled = isTPMEnabledImpl 64 provisionTPM = provisionTPMImpl 65 66 // dummy to check whether the interfaces match 67 _ (secboot.SnapModel) = ModelForSealing(nil) 68 ) 69 70 func isTPMEnabledImpl(tpm *sb.TPMConnection) bool { 71 return tpm.IsEnabled() 72 } 73 74 func CheckTPMKeySealingSupported() error { 75 logger.Noticef("checking if secure boot is enabled...") 76 if err := checkSecureBootEnabled(); err != nil { 77 logger.Noticef("secure boot not enabled: %v", err) 78 return err 79 } 80 logger.Noticef("secure boot is enabled") 81 82 logger.Noticef("checking if TPM device is available...") 83 tpm, err := sbConnectToDefaultTPM() 84 if err != nil { 85 err = fmt.Errorf("cannot connect to TPM device: %v", err) 86 logger.Noticef("%v", err) 87 return err 88 } 89 defer tpm.Close() 90 91 if !isTPMEnabled(tpm) { 92 logger.Noticef("TPM device detected but not enabled") 93 return fmt.Errorf("TPM device is not enabled") 94 } 95 96 logger.Noticef("TPM device detected and enabled") 97 98 return nil 99 } 100 101 func checkSecureBootEnabled() error { 102 // 8be4df61-93ca-11d2-aa0d-00e098032b8c is the EFI Global Variable vendor GUID 103 b, _, err := efi.ReadVarBytes("SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c") 104 if err != nil { 105 if err == efi.ErrNoEFISystem { 106 return err 107 } 108 return fmt.Errorf("cannot read secure boot variable: %v", err) 109 } 110 if len(b) < 1 { 111 return errors.New("secure boot variable does not exist") 112 } 113 if b[0] != 1 { 114 return errors.New("secure boot is disabled") 115 } 116 117 return nil 118 } 119 120 // initramfsPCR is the TPM PCR that we reserve for the EFI image and use 121 // for measurement from the initramfs. 122 const initramfsPCR = 12 123 124 func secureConnectToTPM(ekcfile string) (*sb.TPMConnection, error) { 125 ekCertReader, err := os.Open(ekcfile) 126 if err != nil { 127 return nil, fmt.Errorf("cannot open endorsement key certificate file: %v", err) 128 } 129 defer ekCertReader.Close() 130 131 return sb.SecureConnectToDefaultTPM(ekCertReader, nil) 132 } 133 134 func insecureConnectToTPM() (*sb.TPMConnection, error) { 135 return sbConnectToDefaultTPM() 136 } 137 138 func measureWhenPossible(whatHow func(tpm *sb.TPMConnection) error) error { 139 // the model is ready, we're good to try measuring it now 140 tpm, err := insecureConnectToTPM() 141 if err != nil { 142 if xerrors.Is(err, sb.ErrNoTPM2Device) { 143 return nil 144 } 145 return fmt.Errorf("cannot open TPM connection: %v", err) 146 } 147 defer tpm.Close() 148 149 if !isTPMEnabled(tpm) { 150 return nil 151 } 152 153 return whatHow(tpm) 154 } 155 156 // MeasureSnapSystemEpochWhenPossible measures the snap system epoch only if the 157 // TPM device is available. If there's no TPM device success is returned. 158 func MeasureSnapSystemEpochWhenPossible() error { 159 measure := func(tpm *sb.TPMConnection) error { 160 return sbMeasureSnapSystemEpochToTPM(tpm, initramfsPCR) 161 } 162 163 if err := measureWhenPossible(measure); err != nil { 164 return fmt.Errorf("cannot measure snap system epoch: %v", err) 165 } 166 167 return nil 168 } 169 170 // MeasureSnapModelWhenPossible measures the snap model only if the TPM device is 171 // available. If there's no TPM device success is returned. 172 func MeasureSnapModelWhenPossible(findModel func() (*asserts.Model, error)) error { 173 measure := func(tpm *sb.TPMConnection) error { 174 model, err := findModel() 175 if err != nil { 176 return err 177 } 178 return sbMeasureSnapModelToTPM(tpm, initramfsPCR, model) 179 } 180 181 if err := measureWhenPossible(measure); err != nil { 182 return fmt.Errorf("cannot measure snap model: %v", err) 183 } 184 185 return nil 186 } 187 188 func lockTPMSealedKeys() error { 189 tpm, tpmErr := sbConnectToDefaultTPM() 190 if tpmErr != nil { 191 if xerrors.Is(tpmErr, sb.ErrNoTPM2Device) { 192 logger.Noticef("cannot open TPM connection: %v", tpmErr) 193 return nil 194 } 195 return fmt.Errorf("cannot lock TPM: %v", tpmErr) 196 } 197 defer tpm.Close() 198 199 // Lock access to the sealed keys. This should be called whenever there 200 // is a TPM device detected, regardless of whether secure boot is enabled 201 // or there is an encrypted volume to unlock. Note that snap-bootstrap can 202 // be called several times during initialization, and if there are multiple 203 // volumes to unlock we should lock access to the sealed keys only after 204 // the last encrypted volume is unlocked, in which case lockKeysOnFinish 205 // should be set to true. 206 // 207 // We should only touch the PCR that we've currently reserved for the kernel 208 // EFI image. Touching others will break the ability to perform any kind of 209 // attestation using the TPM because it will make the log inconsistent. 210 return sbBlockPCRProtectionPolicies(tpm, []int{initramfsPCR}) 211 } 212 213 func unlockVolumeUsingSealedKeyTPM(name, sealedEncryptionKeyFile, sourceDevice, targetDevice, mapperName string, opts *UnlockVolumeUsingSealedKeyOptions) (UnlockResult, error) { 214 // TODO:UC20: use sb.SecureConnectToDefaultTPM() if we decide there's benefit in doing that or 215 // we have a hard requirement for a valid EK cert chain for every boot (ie, panic 216 // if there isn't one). But we can't do that as long as we need to download 217 // intermediate certs from the manufacturer. 218 219 res := UnlockResult{IsEncrypted: true, PartDevice: sourceDevice} 220 // Obtain a TPM connection. 221 tpm, tpmErr := sbConnectToDefaultTPM() 222 if tpmErr != nil { 223 if !xerrors.Is(tpmErr, sb.ErrNoTPM2Device) { 224 return res, fmt.Errorf("cannot unlock encrypted device %q: %v", name, tpmErr) 225 } 226 logger.Noticef("cannot open TPM connection: %v", tpmErr) 227 } else { 228 defer tpm.Close() 229 } 230 231 // Also check if the TPM device is enabled. The platform firmware may disable the storage 232 // and endorsement hierarchies, but the device will remain visible to the operating system. 233 tpmDeviceAvailable := tpmErr == nil && isTPMEnabled(tpm) 234 235 // if we don't have a tpm, and we allow using a recovery key, do that 236 // directly 237 if !tpmDeviceAvailable && opts.AllowRecoveryKey { 238 if err := UnlockEncryptedVolumeWithRecoveryKey(mapperName, sourceDevice); err != nil { 239 return res, err 240 } 241 res.FsDevice = targetDevice 242 res.UnlockMethod = UnlockedWithRecoveryKey 243 return res, nil 244 } 245 246 // otherwise we have a tpm and we should use the sealed key first, but 247 // this method will fallback to using the recovery key if enabled 248 method, err := unlockEncryptedPartitionWithSealedKey(tpm, mapperName, sourceDevice, sealedEncryptionKeyFile, "", opts.AllowRecoveryKey) 249 res.UnlockMethod = method 250 if err == nil { 251 res.FsDevice = targetDevice 252 } 253 return res, err 254 } 255 256 func isActivatedWithRecoveryKey(err error) bool { 257 if err == nil { 258 return false 259 } 260 // with non-nil err, we should check for err being ActivateWithTPMSealedKeyError 261 // and RecoveryKeyUsageErr inside that being nil - this indicates that the 262 // recovery key was used to unlock it 263 activateErr, ok := err.(*sb.ActivateWithTPMSealedKeyError) 264 if !ok { 265 return false 266 } 267 return activateErr.RecoveryKeyUsageErr == nil 268 } 269 270 func activateVolOpts(allowRecoveryKey bool) *sb.ActivateVolumeOptions { 271 options := sb.ActivateVolumeOptions{ 272 PassphraseTries: 1, 273 // disable recovery key by default 274 RecoveryKeyTries: 0, 275 KeyringPrefix: keyringPrefix, 276 } 277 if allowRecoveryKey { 278 // enable recovery key only when explicitly allowed 279 options.RecoveryKeyTries = 3 280 } 281 return &options 282 } 283 284 // unlockEncryptedPartitionWithSealedKey unseals the keyfile and opens an encrypted 285 // device. If activation with the sealed key fails, this function will attempt to 286 // activate it with the fallback recovery key instead. 287 func unlockEncryptedPartitionWithSealedKey(tpm *sb.TPMConnection, name, device, keyfile, pinfile string, allowRecovery bool) (UnlockMethod, error) { 288 options := activateVolOpts(allowRecovery) 289 // XXX: pinfile is currently not used 290 activated, err := sbActivateVolumeWithTPMSealedKey(tpm, name, device, keyfile, nil, options) 291 292 if activated { 293 // non nil error may indicate the volume was unlocked using the 294 // recovery key 295 if err == nil { 296 logger.Noticef("successfully activated encrypted device %q with TPM", device) 297 return UnlockedWithSealedKey, nil 298 } else if isActivatedWithRecoveryKey(err) { 299 logger.Noticef("successfully activated encrypted device %q using a fallback activation method", device) 300 return UnlockedWithRecoveryKey, nil 301 } 302 // no other error is possible when activation succeeded 303 return UnlockStatusUnknown, fmt.Errorf("internal error: volume activated with unexpected error: %v", err) 304 } 305 // ActivateVolumeWithTPMSealedKey should always return an error if activated == false 306 return NotUnlocked, fmt.Errorf("cannot activate encrypted device %q: %v", device, err) 307 } 308 309 // SealKeys provisions the TPM and seals the encryption keys according to the 310 // specified parameters. If the TPM is already provisioned, or a sealed key already 311 // exists, SealKeys will fail and return an error. 312 func SealKeys(keys []SealKeyRequest, params *SealKeysParams) error { 313 numModels := len(params.ModelParams) 314 if numModels < 1 { 315 return fmt.Errorf("at least one set of model-specific parameters is required") 316 } 317 318 tpm, err := sbConnectToDefaultTPM() 319 if err != nil { 320 return fmt.Errorf("cannot connect to TPM: %v", err) 321 } 322 defer tpm.Close() 323 if !isTPMEnabled(tpm) { 324 return fmt.Errorf("TPM device is not enabled") 325 } 326 327 pcrProfile, err := buildPCRProtectionProfile(params.ModelParams) 328 if err != nil { 329 return err 330 } 331 332 if params.TPMProvision { 333 // Provision the TPM as late as possible 334 if err := tpmProvision(tpm, params.TPMLockoutAuthFile); err != nil { 335 return err 336 } 337 } 338 339 // Seal the provided keys to the TPM 340 creationParams := sb.KeyCreationParams{ 341 PCRProfile: pcrProfile, 342 PCRPolicyCounterHandle: tpm2.Handle(params.PCRPolicyCounterHandle), 343 AuthKey: params.TPMPolicyAuthKey, 344 } 345 346 sbKeys := make([]*sb.SealKeyRequest, 0, len(keys)) 347 for i := range keys { 348 sbKeys = append(sbKeys, &sb.SealKeyRequest{ 349 Key: keys[i].Key, 350 Path: keys[i].KeyFile, 351 }) 352 } 353 354 authKey, err := sbSealKeyToTPMMultiple(tpm, sbKeys, &creationParams) 355 if err != nil { 356 return err 357 } 358 if params.TPMPolicyAuthKeyFile != "" { 359 if err := osutil.AtomicWriteFile(params.TPMPolicyAuthKeyFile, authKey, 0600, 0); err != nil { 360 return fmt.Errorf("cannot write the policy auth key file: %v", err) 361 } 362 } 363 364 return nil 365 } 366 367 // ResealKeys updates the PCR protection policy for the sealed encryption keys 368 // according to the specified parameters. 369 func ResealKeys(params *ResealKeysParams) error { 370 numModels := len(params.ModelParams) 371 if numModels < 1 { 372 return fmt.Errorf("at least one set of model-specific parameters is required") 373 } 374 375 tpm, err := sbConnectToDefaultTPM() 376 if err != nil { 377 return fmt.Errorf("cannot connect to TPM: %v", err) 378 } 379 defer tpm.Close() 380 if !isTPMEnabled(tpm) { 381 return fmt.Errorf("TPM device is not enabled") 382 } 383 384 pcrProfile, err := buildPCRProtectionProfile(params.ModelParams) 385 if err != nil { 386 return err 387 } 388 389 authKey, err := ioutil.ReadFile(params.TPMPolicyAuthKeyFile) 390 if err != nil { 391 return fmt.Errorf("cannot read the policy auth key file: %v", err) 392 } 393 394 return sbUpdateKeyPCRProtectionPolicyMultiple(tpm, params.KeyFiles, authKey, pcrProfile) 395 } 396 397 func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb.PCRProtectionProfile, error) { 398 numModels := len(modelParams) 399 modelPCRProfiles := make([]*sb.PCRProtectionProfile, 0, numModels) 400 401 for _, mp := range modelParams { 402 modelProfile := sb.NewPCRProtectionProfile() 403 404 loadSequences, err := buildLoadSequences(mp.EFILoadChains) 405 if err != nil { 406 return nil, fmt.Errorf("cannot build EFI image load sequences: %v", err) 407 } 408 409 // Add EFI secure boot policy profile 410 policyParams := sb.EFISecureBootPolicyProfileParams{ 411 PCRAlgorithm: tpm2.HashAlgorithmSHA256, 412 LoadSequences: loadSequences, 413 // TODO:UC20: set SignatureDbUpdateKeystore to support applying forbidden 414 // signature updates to blacklist signing keys (after rotating them). 415 // This also requires integration of sbkeysync, and some work to 416 // ensure that the PCR profile is updated before/after sbkeysync executes. 417 } 418 419 if err := sbAddEFISecureBootPolicyProfile(modelProfile, &policyParams); err != nil { 420 return nil, fmt.Errorf("cannot add EFI secure boot policy profile: %v", err) 421 } 422 423 // Add EFI boot manager profile 424 bootManagerParams := sb.EFIBootManagerProfileParams{ 425 PCRAlgorithm: tpm2.HashAlgorithmSHA256, 426 LoadSequences: loadSequences, 427 } 428 if err := sbAddEFIBootManagerProfile(modelProfile, &bootManagerParams); err != nil { 429 return nil, fmt.Errorf("cannot add EFI boot manager profile: %v", err) 430 } 431 432 // Add systemd EFI stub profile 433 if len(mp.KernelCmdlines) != 0 { 434 systemdStubParams := sb.SystemdEFIStubProfileParams{ 435 PCRAlgorithm: tpm2.HashAlgorithmSHA256, 436 PCRIndex: initramfsPCR, 437 KernelCmdlines: mp.KernelCmdlines, 438 } 439 if err := sbAddSystemdEFIStubProfile(modelProfile, &systemdStubParams); err != nil { 440 return nil, fmt.Errorf("cannot add systemd EFI stub profile: %v", err) 441 } 442 } 443 444 // Add snap model profile 445 if mp.Model != nil { 446 snapModelParams := sb.SnapModelProfileParams{ 447 PCRAlgorithm: tpm2.HashAlgorithmSHA256, 448 PCRIndex: initramfsPCR, 449 Models: []sb.SnapModel{mp.Model}, 450 } 451 if err := sbAddSnapModelProfile(modelProfile, &snapModelParams); err != nil { 452 return nil, fmt.Errorf("cannot add snap model profile: %v", err) 453 } 454 } 455 456 modelPCRProfiles = append(modelPCRProfiles, modelProfile) 457 } 458 459 var pcrProfile *sb.PCRProtectionProfile 460 if numModels > 1 { 461 pcrProfile = sb.NewPCRProtectionProfile().AddProfileOR(modelPCRProfiles...) 462 } else { 463 pcrProfile = modelPCRProfiles[0] 464 } 465 466 logger.Debugf("PCR protection profile:\n%s", pcrProfile.String()) 467 468 return pcrProfile, nil 469 } 470 471 func tpmProvision(tpm *sb.TPMConnection, lockoutAuthFile string) error { 472 // Create and save the lockout authorization file 473 lockoutAuth := make([]byte, 16) 474 // crypto rand is protected against short reads 475 _, err := rand.Read(lockoutAuth) 476 if err != nil { 477 return fmt.Errorf("cannot create lockout authorization: %v", err) 478 } 479 if err := osutil.AtomicWriteFile(lockoutAuthFile, lockoutAuth, 0600, 0); err != nil { 480 return fmt.Errorf("cannot write the lockout authorization file: %v", err) 481 } 482 483 // TODO:UC20: ideally we should ask the firmware to clear the TPM and then reboot 484 // if the device has previously been provisioned, see 485 // https://godoc.org/github.com/snapcore/secboot#RequestTPMClearUsingPPI 486 if err := provisionTPM(tpm, sb.ProvisionModeFull, lockoutAuth); err != nil { 487 logger.Noticef("TPM provisioning error: %v", err) 488 return fmt.Errorf("cannot provision TPM: %v", err) 489 } 490 return nil 491 } 492 493 func provisionTPMImpl(tpm *sb.TPMConnection, mode sb.ProvisionMode, lockoutAuth []byte) error { 494 return tpm.EnsureProvisioned(mode, lockoutAuth) 495 } 496 497 // buildLoadSequences builds EFI load image event trees from this package LoadChains 498 func buildLoadSequences(chains []*LoadChain) (loadseqs []*sb.EFIImageLoadEvent, err error) { 499 // this will build load event trees for the current 500 // device configuration, e.g. something like: 501 // 502 // shim -> recovery grub -> recovery kernel 1 503 // |-> recovery kernel 2 504 // |-> recovery kernel ... 505 // |-> normal grub -> run kernel good 506 // |-> run kernel try 507 508 for _, chain := range chains { 509 // root of load events has source Firmware 510 loadseq, err := chain.loadEvent(sb.Firmware) 511 if err != nil { 512 return nil, err 513 } 514 loadseqs = append(loadseqs, loadseq) 515 } 516 return loadseqs, nil 517 } 518 519 // loadEvent builds the corresponding load event and its tree 520 func (lc *LoadChain) loadEvent(source sb.EFIImageLoadEventSource) (*sb.EFIImageLoadEvent, error) { 521 var next []*sb.EFIImageLoadEvent 522 for _, nextChain := range lc.Next { 523 // everything that is not the root has source shim 524 ev, err := nextChain.loadEvent(sb.Shim) 525 if err != nil { 526 return nil, err 527 } 528 next = append(next, ev) 529 } 530 image, err := efiImageFromBootFile(lc.BootFile) 531 if err != nil { 532 return nil, err 533 } 534 return &sb.EFIImageLoadEvent{ 535 Source: source, 536 Image: image, 537 Next: next, 538 }, nil 539 } 540 541 func efiImageFromBootFile(b *bootloader.BootFile) (sb.EFIImage, error) { 542 if b.Snap == "" { 543 if !osutil.FileExists(b.Path) { 544 return nil, fmt.Errorf("file %s does not exist", b.Path) 545 } 546 return sb.FileEFIImage(b.Path), nil 547 } 548 549 snapf, err := snapfile.Open(b.Snap) 550 if err != nil { 551 return nil, err 552 } 553 return sb.SnapFileEFIImage{ 554 Container: snapf, 555 FileName: b.Path, 556 }, nil 557 }