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  }