gitee.com/mysnapcore/mysnapd@v0.1.0/secboot/keymgr/keymgr_luks2.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2022 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package keymgr
    21  
    22  import (
    23  	"fmt"
    24  	"regexp"
    25  	"time"
    26  
    27  	sb "gitee.com/mysnapcore/mysecboot"
    28  
    29  	"gitee.com/mysnapcore/mysnapd/osutil"
    30  	"gitee.com/mysnapcore/mysnapd/secboot/keys"
    31  	"gitee.com/mysnapcore/mysnapd/secboot/luks2"
    32  )
    33  
    34  const (
    35  	// key slot used by the encryption key
    36  	encryptionKeySlot = 0
    37  	// key slot used by the recovery key
    38  	recoveryKeySlot = 1
    39  	// temporary key slot used when changing the encryption key
    40  	tempKeySlot = recoveryKeySlot + 1
    41  )
    42  
    43  var (
    44  	sbGetDiskUnlockKeyFromKernel = sb.GetDiskUnlockKeyFromKernel
    45  )
    46  
    47  func getEncryptionKeyFromUserKeyring(dev string) ([]byte, error) {
    48  	const remove = false
    49  	const defaultPrefix = "ubuntu-fde"
    50  	// note this is the unlock key, which can be either the main key which
    51  	// was unsealed, or the recovery key, in which case some operations may
    52  	// not make sense
    53  	currKey, err := sbGetDiskUnlockKeyFromKernel(defaultPrefix, dev, remove)
    54  	if err != nil {
    55  		return nil, fmt.Errorf("cannot obtain current unlock key for %v: %v", dev, err)
    56  	}
    57  	return currKey, err
    58  }
    59  
    60  // TODO rather than inspecting the error messages, parse the LUKS2 headers
    61  
    62  var keyslotFull = regexp.MustCompile(`^.*cryptsetup failed with: Key slot [0-9]+ is full, please select another one\.$`)
    63  
    64  // IsKeyslotAlreadyUsed returns true if the error indicates that the keyslot
    65  // attempted for a given key is already used
    66  func IsKeyslotAlreadyUsed(err error) bool {
    67  	if err == nil {
    68  		return false
    69  	}
    70  	return keyslotFull.MatchString(err.Error())
    71  }
    72  
    73  func isKeyslotNotActive(err error) bool {
    74  	match, _ := regexp.MatchString(`.*: Keyslot [0-9]+ is not active`, err.Error())
    75  	return match
    76  }
    77  
    78  func recoveryKDF() (*luks2.KDFOptions, error) {
    79  	usableMem, err := osutil.TotalUsableMemory()
    80  	if err != nil {
    81  		return nil, fmt.Errorf("cannot get usable memory for KDF parameters when adding the recovery key: %v", err)
    82  	}
    83  	// The KDF memory is heuristically calculated by taking the
    84  	// usable memory and subtracting hardcoded 384MB that is
    85  	// needed to keep the system working. Half of that is the mem
    86  	// we want to use for the KDF. Doing it this way avoids the expensive
    87  	// benchmark from cryptsetup. The recovery key is already 128bit
    88  	// strong so we don't need to be super precise here.
    89  	kdfMem := (int(usableMem) - 384*1024*1024) / 2
    90  	// at most 1 GB, but at least 32 kB
    91  	if kdfMem > 1024*1024*1024 {
    92  		kdfMem = (1024 * 1024 * 1024)
    93  	} else if kdfMem < 32*1024 {
    94  		kdfMem = 32 * 1024
    95  	}
    96  	return &luks2.KDFOptions{
    97  		MemoryKiB:       kdfMem / 1024,
    98  		ForceIterations: 4,
    99  	}, nil
   100  }
   101  
   102  // AddRecoveryKeyToLUKSDevice adds a recovery key to a LUKS2 device. It the
   103  // devuce unlock key from the user keyring to authorize the change. The
   104  // recoveyry key is added to keyslot 1.
   105  func AddRecoveryKeyToLUKSDevice(recoveryKey keys.RecoveryKey, dev string) error {
   106  	currKey, err := getEncryptionKeyFromUserKeyring(dev)
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	return AddRecoveryKeyToLUKSDeviceUsingKey(recoveryKey, currKey, dev)
   112  }
   113  
   114  // AddRecoveryKeyToLUKSDeviceUsingKey adds a recovery key rkey to the existing
   115  // LUKS encrypted volume on the block device given by node. The existing key to
   116  // the encrypted volume is provided in the key argument and used to authorize
   117  // the operation.
   118  //
   119  // A heuristic memory cost is used.
   120  func AddRecoveryKeyToLUKSDeviceUsingKey(recoveryKey keys.RecoveryKey, currKey keys.EncryptionKey, dev string) error {
   121  	opts, err := recoveryKDF()
   122  	if err != nil {
   123  		return err
   124  	}
   125  
   126  	options := luks2.AddKeyOptions{
   127  		KDFOptions: *opts,
   128  		Slot:       recoveryKeySlot,
   129  	}
   130  	if err := luks2.AddKey(dev, currKey, recoveryKey[:], &options); err != nil {
   131  		return fmt.Errorf("cannot add key: %v", err)
   132  	}
   133  
   134  	if err := luks2.SetSlotPriority(dev, encryptionKeySlot, luks2.SlotPriorityHigh); err != nil {
   135  		return fmt.Errorf("cannot change keyslot priority: %v", err)
   136  	}
   137  
   138  	return nil
   139  }
   140  
   141  // RemoveRecoveryKeyFromLUKSDevice removes an existing recovery key a LUKS2
   142  // device.
   143  func RemoveRecoveryKeyFromLUKSDevice(dev string) error {
   144  	currKey, err := getEncryptionKeyFromUserKeyring(dev)
   145  	if err != nil {
   146  		return err
   147  	}
   148  	return RemoveRecoveryKeyFromLUKSDeviceUsingKey(currKey, dev)
   149  }
   150  
   151  // RemoveRecoveryKeyFromLUKSDeviceUsingKey removes an existing recovery key a
   152  // LUKS2 using the provided key to authorize the operation.
   153  func RemoveRecoveryKeyFromLUKSDeviceUsingKey(currKey keys.EncryptionKey, dev string) error {
   154  	// just remove the key we think is a recovery key (luks keyslot 1)
   155  	if err := luks2.KillSlot(dev, recoveryKeySlot, currKey); err != nil {
   156  		if !isKeyslotNotActive(err) {
   157  			return fmt.Errorf("cannot kill recovery key slot: %v", err)
   158  		}
   159  	}
   160  	return nil
   161  }
   162  
   163  // StageLUKSDeviceEncryptionKeyChange stages a new encryption key with the goal
   164  // of changing the main encryption key referenced in keyslot 0. The operation is
   165  // authorized using the key that unlocked the device and is stored in the
   166  // keyring (as it happens during factory reset).
   167  func StageLUKSDeviceEncryptionKeyChange(newKey keys.EncryptionKey, dev string) error {
   168  	if len(newKey) != keys.EncryptionKeySize {
   169  		return fmt.Errorf("cannot use a key of size different than %v", keys.EncryptionKeySize)
   170  	}
   171  
   172  	// the key to authorize the device is in the keyring
   173  	currKey, err := getEncryptionKeyFromUserKeyring(dev)
   174  	if err != nil {
   175  		return err
   176  	}
   177  
   178  	// TODO rather than inspecting the errors, parse the LUKS2 headers
   179  
   180  	// free up the temp slot
   181  	if err := luks2.KillSlot(dev, tempKeySlot, currKey); err != nil {
   182  		if !isKeyslotNotActive(err) {
   183  			return fmt.Errorf("cannot kill the temporary keyslot: %v", err)
   184  		}
   185  	}
   186  
   187  	options := luks2.AddKeyOptions{
   188  		KDFOptions: luks2.KDFOptions{TargetDuration: 100 * time.Millisecond},
   189  		Slot:       tempKeySlot,
   190  	}
   191  	if err := luks2.AddKey(dev, currKey[:], newKey, &options); err != nil {
   192  		return fmt.Errorf("cannot add temporary key: %v", err)
   193  	}
   194  	return nil
   195  }
   196  
   197  // TransitionLUKSDeviceEncryptionKeyChange completes the main encryption key
   198  // change to the new key provided in the parameters. The new key must have been
   199  // staged before, thus it can authorize LUKS operations. Lastly, the unlock key
   200  // in the keyring is updated to the new key.
   201  func TransitionLUKSDeviceEncryptionKeyChange(newKey keys.EncryptionKey, dev string) error {
   202  	if len(newKey) != keys.EncryptionKeySize {
   203  		return fmt.Errorf("cannot use a key of size different than %v", keys.EncryptionKeySize)
   204  	}
   205  
   206  	// the expected state is as follows:
   207  	// key slot 0 - the old encryption key
   208  	// key slot 2 - the new encryption key (added during --stage)
   209  	// the desired state is:
   210  	// key slot 0 - the new encryption key
   211  	// key slot 2 - empty
   212  	// it is possible that the system was rebooted right after key slot 0 was
   213  	// populated with the new key and key slot 2 was emptied
   214  
   215  	// there is no state information on disk which would tell if the
   216  	// scenario 1 above occurred and to which stage it was executed, but we
   217  	// need to find out if key slot 2 is in use (as the caller believes that
   218  	// a key was staged earlier); do this indirectly by trying to add a key
   219  	// to key slot 2
   220  
   221  	// TODO rather than inspecting the errors, parse the LUKS2 headers
   222  
   223  	tempKeyslotAlreadyUsed := true
   224  
   225  	options := luks2.AddKeyOptions{
   226  		KDFOptions: luks2.KDFOptions{TargetDuration: 100 * time.Millisecond},
   227  		Slot:       tempKeySlot,
   228  	}
   229  	err := luks2.AddKey(dev, newKey, newKey, &options)
   230  	if err == nil {
   231  		// key slot is not in use, so we are dealing with unexpected reboot scenario
   232  		tempKeyslotAlreadyUsed = false
   233  	} else if err != nil && !IsKeyslotAlreadyUsed(err) {
   234  		return fmt.Errorf("cannot add new encryption key: %v", err)
   235  	}
   236  
   237  	if !tempKeyslotAlreadyUsed {
   238  		// since the key slot was not used, it means that the transition
   239  		// was already carried out (since it got authorized by the new
   240  		// key), so now all is needed is to remove the added key
   241  		if err := luks2.KillSlot(dev, tempKeySlot, newKey); err != nil {
   242  			return fmt.Errorf("cannot kill temporary key slot: %v", err)
   243  		}
   244  
   245  		return nil
   246  	}
   247  
   248  	// first kill the main encryption key slot, authorize the operation
   249  	// using the new key which must have been added to the temp keyslot in
   250  	// the stage operation
   251  	if err := luks2.KillSlot(dev, encryptionKeySlot, newKey); err != nil {
   252  		if !isKeyslotNotActive(err) {
   253  			return fmt.Errorf("cannot kill the encryption key slot: %v", err)
   254  		}
   255  	}
   256  
   257  	options = luks2.AddKeyOptions{
   258  		KDFOptions: luks2.KDFOptions{TargetDuration: 100 * time.Millisecond},
   259  		Slot:       encryptionKeySlot,
   260  	}
   261  	if err := luks2.AddKey(dev, newKey, newKey, &options); err != nil {
   262  		return fmt.Errorf("cannot add new encryption key: %v", err)
   263  	}
   264  
   265  	// now it should be possible to kill the temporary keyslot by using the
   266  	// new key for authorization
   267  	if err := luks2.KillSlot(dev, tempKeySlot, newKey); err != nil {
   268  		if !isKeyslotNotActive(err) {
   269  			return fmt.Errorf("cannot kill temporary key slot: %v", err)
   270  		}
   271  	}
   272  	// TODO needed?
   273  	if err := luks2.SetSlotPriority(dev, encryptionKeySlot, luks2.SlotPriorityHigh); err != nil {
   274  		return fmt.Errorf("cannot change key slot priority: %v", err)
   275  	}
   276  	return nil
   277  }