github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/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 "os" 28 "path/filepath" 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/osutil/disks" 40 "github.com/snapcore/snapd/randutil" 41 "github.com/snapcore/snapd/snap/snapfile" 42 ) 43 44 const ( 45 // Handles are in the block reserved for owner objects (0x01800000 - 0x01bfffff) 46 pinHandle = 0x01880000 47 ) 48 49 var ( 50 sbConnectToDefaultTPM = sb.ConnectToDefaultTPM 51 sbMeasureSnapSystemEpochToTPM = sb.MeasureSnapSystemEpochToTPM 52 sbMeasureSnapModelToTPM = sb.MeasureSnapModelToTPM 53 sbLockAccessToSealedKeys = sb.LockAccessToSealedKeys 54 sbActivateVolumeWithTPMSealedKey = sb.ActivateVolumeWithTPMSealedKey 55 sbActivateVolumeWithRecoveryKey = sb.ActivateVolumeWithRecoveryKey 56 sbAddEFISecureBootPolicyProfile = sb.AddEFISecureBootPolicyProfile 57 sbAddEFIBootManagerProfile = sb.AddEFIBootManagerProfile 58 sbAddSystemdEFIStubProfile = sb.AddSystemdEFIStubProfile 59 sbAddSnapModelProfile = sb.AddSnapModelProfile 60 sbProvisionTPM = sb.ProvisionTPM 61 sbSealKeyToTPM = sb.SealKeyToTPM 62 sbUpdateKeyPCRProtectionPolicy = sb.UpdateKeyPCRProtectionPolicy 63 64 randutilRandomKernelUUID = randutil.RandomKernelUUID 65 66 isTPMEnabled = isTPMEnabledImpl 67 ) 68 69 func isTPMEnabledImpl(tpm *sb.TPMConnection) bool { 70 return tpm.IsEnabled() 71 } 72 73 func CheckKeySealingSupported() 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 const tpmPCR = 12 120 121 func secureConnectToTPM(ekcfile string) (*sb.TPMConnection, error) { 122 ekCertReader, err := os.Open(ekcfile) 123 if err != nil { 124 return nil, fmt.Errorf("cannot open endorsement key certificate file: %v", err) 125 } 126 defer ekCertReader.Close() 127 128 return sb.SecureConnectToDefaultTPM(ekCertReader, nil) 129 } 130 131 func insecureConnectToTPM() (*sb.TPMConnection, error) { 132 return sbConnectToDefaultTPM() 133 } 134 135 func measureWhenPossible(whatHow func(tpm *sb.TPMConnection) error) error { 136 // the model is ready, we're good to try measuring it now 137 tpm, err := insecureConnectToTPM() 138 if err != nil { 139 if xerrors.Is(err, sb.ErrNoTPM2Device) { 140 return nil 141 } 142 return fmt.Errorf("cannot open TPM connection: %v", err) 143 } 144 defer tpm.Close() 145 146 if !isTPMEnabled(tpm) { 147 return nil 148 } 149 150 return whatHow(tpm) 151 } 152 153 // MeasureSnapSystemEpochWhenPossible measures the snap system epoch only if the 154 // TPM device is available. If there's no TPM device success is returned. 155 func MeasureSnapSystemEpochWhenPossible() error { 156 measure := func(tpm *sb.TPMConnection) error { 157 return sbMeasureSnapSystemEpochToTPM(tpm, tpmPCR) 158 } 159 160 if err := measureWhenPossible(measure); err != nil { 161 return fmt.Errorf("cannot measure snap system epoch: %v", err) 162 } 163 164 return nil 165 } 166 167 // MeasureSnapModelWhenPossible measures the snap model only if the TPM device is 168 // available. If there's no TPM device success is returned. 169 func MeasureSnapModelWhenPossible(findModel func() (*asserts.Model, error)) error { 170 measure := func(tpm *sb.TPMConnection) error { 171 model, err := findModel() 172 if err != nil { 173 return err 174 } 175 return sbMeasureSnapModelToTPM(tpm, tpmPCR, model) 176 } 177 178 if err := measureWhenPossible(measure); err != nil { 179 return fmt.Errorf("cannot measure snap model: %v", err) 180 } 181 182 return nil 183 } 184 185 // UnlockVolumeIfEncrypted verifies whether an encrypted volume with the specified 186 // name exists and unlocks it. With lockKeysOnFinish set, access to the sealed 187 // keys will be locked when this function completes. The path to the device node 188 // is returned as well as whether the device node is an decrypted device node ( 189 // in the encrypted case). If no encrypted volume was found, then the returned 190 // device node is an unencrypted normal volume. 191 func UnlockVolumeIfEncrypted(disk disks.Disk, name string, encryptionKeyDir string, lockKeysOnFinish bool) (string, bool, error) { 192 // TODO:UC20: use sb.SecureConnectToDefaultTPM() if we decide there's benefit in doing that or 193 // we have a hard requirement for a valid EK cert chain for every boot (ie, panic 194 // if there isn't one). But we can't do that as long as we need to download 195 // intermediate certs from the manufacturer. 196 tpm, tpmErr := sbConnectToDefaultTPM() 197 if tpmErr != nil { 198 if !xerrors.Is(tpmErr, sb.ErrNoTPM2Device) { 199 return "", false, fmt.Errorf("cannot unlock encrypted device %q: %v", name, tpmErr) 200 } 201 logger.Noticef("cannot open TPM connection: %v", tpmErr) 202 } else { 203 defer tpm.Close() 204 } 205 206 // Also check if the TPM device is enabled. The platform firmware may disable the storage 207 // and endorsement hierarchies, but the device will remain visible to the operating system. 208 tpmDeviceAvailable := tpmErr == nil && isTPMEnabled(tpm) 209 210 var lockErr error 211 var mapperName string 212 err, foundEncDev := func() (error, bool) { 213 defer func() { 214 if lockKeysOnFinish && tpmDeviceAvailable { 215 // Lock access to the sealed keys. This should be called whenever there 216 // is a TPM device detected, regardless of whether secure boot is enabled 217 // or there is an encrypted volume to unlock. Note that snap-bootstrap can 218 // be called several times during initialization, and if there are multiple 219 // volumes to unlock we should lock access to the sealed keys only after 220 // the last encrypted volume is unlocked, in which case lockKeysOnFinish 221 // should be set to true. 222 lockErr = sbLockAccessToSealedKeys(tpm) 223 } 224 }() 225 226 // find the encrypted device using the disk we were provided - note that 227 // we do not specify IsDecryptedDevice in opts because here we are 228 // looking for the encrypted device to unlock, later on in the boot 229 // process we will look for the decrypted device to ensure it matches 230 // what we expected 231 partUUID, err := disk.FindMatchingPartitionUUID(name + "-enc") 232 var errNotFound disks.FilesystemLabelNotFoundError 233 if xerrors.As(err, &errNotFound) { 234 // didn't find the encrypted label, so return nil to try the 235 // decrypted label again 236 return nil, false 237 } 238 if err != nil { 239 return err, false 240 } 241 encdev := filepath.Join("/dev/disk/by-partuuid", partUUID) 242 243 mapperName = name + "-" + randutilRandomKernelUUID() 244 if !tpmDeviceAvailable { 245 return unlockEncryptedPartitionWithRecoveryKey(mapperName, encdev), true 246 } 247 248 sealedKeyPath := filepath.Join(encryptionKeyDir, name+".sealed-key") 249 return unlockEncryptedPartitionWithSealedKey(tpm, mapperName, encdev, sealedKeyPath, "", lockKeysOnFinish), true 250 }() 251 if err != nil { 252 return "", false, err 253 } 254 if lockErr != nil { 255 return "", false, fmt.Errorf("cannot lock access to sealed keys: %v", lockErr) 256 } 257 258 if foundEncDev { 259 // return the encrypted device if the device we are maybe unlocking is 260 // an encrypted device 261 return filepath.Join("/dev/mapper", mapperName), true, nil 262 } 263 264 // otherwise find the device from the disk 265 partUUID, err := disk.FindMatchingPartitionUUID(name) 266 if err != nil { 267 return "", false, err 268 } 269 return filepath.Join("/dev/disk/by-partuuid", partUUID), false, nil 270 } 271 272 // unlockEncryptedPartitionWithRecoveryKey prompts for the recovery key and use 273 // it to open an encrypted device. 274 func unlockEncryptedPartitionWithRecoveryKey(name, device string) error { 275 options := sb.ActivateWithRecoveryKeyOptions{ 276 Tries: 3, 277 } 278 279 if err := sbActivateVolumeWithRecoveryKey(name, device, nil, &options); err != nil { 280 return fmt.Errorf("cannot unlock encrypted device %q: %v", device, err) 281 } 282 283 return nil 284 } 285 286 // unlockEncryptedPartitionWithSealedKey unseals the keyfile and opens an encrypted 287 // device. If activation with the sealed key fails, this function will attempt to 288 // activate it with the fallback recovery key instead. 289 func unlockEncryptedPartitionWithSealedKey(tpm *sb.TPMConnection, name, device, keyfile, pinfile string, lock bool) error { 290 options := sb.ActivateWithTPMSealedKeyOptions{ 291 PINTries: 1, 292 RecoveryKeyTries: 3, 293 LockSealedKeyAccess: lock, 294 } 295 296 // XXX: pinfile is currently not used 297 activated, err := sbActivateVolumeWithTPMSealedKey(tpm, name, device, keyfile, nil, &options) 298 if !activated { 299 // ActivateVolumeWithTPMSealedKey should always return an error if activated == false 300 return fmt.Errorf("cannot activate encrypted device %q: %v", device, err) 301 } 302 if err != nil { 303 logger.Noticef("successfully activated encrypted device %q using a fallback activation method", device) 304 } else { 305 logger.Noticef("successfully activated encrypted device %q with TPM", device) 306 } 307 308 return nil 309 } 310 311 // SealKey provisions the TPM and seals a partition encryption key according to the 312 // specified parameters. If the TPM is already provisioned, or a sealed key already 313 // exists, SealKey will fail and return an error. 314 func SealKey(key EncryptionKey, params *SealKeyParams) error { 315 numModels := len(params.ModelParams) 316 if numModels < 1 { 317 return fmt.Errorf("at least one set of model-specific parameters is required") 318 } 319 320 tpm, err := sbConnectToDefaultTPM() 321 if err != nil { 322 return fmt.Errorf("cannot connect to TPM: %v", err) 323 } 324 defer tpm.Close() 325 if !isTPMEnabled(tpm) { 326 return fmt.Errorf("TPM device is not enabled") 327 } 328 329 pcrProfile, err := buildPCRProtectionProfile(params.ModelParams) 330 if err != nil { 331 return err 332 } 333 334 // Provision the TPM as late as possible 335 if err := tpmProvision(tpm, params.TPMLockoutAuthFile); err != nil { 336 return err 337 } 338 339 // Seal key to the TPM 340 creationParams := sb.KeyCreationParams{ 341 PCRProfile: pcrProfile, 342 PINHandle: pinHandle, 343 } 344 return sbSealKeyToTPM(tpm, key[:], params.KeyFile, params.TPMPolicyUpdateDataFile, &creationParams) 345 } 346 347 // ResealKey updates the PCR protection policy for the sealed encryption key according to 348 // the specified parameters. 349 func ResealKey(params *ResealKeyParams) error { 350 numModels := len(params.ModelParams) 351 if numModels < 1 { 352 return fmt.Errorf("at least one set of model-specific parameters is required") 353 } 354 355 tpm, err := sbConnectToDefaultTPM() 356 if err != nil { 357 return fmt.Errorf("cannot connect to TPM: %v", err) 358 } 359 defer tpm.Close() 360 if !isTPMEnabled(tpm) { 361 return fmt.Errorf("TPM device is not enabled") 362 } 363 364 pcrProfile, err := buildPCRProtectionProfile(params.ModelParams) 365 if err != nil { 366 return err 367 } 368 369 return sbUpdateKeyPCRProtectionPolicy(tpm, params.KeyFile, params.TPMPolicyUpdateDataFile, pcrProfile) 370 } 371 372 func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb.PCRProtectionProfile, error) { 373 numModels := len(modelParams) 374 modelPCRProfiles := make([]*sb.PCRProtectionProfile, 0, numModels) 375 376 for _, mp := range modelParams { 377 modelProfile := sb.NewPCRProtectionProfile() 378 379 loadSequences, err := buildLoadSequences(mp.EFILoadChains) 380 if err != nil { 381 return nil, fmt.Errorf("cannot build EFI image load sequences: %v", err) 382 } 383 384 // Add EFI secure boot policy profile 385 policyParams := sb.EFISecureBootPolicyProfileParams{ 386 PCRAlgorithm: tpm2.HashAlgorithmSHA256, 387 LoadSequences: loadSequences, 388 // TODO:UC20: set SignatureDbUpdateKeystore to support applying forbidden 389 // signature updates to blacklist signing keys (after rotating them). 390 // This also requires integration of sbkeysync, and some work to 391 // ensure that the PCR profile is updated before/after sbkeysync executes. 392 } 393 394 if err := sbAddEFISecureBootPolicyProfile(modelProfile, &policyParams); err != nil { 395 return nil, fmt.Errorf("cannot add EFI secure boot policy profile: %v", err) 396 } 397 398 // Add EFI boot manager profile 399 bootManagerParams := sb.EFIBootManagerProfileParams{ 400 PCRAlgorithm: tpm2.HashAlgorithmSHA256, 401 LoadSequences: loadSequences, 402 } 403 if err := sbAddEFIBootManagerProfile(modelProfile, &bootManagerParams); err != nil { 404 return nil, fmt.Errorf("cannot add EFI boot manager profile: %v", err) 405 } 406 407 // Add systemd EFI stub profile 408 if len(mp.KernelCmdlines) != 0 { 409 systemdStubParams := sb.SystemdEFIStubProfileParams{ 410 PCRAlgorithm: tpm2.HashAlgorithmSHA256, 411 PCRIndex: tpmPCR, 412 KernelCmdlines: mp.KernelCmdlines, 413 } 414 if err := sbAddSystemdEFIStubProfile(modelProfile, &systemdStubParams); err != nil { 415 return nil, fmt.Errorf("cannot add systemd EFI stub profile: %v", err) 416 } 417 } 418 419 // Add snap model profile 420 if mp.Model != nil { 421 snapModelParams := sb.SnapModelProfileParams{ 422 PCRAlgorithm: tpm2.HashAlgorithmSHA256, 423 PCRIndex: tpmPCR, 424 Models: []sb.SnapModel{mp.Model}, 425 } 426 if err := sbAddSnapModelProfile(modelProfile, &snapModelParams); err != nil { 427 return nil, fmt.Errorf("cannot add snap model profile: %v", err) 428 } 429 } 430 431 modelPCRProfiles = append(modelPCRProfiles, modelProfile) 432 } 433 434 var pcrProfile *sb.PCRProtectionProfile 435 if numModels > 1 { 436 pcrProfile = sb.NewPCRProtectionProfile().AddProfileOR(modelPCRProfiles...) 437 } else { 438 pcrProfile = modelPCRProfiles[0] 439 } 440 441 logger.Debugf("PCR protection profile:\n%s", pcrProfile.String()) 442 443 return pcrProfile, nil 444 } 445 446 func tpmProvision(tpm *sb.TPMConnection, lockoutAuthFile string) error { 447 // Create and save the lockout authorization file 448 lockoutAuth := make([]byte, 16) 449 // crypto rand is protected against short reads 450 _, err := rand.Read(lockoutAuth) 451 if err != nil { 452 return fmt.Errorf("cannot create lockout authorization: %v", err) 453 } 454 if err := osutil.AtomicWriteFile(lockoutAuthFile, lockoutAuth, 0600, 0); err != nil { 455 return fmt.Errorf("cannot write the lockout authorization file: %v", err) 456 } 457 458 // TODO:UC20: ideally we should ask the firmware to clear the TPM and then reboot 459 // if the device has previously been provisioned, see 460 // https://godoc.org/github.com/snapcore/secboot#RequestTPMClearUsingPPI 461 if err := sbProvisionTPM(tpm, sb.ProvisionModeFull, lockoutAuth); err != nil { 462 logger.Noticef("TPM provisioning error: %v", err) 463 return fmt.Errorf("cannot provision TPM: %v", err) 464 } 465 return nil 466 } 467 468 // buildLoadSequences builds EFI load image event trees from this package LoadChains 469 func buildLoadSequences(chains []*LoadChain) (loadseqs []*sb.EFIImageLoadEvent, err error) { 470 // this will build load event trees for the current 471 // device configuration, e.g. something like: 472 // 473 // shim -> recovery grub -> recovery kernel 1 474 // |-> recovery kernel 2 475 // |-> recovery kernel ... 476 // |-> normal grub -> run kernel good 477 // |-> run kernel try 478 479 for _, chain := range chains { 480 // root of load events has source Firmware 481 loadseq, err := chain.loadEvent(sb.Firmware) 482 if err != nil { 483 return nil, err 484 } 485 loadseqs = append(loadseqs, loadseq) 486 } 487 return loadseqs, nil 488 } 489 490 // loadEvent builds the corresponding load event and its tree 491 func (lc *LoadChain) loadEvent(source sb.EFIImageLoadEventSource) (*sb.EFIImageLoadEvent, error) { 492 var next []*sb.EFIImageLoadEvent 493 for _, nextChain := range lc.Next { 494 // everything that is not the root has source shim 495 ev, err := nextChain.loadEvent(sb.Shim) 496 if err != nil { 497 return nil, err 498 } 499 next = append(next, ev) 500 } 501 image, err := efiImageFromBootFile(lc.BootFile) 502 if err != nil { 503 return nil, err 504 } 505 return &sb.EFIImageLoadEvent{ 506 Source: source, 507 Image: image, 508 Next: next, 509 }, nil 510 } 511 512 func efiImageFromBootFile(b *bootloader.BootFile) (sb.EFIImage, error) { 513 if b.Snap == "" { 514 if !osutil.FileExists(b.Path) { 515 return nil, fmt.Errorf("file %s does not exist", b.Path) 516 } 517 return sb.FileEFIImage(b.Path), nil 518 } 519 520 snapf, err := snapfile.Open(b.Snap) 521 if err != nil { 522 return nil, err 523 } 524 return sb.SnapFileEFIImage{ 525 Container: snapf, 526 Path: b.Snap, 527 FileName: b.Path, 528 }, nil 529 }