gitee.com/mysnapcore/mysnapd@v0.1.0/secboot/luks2/cryptsetup.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 luks2
    21  
    22  import (
    23  	"bytes"
    24  	"fmt"
    25  	"io"
    26  	"os"
    27  	"os/exec"
    28  	"strconv"
    29  	"time"
    30  
    31  	"gitee.com/mysnapcore/mysnapd/osutil"
    32  
    33  	"golang.org/x/xerrors"
    34  )
    35  
    36  const (
    37  	// AnySlot tells a command to automatically choose an appropriate slot
    38  	// as opposed to hard coding one.
    39  	AnySlot = -1
    40  )
    41  
    42  // cryptsetupCmd is a helper for running the cryptsetup command. If stdin is supplied, data read
    43  // from it is supplied to cryptsetup via its stdin. If callback is supplied, it will be invoked
    44  // after cryptsetup has started.
    45  func cryptsetupCmd(stdin io.Reader, callback func(cmd *exec.Cmd) error, args ...string) error {
    46  	cmd := exec.Command("cryptsetup", args...)
    47  	cmd.Stdin = stdin
    48  
    49  	var b bytes.Buffer
    50  	cmd.Stdout = &b
    51  	cmd.Stderr = &b
    52  
    53  	if err := cmd.Start(); err != nil {
    54  		return xerrors.Errorf("cannot start cryptsetup: %w", err)
    55  	}
    56  
    57  	var cbErr error
    58  	if callback != nil {
    59  		cbErr = callback(cmd)
    60  	}
    61  
    62  	err := cmd.Wait()
    63  
    64  	switch {
    65  	case cbErr != nil:
    66  		return cbErr
    67  	case err != nil:
    68  		return fmt.Errorf("cryptsetup failed with: %v", osutil.OutputErr(b.Bytes(), err))
    69  	}
    70  
    71  	return nil
    72  }
    73  
    74  // KDFOptions specifies parameters for the Argon2 KDF.
    75  type KDFOptions struct {
    76  	// TargetDuration specifies the target time for benchmarking of the
    77  	// time and memory cost parameters. If it is zero then the cryptsetup
    78  	// default is used. If ForceIterations is not zero then this is ignored.
    79  	TargetDuration time.Duration
    80  
    81  	// MemoryKiB specifies the maximum memory cost in KiB when ForceIterations
    82  	// is zero, or the actual memory cost in KiB when ForceIterations is not zero.
    83  	// If this is set to zero, then the cryptsetup default is used.
    84  	MemoryKiB int
    85  
    86  	// ForceIterations specifies the time cost. If set to zero, the time
    87  	// and memory cost are determined by benchmarking the algorithm based on
    88  	// the specified TargetDuration. Set to a non-zero number to force the
    89  	// time cost to the value of this field, and the memory cost to the value
    90  	// of MemoryKiB, disabling benchmarking.
    91  	ForceIterations int
    92  
    93  	// Parallel sets the maximum number of parallel threads. Cryptsetup may
    94  	// choose a lower value based on its own maximum and the number of available
    95  	// CPU cores.
    96  	Parallel int
    97  }
    98  
    99  func (options *KDFOptions) appendArguments(args []string) []string {
   100  	// use argon2i as the KDF
   101  	args = append(args, "--pbkdf", "argon2i")
   102  
   103  	switch {
   104  	case options.ForceIterations != 0:
   105  		// Disable benchmarking by forcing the time cost
   106  		args = append(args,
   107  			"--pbkdf-force-iterations", strconv.Itoa(options.ForceIterations))
   108  	case options.TargetDuration != 0:
   109  		args = append(args,
   110  			"--iter-time", strconv.FormatInt(int64(options.TargetDuration/time.Millisecond), 10))
   111  	}
   112  
   113  	if options.MemoryKiB != 0 {
   114  		args = append(args, "--pbkdf-memory", strconv.Itoa(options.MemoryKiB))
   115  	}
   116  
   117  	if options.Parallel != 0 {
   118  		args = append(args, "--pbkdf-parallel", strconv.Itoa(options.Parallel))
   119  	}
   120  
   121  	return args
   122  }
   123  
   124  // AddKeyOptions provides the options for adding a key to a LUKS2 volume
   125  type AddKeyOptions struct {
   126  	// KDFOptions describes the KDF options for the new key slot.
   127  	KDFOptions KDFOptions
   128  
   129  	// Slot is the keyslot to use. Note that the default value is slot 0. In
   130  	// order to automatically choose a slot, use AnySlot.
   131  	Slot int
   132  }
   133  
   134  // AddKey adds the supplied key in to a new keyslot for specified LUKS2 container. In order to do this,
   135  // an existing key must be provided. The KDF for the new keyslot will be configured to use argon2i with
   136  // the supplied benchmark time. The key will be added to the supplied slot.
   137  //
   138  // If options is not supplied, the default KDF benchmark time is used and the command will
   139  // automatically choose an appropriate slot.
   140  func AddKey(devicePath string, existingKey, key []byte, options *AddKeyOptions) error {
   141  	if options == nil {
   142  		options = &AddKeyOptions{Slot: AnySlot}
   143  	}
   144  
   145  	fifoPath, cleanupFifo, err := mkFifo()
   146  	if err != nil {
   147  		return xerrors.Errorf("cannot create FIFO for passing existing key to cryptsetup: %w", err)
   148  	}
   149  	defer cleanupFifo()
   150  
   151  	args := []string{
   152  		// add a new key
   153  		"luksAddKey",
   154  		// LUKS2 only
   155  		"--type", "luks2",
   156  		// read existing key from named pipe
   157  		"--key-file", fifoPath}
   158  
   159  	// apply KDF options
   160  	args = options.KDFOptions.appendArguments(args)
   161  
   162  	if options.Slot != AnySlot {
   163  		args = append(args, "--key-slot", strconv.Itoa(options.Slot))
   164  	}
   165  
   166  	args = append(args,
   167  		// container to add key to
   168  		devicePath,
   169  		// read new key from stdin.
   170  		// Note that we can't supply the new key and existing key via the same channel
   171  		// because pipes and FIFOs aren't seekable - we would need to use an actual file
   172  		// in order to be able to do this.
   173  		"-")
   174  
   175  	writeExistingKeyToFifo := func(cmd *exec.Cmd) error {
   176  		f, err := os.OpenFile(fifoPath, os.O_WRONLY, 0)
   177  		if err != nil {
   178  			// If we fail to open the write end, the read end will be blocked in open(), so
   179  			// kill the process.
   180  			cmd.Process.Kill()
   181  			return xerrors.Errorf("cannot open FIFO for passing existing key to cryptsetup: %w", err)
   182  		}
   183  
   184  		if _, err := f.Write(existingKey); err != nil {
   185  			// The read end is open and blocked inside read(). Closing our write end will result in the
   186  			// read end returning 0 bytes (EOF) and continuing cleanly.
   187  			if err := f.Close(); err != nil {
   188  				// If we can't close the write end, the read end will remain blocked inside read(),
   189  				// so kill the process.
   190  				cmd.Process.Kill()
   191  			}
   192  			return xerrors.Errorf("cannot pass existing key to cryptsetup: %w", err)
   193  		}
   194  
   195  		if err := f.Close(); err != nil {
   196  			// If we can't close the write end, the read end will remain blocked inside read(),
   197  			// so kill the process.
   198  			cmd.Process.Kill()
   199  			return xerrors.Errorf("cannot close write end of FIFO: %w", err)
   200  		}
   201  
   202  		return nil
   203  	}
   204  
   205  	return cryptsetupCmd(bytes.NewReader(key), writeExistingKeyToFifo, args...)
   206  }
   207  
   208  // KillSlot erases the keyslot with the supplied slot number from the specified LUKS2 container.
   209  // Note that a valid key for a remaining keyslot must be supplied, in order to prevent the last
   210  // keyslot from being erased.
   211  func KillSlot(devicePath string, slot int, key []byte) error {
   212  	return cryptsetupCmd(bytes.NewReader(key), nil, "luksKillSlot", "--type", "luks2", "--key-file", "-", devicePath, strconv.Itoa(slot))
   213  }
   214  
   215  // SetSlotPriority sets the priority of the keyslot with the supplied slot number on
   216  // the specified LUKS2 container.
   217  func SetSlotPriority(devicePath string, slot int, priority SlotPriority) error {
   218  	return cryptsetupCmd(nil, nil, "config", "--priority", priority.String(), "--key-slot", strconv.Itoa(slot), devicePath)
   219  }