gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/secboot/secboot_sb.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  // +build !nosecboot
     3  
     4  /*
     5   * Copyright (C) 2021 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  	"fmt"
    25  	"path/filepath"
    26  
    27  	sb "github.com/snapcore/secboot"
    28  	"golang.org/x/xerrors"
    29  
    30  	"github.com/snapcore/snapd/kernel/fde"
    31  	"github.com/snapcore/snapd/logger"
    32  	"github.com/snapcore/snapd/osutil/disks"
    33  )
    34  
    35  var (
    36  	sbActivateVolumeWithKey         = sb.ActivateVolumeWithKey
    37  	sbActivateVolumeWithKeyData     = sb.ActivateVolumeWithKeyData
    38  	sbActivateVolumeWithRecoveryKey = sb.ActivateVolumeWithRecoveryKey
    39  	sbDeactivateVolume              = sb.DeactivateVolume
    40  )
    41  
    42  func init() {
    43  	WithSecbootSupport = true
    44  }
    45  
    46  // LockSealedKeys manually locks access to the sealed keys. Meant to be
    47  // called in place of passing lockKeysOnFinish as true to
    48  // UnlockVolumeUsingSealedKeyIfEncrypted for cases where we don't know if a
    49  // given call is the last one to unlock a volume like in degraded recover mode.
    50  func LockSealedKeys() error {
    51  	if fdeHasRevealKey() {
    52  		return fde.LockSealedKeys()
    53  	}
    54  	return lockTPMSealedKeys()
    55  }
    56  
    57  // UnlockVolumeUsingSealedKeyIfEncrypted verifies whether an encrypted volume
    58  // with the specified name exists and unlocks it using a sealed key in a file
    59  // with a corresponding name. The options control activation with the
    60  // recovery key will be attempted if a prior activation attempt with
    61  // the sealed key fails.
    62  //
    63  // Note that if the function proceeds to the point where it knows definitely
    64  // whether there is an encrypted device or not, IsEncrypted on the return
    65  // value will be true, even if error is non-nil. This is so that callers can be
    66  // robust and try unlocking using another method for example.
    67  func UnlockVolumeUsingSealedKeyIfEncrypted(disk disks.Disk, name string, sealedEncryptionKeyFile string, opts *UnlockVolumeUsingSealedKeyOptions) (UnlockResult, error) {
    68  	res := UnlockResult{}
    69  
    70  	// find the encrypted device using the disk we were provided - note that
    71  	// we do not specify IsDecryptedDevice in opts because here we are
    72  	// looking for the encrypted device to unlock, later on in the boot
    73  	// process we will look for the decrypted device to ensure it matches
    74  	// what we expected
    75  	partUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(EncryptedPartitionName(name))
    76  	if err == nil {
    77  		res.IsEncrypted = true
    78  	} else {
    79  		var errNotFound disks.PartitionNotFoundError
    80  		if !xerrors.As(err, &errNotFound) {
    81  			// some other kind of catastrophic error searching
    82  			return res, fmt.Errorf("error enumerating partitions for disk to find encrypted device %q: %v", name, err)
    83  		}
    84  		// otherwise it is an error not found and we should search for the
    85  		// unencrypted device
    86  		partUUID, err = disk.FindMatchingPartitionUUIDWithFsLabel(name)
    87  		if err != nil {
    88  			return res, fmt.Errorf("error enumerating partitions for disk to find unencrypted device %q: %v", name, err)
    89  		}
    90  	}
    91  
    92  	partDevice := filepath.Join("/dev/disk/by-partuuid", partUUID)
    93  
    94  	if !res.IsEncrypted {
    95  		// if we didn't find an encrypted device just return, don't try to
    96  		// unlock it
    97  		// the filesystem device for the unencrypted case is the same as the
    98  		// partition device
    99  		res.PartDevice = partDevice
   100  		res.FsDevice = res.PartDevice
   101  		return res, nil
   102  	}
   103  
   104  	mapperName := name + "-" + randutilRandomKernelUUID()
   105  	sourceDevice := partDevice
   106  	targetDevice := filepath.Join("/dev/mapper", mapperName)
   107  
   108  	if fdeHasRevealKey() {
   109  		return unlockVolumeUsingSealedKeyFDERevealKey(name, sealedEncryptionKeyFile, sourceDevice, targetDevice, mapperName, opts)
   110  	} else {
   111  		return unlockVolumeUsingSealedKeyTPM(name, sealedEncryptionKeyFile, sourceDevice, targetDevice, mapperName, opts)
   112  	}
   113  }
   114  
   115  // UnlockEncryptedVolumeUsingKey unlocks an existing volume using the provided key.
   116  func UnlockEncryptedVolumeUsingKey(disk disks.Disk, name string, key []byte) (UnlockResult, error) {
   117  	unlockRes := UnlockResult{
   118  		UnlockMethod: NotUnlocked,
   119  	}
   120  	// find the encrypted device using the disk we were provided - note that
   121  	// we do not specify IsDecryptedDevice in opts because here we are
   122  	// looking for the encrypted device to unlock, later on in the boot
   123  	// process we will look for the decrypted device to ensure it matches
   124  	// what we expected
   125  	partUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(EncryptedPartitionName(name))
   126  	if err != nil {
   127  		return unlockRes, err
   128  	}
   129  	unlockRes.IsEncrypted = true
   130  	// we have a device
   131  	encdev := filepath.Join("/dev/disk/by-partuuid", partUUID)
   132  	unlockRes.PartDevice = encdev
   133  	// make up a new name for the mapped device
   134  	mapperName := name + "-" + randutilRandomKernelUUID()
   135  	if err := unlockEncryptedPartitionWithKey(mapperName, encdev, key); err != nil {
   136  		return unlockRes, err
   137  	}
   138  
   139  	unlockRes.FsDevice = filepath.Join("/dev/mapper/", mapperName)
   140  	unlockRes.UnlockMethod = UnlockedWithKey
   141  	return unlockRes, nil
   142  }
   143  
   144  // unlockEncryptedPartitionWithKey unlocks encrypted partition with the provided
   145  // key.
   146  func unlockEncryptedPartitionWithKey(name, device string, key []byte) error {
   147  	// no special options set
   148  	options := sb.ActivateVolumeOptions{}
   149  	err := sbActivateVolumeWithKey(name, device, key, &options)
   150  	if err == nil {
   151  		logger.Noticef("successfully activated encrypted device %v using a key", device)
   152  	}
   153  	return err
   154  }
   155  
   156  // UnlockEncryptedVolumeWithRecoveryKey prompts for the recovery key and uses it
   157  // to open an encrypted device.
   158  func UnlockEncryptedVolumeWithRecoveryKey(name, device string) error {
   159  	options := sb.ActivateVolumeOptions{
   160  		RecoveryKeyTries: 3,
   161  		KeyringPrefix:    keyringPrefix,
   162  	}
   163  
   164  	if err := sbActivateVolumeWithRecoveryKey(name, device, nil, &options); err != nil {
   165  		return fmt.Errorf("cannot unlock encrypted device %q: %v", device, err)
   166  	}
   167  
   168  	return nil
   169  }