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