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