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  }