github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/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 "bytes" 25 "crypto/rand" 26 "encoding/json" 27 "errors" 28 "fmt" 29 "io/ioutil" 30 "os" 31 "os/exec" 32 "path/filepath" 33 "time" 34 35 "github.com/canonical/go-tpm2" 36 sb "github.com/snapcore/secboot" 37 "golang.org/x/xerrors" 38 39 "github.com/snapcore/snapd/asserts" 40 "github.com/snapcore/snapd/bootloader" 41 "github.com/snapcore/snapd/bootloader/efi" 42 "github.com/snapcore/snapd/dirs" 43 "github.com/snapcore/snapd/logger" 44 "github.com/snapcore/snapd/osutil" 45 "github.com/snapcore/snapd/osutil/disks" 46 "github.com/snapcore/snapd/randutil" 47 "github.com/snapcore/snapd/snap/snapfile" 48 ) 49 50 const ( 51 keyringPrefix = "ubuntu-fde" 52 ) 53 54 var ( 55 sbConnectToDefaultTPM = sb.ConnectToDefaultTPM 56 sbMeasureSnapSystemEpochToTPM = sb.MeasureSnapSystemEpochToTPM 57 sbMeasureSnapModelToTPM = sb.MeasureSnapModelToTPM 58 sbBlockPCRProtectionPolicies = sb.BlockPCRProtectionPolicies 59 sbActivateVolumeWithTPMSealedKey = sb.ActivateVolumeWithTPMSealedKey 60 sbActivateVolumeWithRecoveryKey = sb.ActivateVolumeWithRecoveryKey 61 sbActivateVolumeWithKey = sb.ActivateVolumeWithKey 62 sbAddEFISecureBootPolicyProfile = sb.AddEFISecureBootPolicyProfile 63 sbAddEFIBootManagerProfile = sb.AddEFIBootManagerProfile 64 sbAddSystemdEFIStubProfile = sb.AddSystemdEFIStubProfile 65 sbAddSnapModelProfile = sb.AddSnapModelProfile 66 sbSealKeyToTPMMultiple = sb.SealKeyToTPMMultiple 67 sbUpdateKeyPCRProtectionPolicyMultiple = sb.UpdateKeyPCRProtectionPolicyMultiple 68 69 randutilRandomKernelUUID = randutil.RandomKernelUUID 70 71 isTPMEnabled = isTPMEnabledImpl 72 provisionTPM = provisionTPMImpl 73 ) 74 75 func isTPMEnabledImpl(tpm *sb.TPMConnection) bool { 76 return tpm.IsEnabled() 77 } 78 79 func CheckKeySealingSupported() error { 80 logger.Noticef("checking if secure boot is enabled...") 81 if err := checkSecureBootEnabled(); err != nil { 82 logger.Noticef("secure boot not enabled: %v", err) 83 return err 84 } 85 logger.Noticef("secure boot is enabled") 86 87 logger.Noticef("checking if TPM device is available...") 88 tpm, err := sbConnectToDefaultTPM() 89 if err != nil { 90 err = fmt.Errorf("cannot connect to TPM device: %v", err) 91 logger.Noticef("%v", err) 92 return err 93 } 94 defer tpm.Close() 95 96 if !isTPMEnabled(tpm) { 97 logger.Noticef("TPM device detected but not enabled") 98 return fmt.Errorf("TPM device is not enabled") 99 } 100 101 logger.Noticef("TPM device detected and enabled") 102 103 return nil 104 } 105 106 func checkSecureBootEnabled() error { 107 // 8be4df61-93ca-11d2-aa0d-00e098032b8c is the EFI Global Variable vendor GUID 108 b, _, err := efi.ReadVarBytes("SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c") 109 if err != nil { 110 if err == efi.ErrNoEFISystem { 111 return err 112 } 113 return fmt.Errorf("cannot read secure boot variable: %v", err) 114 } 115 if len(b) < 1 { 116 return errors.New("secure boot variable does not exist") 117 } 118 if b[0] != 1 { 119 return errors.New("secure boot is disabled") 120 } 121 122 return nil 123 } 124 125 // initramfsPCR is the TPM PCR that we reserve for the EFI image and use 126 // for measurement from the initramfs. 127 const initramfsPCR = 12 128 129 func secureConnectToTPM(ekcfile string) (*sb.TPMConnection, error) { 130 ekCertReader, err := os.Open(ekcfile) 131 if err != nil { 132 return nil, fmt.Errorf("cannot open endorsement key certificate file: %v", err) 133 } 134 defer ekCertReader.Close() 135 136 return sb.SecureConnectToDefaultTPM(ekCertReader, nil) 137 } 138 139 func insecureConnectToTPM() (*sb.TPMConnection, error) { 140 return sbConnectToDefaultTPM() 141 } 142 143 func measureWhenPossible(whatHow func(tpm *sb.TPMConnection) error) error { 144 // the model is ready, we're good to try measuring it now 145 tpm, err := insecureConnectToTPM() 146 if err != nil { 147 if xerrors.Is(err, sb.ErrNoTPM2Device) { 148 return nil 149 } 150 return fmt.Errorf("cannot open TPM connection: %v", err) 151 } 152 defer tpm.Close() 153 154 if !isTPMEnabled(tpm) { 155 return nil 156 } 157 158 return whatHow(tpm) 159 } 160 161 // MeasureSnapSystemEpochWhenPossible measures the snap system epoch only if the 162 // TPM device is available. If there's no TPM device success is returned. 163 func MeasureSnapSystemEpochWhenPossible() error { 164 measure := func(tpm *sb.TPMConnection) error { 165 return sbMeasureSnapSystemEpochToTPM(tpm, initramfsPCR) 166 } 167 168 if err := measureWhenPossible(measure); err != nil { 169 return fmt.Errorf("cannot measure snap system epoch: %v", err) 170 } 171 172 return nil 173 } 174 175 // MeasureSnapModelWhenPossible measures the snap model only if the TPM device is 176 // available. If there's no TPM device success is returned. 177 func MeasureSnapModelWhenPossible(findModel func() (*asserts.Model, error)) error { 178 measure := func(tpm *sb.TPMConnection) error { 179 model, err := findModel() 180 if err != nil { 181 return err 182 } 183 return sbMeasureSnapModelToTPM(tpm, initramfsPCR, model) 184 } 185 186 if err := measureWhenPossible(measure); err != nil { 187 return fmt.Errorf("cannot measure snap model: %v", err) 188 } 189 190 return nil 191 } 192 193 // LockSealedKeys manually locks access to the sealed keys. Meant to be 194 // called in place of passing lockKeysOnFinish as true to 195 // UnlockVolumeUsingSealedKeyIfEncrypted for cases where we don't know if a 196 // given call is the last one to unlock a volume like in degraded recover mode. 197 func LockSealedKeys() error { 198 if FDEHasRevealKey() { 199 return lockFDERevealSealedKeys() 200 } 201 return lockTPMSealedKeys() 202 } 203 204 func lockFDERevealSealedKeys() error { 205 buf, err := json.Marshal(FDERevealKeyRequest{ 206 Op: "lock", 207 }) 208 if err != nil { 209 return fmt.Errorf(`cannot build request for fde-reveal-key "lock": %v`, err) 210 } 211 if output, err := runFDERevealKeyCommand(buf); err != nil { 212 return fmt.Errorf(`cannot run fde-reveal-key "lock": %v`, osutil.OutputErr(output, err)) 213 } 214 215 return nil 216 } 217 218 func lockTPMSealedKeys() error { 219 tpm, tpmErr := sbConnectToDefaultTPM() 220 if tpmErr != nil { 221 if xerrors.Is(tpmErr, sb.ErrNoTPM2Device) { 222 logger.Noticef("cannot open TPM connection: %v", tpmErr) 223 return nil 224 } 225 return fmt.Errorf("cannot lock TPM: %v", tpmErr) 226 } 227 defer tpm.Close() 228 229 // Lock access to the sealed keys. This should be called whenever there 230 // is a TPM device detected, regardless of whether secure boot is enabled 231 // or there is an encrypted volume to unlock. Note that snap-bootstrap can 232 // be called several times during initialization, and if there are multiple 233 // volumes to unlock we should lock access to the sealed keys only after 234 // the last encrypted volume is unlocked, in which case lockKeysOnFinish 235 // should be set to true. 236 // 237 // We should only touch the PCR that we've currently reserved for the kernel 238 // EFI image. Touching others will break the ability to perform any kind of 239 // attestation using the TPM because it will make the log inconsistent. 240 return sbBlockPCRProtectionPolicies(tpm, []int{initramfsPCR}) 241 } 242 243 // UnlockVolumeUsingSealedKeyIfEncrypted verifies whether an encrypted volume 244 // with the specified name exists and unlocks it using a sealed key in a file 245 // with a corresponding name. The options control activation with the 246 // recovery key will be attempted if a prior activation attempt with 247 // the sealed key fails. 248 // 249 // Note that if the function proceeds to the point where it knows definitely 250 // whether there is an encrypted device or not, IsEncrypted on the return 251 // value will be true, even if error is non-nil. This is so that callers can be 252 // robust and try unlocking using another method for example. 253 func UnlockVolumeUsingSealedKeyIfEncrypted(disk disks.Disk, name string, sealedEncryptionKeyFile string, opts *UnlockVolumeUsingSealedKeyOptions) (UnlockResult, error) { 254 res := UnlockResult{} 255 256 // find the encrypted device using the disk we were provided - note that 257 // we do not specify IsDecryptedDevice in opts because here we are 258 // looking for the encrypted device to unlock, later on in the boot 259 // process we will look for the decrypted device to ensure it matches 260 // what we expected 261 partUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(EncryptedPartitionName(name)) 262 if err == nil { 263 res.IsEncrypted = true 264 } else { 265 var errNotFound disks.PartitionNotFoundError 266 if !xerrors.As(err, &errNotFound) { 267 // some other kind of catastrophic error searching 268 return res, fmt.Errorf("error enumerating partitions for disk to find encrypted device %q: %v", name, err) 269 } 270 // otherwise it is an error not found and we should search for the 271 // unencrypted device 272 partUUID, err = disk.FindMatchingPartitionUUIDWithFsLabel(name) 273 if err != nil { 274 return res, fmt.Errorf("error enumerating partitions for disk to find unencrypted device %q: %v", name, err) 275 } 276 } 277 278 partDevice := filepath.Join("/dev/disk/by-partuuid", partUUID) 279 280 if !res.IsEncrypted { 281 // if we didn't find an encrypted device just return, don't try to 282 // unlock it 283 // the filesystem device for the unencrypted case is the same as the 284 // partition device 285 res.PartDevice = partDevice 286 res.FsDevice = res.PartDevice 287 return res, nil 288 } 289 290 mapperName := name + "-" + randutilRandomKernelUUID() 291 sourceDevice := partDevice 292 targetDevice := filepath.Join("/dev/mapper", mapperName) 293 294 if FDEHasRevealKey() { 295 return unlockVolumeUsingSealedKeyFDERevealKey(name, sealedEncryptionKeyFile, sourceDevice, targetDevice, mapperName, opts) 296 } else { 297 return unlockVolumeUsingSealedKeySecboot(name, sealedEncryptionKeyFile, sourceDevice, targetDevice, mapperName, opts) 298 } 299 } 300 301 // FDERevealKeyRequest carries the operation and parameters for the 302 // fde-reveal-key binary to support unsealing keys that were sealed 303 // with the "fde-setup" hook. 304 type FDERevealKeyRequest struct { 305 Op string `json:"op"` 306 307 SealedKey []byte `json:"sealed-key,omitempty"` 308 KeyName string `json:"key-name,omitempty"` 309 310 // TODO: add VolumeName,SourceDevicePath later 311 } 312 313 // fdeRevealKeyRuntimeMax is the maximum runtime a fde-reveal-key can execute 314 // XXX: what is a reasonable default here? 315 var fdeRevealKeyRuntimeMax = 2 * time.Minute 316 317 // 50 ms means we check at a frequency 20 Hz, fast enough to not hold 318 // up boot, but not too fast that we are hogging the CPU from the 319 // thing we are waiting to finish running 320 var fdeRevealKeyPollWait = 50 * time.Millisecond 321 322 // fdeRevealKeyPollWaitParanoiaFactor controls much longer we wait 323 // then fdeRevealKeyRuntimeMax before stopping to poll for results 324 var fdeRevealKeyPollWaitParanoiaFactor = 2 325 326 // overridden in tests 327 var fdeRevealKeyCommandExtra []string 328 329 // runFDERevealKeyCommand returns the output of fde-reveal-key run 330 // with systemd. 331 // 332 // Note that systemd-run in the initrd can only talk to the private 333 // systemd bus so this cannot use "--pipe" or "--wait", see 334 // https://github.com/snapcore/core-initrd/issues/13 335 func runFDERevealKeyCommand(stdin []byte) (output []byte, err error) { 336 runDir := filepath.Join(dirs.GlobalRootDir, "/run/fde-reveal-key") 337 if err := os.MkdirAll(runDir, 0700); err != nil { 338 return nil, fmt.Errorf("cannot create tmp dir for fde-reveal-key: %v", err) 339 } 340 341 // delete and re-create the std{in,out,err} stream files that we use for the 342 // hook to be robust against bugs where the files are created with too 343 // permissive permissions or not properly deleted afterwards since the hook 344 // will be invoked multiple times during the initrd and we want to be really 345 // careful since the stdout file will contain the unsealed encryption key 346 for _, stream := range []string{"stdin", "stdout", "stderr"} { 347 streamFile := filepath.Join(runDir, "fde-reveal-key."+stream) 348 // we want to make sure that the file permissions for stdout are always 349 // 0600, so to ensure this is the case and be robust against bugs, we 350 // always delete the file and re-create it with 0600 351 352 // note that if the file already exists, WriteFile will not change the 353 // permissions, so deleting first is the right thing to do 354 os.Remove(streamFile) 355 if stream == "stdin" { 356 err = ioutil.WriteFile(streamFile, stdin, 0600) 357 } else { 358 err = ioutil.WriteFile(streamFile, nil, 0600) 359 } 360 if err != nil { 361 return nil, fmt.Errorf("cannot create %s for fde-reveal-key: %v", stream, err) 362 } 363 } 364 365 // TODO: put this into a new "systemd/run" package 366 cmd := exec.Command( 367 "systemd-run", 368 "--collect", 369 "--service-type=exec", 370 "--quiet", 371 // ensure we get some result from the hook within a 372 // reasonable timeout and output from systemd if 373 // things go wrong 374 fmt.Sprintf("--property=RuntimeMaxSec=%s", fdeRevealKeyRuntimeMax), 375 // Do not allow mounting, this ensures hooks in initrd 376 // can not mess around with ubuntu-data. 377 // 378 // Note that this is not about perfect confinement, more about 379 // making sure that people using the hook know that we do not 380 // want them to mess around outside of just providing unseal. 381 "--property=SystemCallFilter=~@mount", 382 // WORKAROUNDS 383 // workaround the lack of "--pipe" 384 fmt.Sprintf("--property=StandardInput=file:%s/fde-reveal-key.stdin", runDir), 385 // NOTE: these files are manually created above with 0600 because by 386 // default systemd will create them 0644 and we want to be paranoid here 387 fmt.Sprintf("--property=StandardOutput=file:%s/fde-reveal-key.stdout", runDir), 388 fmt.Sprintf("--property=StandardError=file:%s/fde-reveal-key.stderr", runDir), 389 // this ensures we get useful output for e.g. segfaults 390 fmt.Sprintf(`--property=ExecStopPost=/bin/sh -c 'if [ "$EXIT_STATUS" = 0 ]; then touch %[1]s/fde-reveal-key.success; else echo "service result: $SERVICE_RESULT" >%[1]s/fde-reveal-key.failed; fi'`, runDir), 391 ) 392 if fdeRevealKeyCommandExtra != nil { 393 cmd.Args = append(cmd.Args, fdeRevealKeyCommandExtra...) 394 } 395 // fde-reveal-key is what we actually need to run 396 cmd.Args = append(cmd.Args, "fde-reveal-key") 397 398 // ensure we cleanup our tmp files 399 defer func() { 400 if err := os.RemoveAll(runDir); err != nil { 401 logger.Noticef("cannot remove tmp dir: %v", err) 402 } 403 }() 404 405 // run the command 406 output, err = cmd.CombinedOutput() 407 if err != nil { 408 return output, err 409 } 410 411 // This loop will be terminate by systemd-run, either because 412 // fde-reveal-key exists or it gets killed when it reaches the 413 // fdeRevealKeyRuntimeMax defined above. 414 // 415 // However we are paranoid and exit this loop if systemd 416 // did not terminate the process after twice the allocated 417 // runtime 418 maxLoops := int(fdeRevealKeyRuntimeMax/fdeRevealKeyPollWait) * fdeRevealKeyPollWaitParanoiaFactor 419 for i := 0; i < maxLoops; i++ { 420 switch { 421 case osutil.FileExists(filepath.Join(runDir, "fde-reveal-key.failed")): 422 stderr, _ := ioutil.ReadFile(filepath.Join(runDir, "fde-reveal-key.stderr")) 423 systemdErr, _ := ioutil.ReadFile(filepath.Join(runDir, "fde-reveal-key.failed")) 424 buf := bytes.NewBuffer(stderr) 425 buf.Write(systemdErr) 426 return buf.Bytes(), fmt.Errorf("fde-reveal-key failed") 427 case osutil.FileExists(filepath.Join(runDir, "fde-reveal-key.success")): 428 return ioutil.ReadFile(filepath.Join(runDir, "fde-reveal-key.stdout")) 429 default: 430 time.Sleep(fdeRevealKeyPollWait) 431 } 432 } 433 434 // this should never happen, the loop above should be terminated 435 // via systemd 436 return nil, fmt.Errorf("internal error: systemd-run did not honor RuntimeMax=%s setting", fdeRevealKeyRuntimeMax) 437 } 438 439 func unlockVolumeUsingSealedKeyFDERevealKey(name, sealedEncryptionKeyFile, sourceDevice, targetDevice, mapperName string, opts *UnlockVolumeUsingSealedKeyOptions) (UnlockResult, error) { 440 res := UnlockResult{IsEncrypted: true, PartDevice: sourceDevice} 441 442 sealedKey, err := ioutil.ReadFile(sealedEncryptionKeyFile) 443 if err != nil { 444 return res, fmt.Errorf("cannot read sealed key file: %v", err) 445 } 446 buf, err := json.Marshal(FDERevealKeyRequest{ 447 Op: "reveal", 448 SealedKey: sealedKey, 449 KeyName: name, 450 }) 451 if err != nil { 452 return res, fmt.Errorf("cannot build request for fde-reveal-key: %v", err) 453 } 454 output, err := runFDERevealKeyCommand(buf) 455 if err != nil { 456 return res, fmt.Errorf("cannot run fde-reveal-key: %v", osutil.OutputErr(output, err)) 457 } 458 459 // the output of fde-reveal-key is the unsealed key 460 unsealedKey := output 461 if err := unlockEncryptedPartitionWithKey(mapperName, sourceDevice, unsealedKey); err != nil { 462 return res, fmt.Errorf("cannot unlock encrypted partition: %v", err) 463 } 464 res.FsDevice = targetDevice 465 res.UnlockMethod = UnlockedWithSealedKey 466 return res, nil 467 } 468 469 func unlockVolumeUsingSealedKeySecboot(name, sealedEncryptionKeyFile, sourceDevice, targetDevice, mapperName string, opts *UnlockVolumeUsingSealedKeyOptions) (UnlockResult, error) { 470 // TODO:UC20: use sb.SecureConnectToDefaultTPM() if we decide there's benefit in doing that or 471 // we have a hard requirement for a valid EK cert chain for every boot (ie, panic 472 // if there isn't one). But we can't do that as long as we need to download 473 // intermediate certs from the manufacturer. 474 475 res := UnlockResult{IsEncrypted: true, PartDevice: sourceDevice} 476 // Obtain a TPM connection. 477 tpm, tpmErr := sbConnectToDefaultTPM() 478 if tpmErr != nil { 479 if !xerrors.Is(tpmErr, sb.ErrNoTPM2Device) { 480 return res, fmt.Errorf("cannot unlock encrypted device %q: %v", name, tpmErr) 481 } 482 logger.Noticef("cannot open TPM connection: %v", tpmErr) 483 } else { 484 defer tpm.Close() 485 } 486 487 // Also check if the TPM device is enabled. The platform firmware may disable the storage 488 // and endorsement hierarchies, but the device will remain visible to the operating system. 489 tpmDeviceAvailable := tpmErr == nil && isTPMEnabled(tpm) 490 491 // if we don't have a tpm, and we allow using a recovery key, do that 492 // directly 493 if !tpmDeviceAvailable && opts.AllowRecoveryKey { 494 if err := UnlockEncryptedVolumeWithRecoveryKey(mapperName, sourceDevice); err != nil { 495 return res, err 496 } 497 res.FsDevice = targetDevice 498 res.UnlockMethod = UnlockedWithRecoveryKey 499 return res, nil 500 } 501 502 // otherwise we have a tpm and we should use the sealed key first, but 503 // this method will fallback to using the recovery key if enabled 504 method, err := unlockEncryptedPartitionWithSealedKey(tpm, mapperName, sourceDevice, sealedEncryptionKeyFile, "", opts.AllowRecoveryKey) 505 res.UnlockMethod = method 506 if err == nil { 507 res.FsDevice = targetDevice 508 } 509 return res, err 510 } 511 512 // UnlockEncryptedVolumeUsingKey unlocks an existing volume using the provided key. 513 func UnlockEncryptedVolumeUsingKey(disk disks.Disk, name string, key []byte) (UnlockResult, error) { 514 unlockRes := UnlockResult{ 515 UnlockMethod: NotUnlocked, 516 } 517 // find the encrypted device using the disk we were provided - note that 518 // we do not specify IsDecryptedDevice in opts because here we are 519 // looking for the encrypted device to unlock, later on in the boot 520 // process we will look for the decrypted device to ensure it matches 521 // what we expected 522 partUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(EncryptedPartitionName(name)) 523 if err != nil { 524 return unlockRes, err 525 } 526 unlockRes.IsEncrypted = true 527 // we have a device 528 encdev := filepath.Join("/dev/disk/by-partuuid", partUUID) 529 unlockRes.PartDevice = encdev 530 // make up a new name for the mapped device 531 mapperName := name + "-" + randutilRandomKernelUUID() 532 if err := unlockEncryptedPartitionWithKey(mapperName, encdev, key); err != nil { 533 return unlockRes, err 534 } 535 536 unlockRes.FsDevice = filepath.Join("/dev/mapper/", mapperName) 537 unlockRes.UnlockMethod = UnlockedWithKey 538 return unlockRes, nil 539 } 540 541 // UnlockEncryptedVolumeWithRecoveryKey prompts for the recovery key and uses it 542 // to open an encrypted device. 543 func UnlockEncryptedVolumeWithRecoveryKey(name, device string) error { 544 options := sb.ActivateVolumeOptions{ 545 RecoveryKeyTries: 3, 546 KeyringPrefix: keyringPrefix, 547 } 548 549 if err := sbActivateVolumeWithRecoveryKey(name, device, nil, &options); err != nil { 550 return fmt.Errorf("cannot unlock encrypted device %q: %v", device, err) 551 } 552 553 return nil 554 } 555 556 func isActivatedWithRecoveryKey(err error) bool { 557 if err == nil { 558 return false 559 } 560 // with non-nil err, we should check for err being ActivateWithTPMSealedKeyError 561 // and RecoveryKeyUsageErr inside that being nil - this indicates that the 562 // recovery key was used to unlock it 563 activateErr, ok := err.(*sb.ActivateWithTPMSealedKeyError) 564 if !ok { 565 return false 566 } 567 return activateErr.RecoveryKeyUsageErr == nil 568 } 569 570 // unlockEncryptedPartitionWithSealedKey unseals the keyfile and opens an encrypted 571 // device. If activation with the sealed key fails, this function will attempt to 572 // activate it with the fallback recovery key instead. 573 func unlockEncryptedPartitionWithSealedKey(tpm *sb.TPMConnection, name, device, keyfile, pinfile string, allowRecovery bool) (UnlockMethod, error) { 574 options := sb.ActivateVolumeOptions{ 575 PassphraseTries: 1, 576 // disable recovery key by default 577 RecoveryKeyTries: 0, 578 KeyringPrefix: keyringPrefix, 579 } 580 if allowRecovery { 581 // enable recovery key only when explicitly allowed 582 options.RecoveryKeyTries = 3 583 } 584 585 // XXX: pinfile is currently not used 586 activated, err := sbActivateVolumeWithTPMSealedKey(tpm, name, device, keyfile, nil, &options) 587 588 if activated { 589 // non nil error may indicate the volume was unlocked using the 590 // recovery key 591 if err == nil { 592 logger.Noticef("successfully activated encrypted device %q with TPM", device) 593 return UnlockedWithSealedKey, nil 594 } else if isActivatedWithRecoveryKey(err) { 595 logger.Noticef("successfully activated encrypted device %q using a fallback activation method", device) 596 return UnlockedWithRecoveryKey, nil 597 } 598 // no other error is possible when activation succeeded 599 return UnlockStatusUnknown, fmt.Errorf("internal error: volume activated with unexpected error: %v", err) 600 } 601 // ActivateVolumeWithTPMSealedKey should always return an error if activated == false 602 return NotUnlocked, fmt.Errorf("cannot activate encrypted device %q: %v", device, err) 603 } 604 605 // unlockEncryptedPartitionWithKey unlocks encrypted partition with the provided 606 // key. 607 func unlockEncryptedPartitionWithKey(name, device string, key []byte) error { 608 // no special options set 609 options := sb.ActivateVolumeOptions{} 610 err := sbActivateVolumeWithKey(name, device, key, &options) 611 if err == nil { 612 logger.Noticef("successfully activated encrypted device %v using a key", device) 613 } 614 return err 615 } 616 617 // SealKeys provisions the TPM and seals the encryption keys according to the 618 // specified parameters. If the TPM is already provisioned, or a sealed key already 619 // exists, SealKeys will fail and return an error. 620 func SealKeys(keys []SealKeyRequest, params *SealKeysParams) error { 621 numModels := len(params.ModelParams) 622 if numModels < 1 { 623 return fmt.Errorf("at least one set of model-specific parameters is required") 624 } 625 626 tpm, err := sbConnectToDefaultTPM() 627 if err != nil { 628 return fmt.Errorf("cannot connect to TPM: %v", err) 629 } 630 defer tpm.Close() 631 if !isTPMEnabled(tpm) { 632 return fmt.Errorf("TPM device is not enabled") 633 } 634 635 pcrProfile, err := buildPCRProtectionProfile(params.ModelParams) 636 if err != nil { 637 return err 638 } 639 640 if params.TPMProvision { 641 // Provision the TPM as late as possible 642 if err := tpmProvision(tpm, params.TPMLockoutAuthFile); err != nil { 643 return err 644 } 645 } 646 647 // Seal the provided keys to the TPM 648 creationParams := sb.KeyCreationParams{ 649 PCRProfile: pcrProfile, 650 PCRPolicyCounterHandle: tpm2.Handle(params.PCRPolicyCounterHandle), 651 AuthKey: params.TPMPolicyAuthKey, 652 } 653 654 sbKeys := make([]*sb.SealKeyRequest, 0, len(keys)) 655 for i := range keys { 656 sbKeys = append(sbKeys, &sb.SealKeyRequest{ 657 Key: keys[i].Key[:], 658 Path: keys[i].KeyFile, 659 }) 660 } 661 662 authKey, err := sbSealKeyToTPMMultiple(tpm, sbKeys, &creationParams) 663 if err != nil { 664 return err 665 } 666 if params.TPMPolicyAuthKeyFile != "" { 667 if err := osutil.AtomicWriteFile(params.TPMPolicyAuthKeyFile, authKey, 0600, 0); err != nil { 668 return fmt.Errorf("cannot write the policy auth key file: %v", err) 669 } 670 } 671 672 return nil 673 } 674 675 // ResealKeys updates the PCR protection policy for the sealed encryption keys 676 // according to the specified parameters. 677 func ResealKeys(params *ResealKeysParams) error { 678 numModels := len(params.ModelParams) 679 if numModels < 1 { 680 return fmt.Errorf("at least one set of model-specific parameters is required") 681 } 682 683 tpm, err := sbConnectToDefaultTPM() 684 if err != nil { 685 return fmt.Errorf("cannot connect to TPM: %v", err) 686 } 687 defer tpm.Close() 688 if !isTPMEnabled(tpm) { 689 return fmt.Errorf("TPM device is not enabled") 690 } 691 692 pcrProfile, err := buildPCRProtectionProfile(params.ModelParams) 693 if err != nil { 694 return err 695 } 696 697 authKey, err := ioutil.ReadFile(params.TPMPolicyAuthKeyFile) 698 if err != nil { 699 return fmt.Errorf("cannot read the policy auth key file: %v", err) 700 } 701 702 return sbUpdateKeyPCRProtectionPolicyMultiple(tpm, params.KeyFiles, authKey, pcrProfile) 703 } 704 705 func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb.PCRProtectionProfile, error) { 706 numModels := len(modelParams) 707 modelPCRProfiles := make([]*sb.PCRProtectionProfile, 0, numModels) 708 709 for _, mp := range modelParams { 710 modelProfile := sb.NewPCRProtectionProfile() 711 712 loadSequences, err := buildLoadSequences(mp.EFILoadChains) 713 if err != nil { 714 return nil, fmt.Errorf("cannot build EFI image load sequences: %v", err) 715 } 716 717 // Add EFI secure boot policy profile 718 policyParams := sb.EFISecureBootPolicyProfileParams{ 719 PCRAlgorithm: tpm2.HashAlgorithmSHA256, 720 LoadSequences: loadSequences, 721 // TODO:UC20: set SignatureDbUpdateKeystore to support applying forbidden 722 // signature updates to blacklist signing keys (after rotating them). 723 // This also requires integration of sbkeysync, and some work to 724 // ensure that the PCR profile is updated before/after sbkeysync executes. 725 } 726 727 if err := sbAddEFISecureBootPolicyProfile(modelProfile, &policyParams); err != nil { 728 return nil, fmt.Errorf("cannot add EFI secure boot policy profile: %v", err) 729 } 730 731 // Add EFI boot manager profile 732 bootManagerParams := sb.EFIBootManagerProfileParams{ 733 PCRAlgorithm: tpm2.HashAlgorithmSHA256, 734 LoadSequences: loadSequences, 735 } 736 if err := sbAddEFIBootManagerProfile(modelProfile, &bootManagerParams); err != nil { 737 return nil, fmt.Errorf("cannot add EFI boot manager profile: %v", err) 738 } 739 740 // Add systemd EFI stub profile 741 if len(mp.KernelCmdlines) != 0 { 742 systemdStubParams := sb.SystemdEFIStubProfileParams{ 743 PCRAlgorithm: tpm2.HashAlgorithmSHA256, 744 PCRIndex: initramfsPCR, 745 KernelCmdlines: mp.KernelCmdlines, 746 } 747 if err := sbAddSystemdEFIStubProfile(modelProfile, &systemdStubParams); err != nil { 748 return nil, fmt.Errorf("cannot add systemd EFI stub profile: %v", err) 749 } 750 } 751 752 // Add snap model profile 753 if mp.Model != nil { 754 snapModelParams := sb.SnapModelProfileParams{ 755 PCRAlgorithm: tpm2.HashAlgorithmSHA256, 756 PCRIndex: initramfsPCR, 757 Models: []sb.SnapModel{mp.Model}, 758 } 759 if err := sbAddSnapModelProfile(modelProfile, &snapModelParams); err != nil { 760 return nil, fmt.Errorf("cannot add snap model profile: %v", err) 761 } 762 } 763 764 modelPCRProfiles = append(modelPCRProfiles, modelProfile) 765 } 766 767 var pcrProfile *sb.PCRProtectionProfile 768 if numModels > 1 { 769 pcrProfile = sb.NewPCRProtectionProfile().AddProfileOR(modelPCRProfiles...) 770 } else { 771 pcrProfile = modelPCRProfiles[0] 772 } 773 774 logger.Debugf("PCR protection profile:\n%s", pcrProfile.String()) 775 776 return pcrProfile, nil 777 } 778 779 func tpmProvision(tpm *sb.TPMConnection, lockoutAuthFile string) error { 780 // Create and save the lockout authorization file 781 lockoutAuth := make([]byte, 16) 782 // crypto rand is protected against short reads 783 _, err := rand.Read(lockoutAuth) 784 if err != nil { 785 return fmt.Errorf("cannot create lockout authorization: %v", err) 786 } 787 if err := osutil.AtomicWriteFile(lockoutAuthFile, lockoutAuth, 0600, 0); err != nil { 788 return fmt.Errorf("cannot write the lockout authorization file: %v", err) 789 } 790 791 // TODO:UC20: ideally we should ask the firmware to clear the TPM and then reboot 792 // if the device has previously been provisioned, see 793 // https://godoc.org/github.com/snapcore/secboot#RequestTPMClearUsingPPI 794 if err := provisionTPM(tpm, sb.ProvisionModeFull, lockoutAuth); err != nil { 795 logger.Noticef("TPM provisioning error: %v", err) 796 return fmt.Errorf("cannot provision TPM: %v", err) 797 } 798 return nil 799 } 800 801 func provisionTPMImpl(tpm *sb.TPMConnection, mode sb.ProvisionMode, lockoutAuth []byte) error { 802 return tpm.EnsureProvisioned(mode, lockoutAuth) 803 } 804 805 // buildLoadSequences builds EFI load image event trees from this package LoadChains 806 func buildLoadSequences(chains []*LoadChain) (loadseqs []*sb.EFIImageLoadEvent, err error) { 807 // this will build load event trees for the current 808 // device configuration, e.g. something like: 809 // 810 // shim -> recovery grub -> recovery kernel 1 811 // |-> recovery kernel 2 812 // |-> recovery kernel ... 813 // |-> normal grub -> run kernel good 814 // |-> run kernel try 815 816 for _, chain := range chains { 817 // root of load events has source Firmware 818 loadseq, err := chain.loadEvent(sb.Firmware) 819 if err != nil { 820 return nil, err 821 } 822 loadseqs = append(loadseqs, loadseq) 823 } 824 return loadseqs, nil 825 } 826 827 // loadEvent builds the corresponding load event and its tree 828 func (lc *LoadChain) loadEvent(source sb.EFIImageLoadEventSource) (*sb.EFIImageLoadEvent, error) { 829 var next []*sb.EFIImageLoadEvent 830 for _, nextChain := range lc.Next { 831 // everything that is not the root has source shim 832 ev, err := nextChain.loadEvent(sb.Shim) 833 if err != nil { 834 return nil, err 835 } 836 next = append(next, ev) 837 } 838 image, err := efiImageFromBootFile(lc.BootFile) 839 if err != nil { 840 return nil, err 841 } 842 return &sb.EFIImageLoadEvent{ 843 Source: source, 844 Image: image, 845 Next: next, 846 }, nil 847 } 848 849 func efiImageFromBootFile(b *bootloader.BootFile) (sb.EFIImage, error) { 850 if b.Snap == "" { 851 if !osutil.FileExists(b.Path) { 852 return nil, fmt.Errorf("file %s does not exist", b.Path) 853 } 854 return sb.FileEFIImage(b.Path), nil 855 } 856 857 snapf, err := snapfile.Open(b.Snap) 858 if err != nil { 859 return nil, err 860 } 861 return sb.SnapFileEFIImage{ 862 Container: snapf, 863 FileName: b.Path, 864 }, nil 865 }