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  }