gitee.com/mysnapcore/mysnapd@v0.1.0/cmd/snap-fde-keymgr/main.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 main
    21  
    22  import (
    23  	"encoding/json"
    24  	"fmt"
    25  	"io"
    26  	"io/ioutil"
    27  	"os"
    28  	"strings"
    29  
    30  	"github.com/jessevdk/go-flags"
    31  
    32  	"gitee.com/mysnapcore/mysnapd/osutil"
    33  	"gitee.com/mysnapcore/mysnapd/secboot/keymgr"
    34  	"gitee.com/mysnapcore/mysnapd/secboot/keys"
    35  )
    36  
    37  var osStdin io.Reader = os.Stdin
    38  
    39  type commonMultiDeviceMixin struct {
    40  	Devices        []string `long:"devices" description:"encrypted devices (can be more than one)" required:"yes"`
    41  	Authorizations []string `long:"authorizations" description:"authorization sources (one for each device, either 'keyring' or 'file:<key-file>')" required:"yes"`
    42  }
    43  
    44  type cmdAddRecoveryKey struct {
    45  	commonMultiDeviceMixin
    46  	KeyFile string `long:"key-file" description:"path for generated recovery key file" required:"yes"`
    47  }
    48  
    49  type cmdRemoveRecoveryKey struct {
    50  	commonMultiDeviceMixin
    51  	KeyFiles []string `long:"key-files" description:"path to recovery key files to be removed" required:"yes"`
    52  }
    53  
    54  type cmdChangeEncryptionKey struct {
    55  	Device     string `long:"device" description:"encrypted device" required:"yes"`
    56  	Stage      bool   `long:"stage" description:"stage the new key"`
    57  	Transition bool   `long:"transition" description:"replace the old key, unstage the new"`
    58  }
    59  
    60  type options struct {
    61  	CmdAddRecoveryKey      cmdAddRecoveryKey      `command:"add-recovery-key"`
    62  	CmdRemoveRecoveryKey   cmdRemoveRecoveryKey   `command:"remove-recovery-key"`
    63  	CmdChangeEncryptionKey cmdChangeEncryptionKey `command:"change-encryption-key"`
    64  }
    65  
    66  var (
    67  	keymgrAddRecoveryKeyToLUKSDevice              = keymgr.AddRecoveryKeyToLUKSDevice
    68  	keymgrAddRecoveryKeyToLUKSDeviceUsingKey      = keymgr.AddRecoveryKeyToLUKSDeviceUsingKey
    69  	keymgrRemoveRecoveryKeyFromLUKSDevice         = keymgr.RemoveRecoveryKeyFromLUKSDevice
    70  	keymgrRemoveRecoveryKeyFromLUKSDeviceUsingKey = keymgr.RemoveRecoveryKeyFromLUKSDeviceUsingKey
    71  	keymgrStageLUKSDeviceEncryptionKeyChange      = keymgr.StageLUKSDeviceEncryptionKeyChange
    72  	keymgrTransitionLUKSDeviceEncryptionKeyChange = keymgr.TransitionLUKSDeviceEncryptionKeyChange
    73  )
    74  
    75  func validateAuthorizations(authorizations []string) error {
    76  	for _, authz := range authorizations {
    77  		switch {
    78  		case authz == "keyring":
    79  			// happy
    80  		case strings.HasPrefix(authz, "file:"):
    81  			// file must exist
    82  			kf := authz[len("file:"):]
    83  			if !osutil.FileExists(kf) {
    84  				return fmt.Errorf("authorization file %v does not exist", kf)
    85  			}
    86  		default:
    87  			return fmt.Errorf("unknown authorization method %q", authz)
    88  		}
    89  	}
    90  	return nil
    91  }
    92  
    93  func writeIfNotExists(p string, data []byte) (alreadyExists bool, err error) {
    94  	f, err := os.OpenFile(p, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600)
    95  	if err != nil {
    96  		if os.IsExist(err) {
    97  			return true, nil
    98  		}
    99  		return false, err
   100  	}
   101  	if _, err := f.Write(data); err != nil {
   102  		f.Close()
   103  		return false, err
   104  	}
   105  	return false, f.Close()
   106  }
   107  
   108  func (c *cmdAddRecoveryKey) Execute(args []string) error {
   109  	recoveryKey, err := keys.NewRecoveryKey()
   110  	if err != nil {
   111  		return fmt.Errorf("cannot create recovery key: %v", err)
   112  	}
   113  	if len(c.Authorizations) != len(c.Devices) {
   114  		return fmt.Errorf("cannot add recovery keys: mismatch in the number of devices and authorizations")
   115  	}
   116  	if err := validateAuthorizations(c.Authorizations); err != nil {
   117  		return fmt.Errorf("cannot add recovery keys with invalid authorizations: %v", err)
   118  	}
   119  	// write the key to the file, if the file already exists it is possible
   120  	// that we are being called again after an unexpected reboot or a
   121  	// similar event
   122  	alreadyExists, err := writeIfNotExists(c.KeyFile, recoveryKey[:])
   123  	if err != nil {
   124  		return fmt.Errorf("cannot write recovery key to file: %v", err)
   125  	}
   126  	if alreadyExists {
   127  		// we already have the recovery key, read it back
   128  		maybeKey, err := ioutil.ReadFile(c.KeyFile)
   129  		if err != nil {
   130  			return fmt.Errorf("cannot read existing recovery key file: %v", err)
   131  		}
   132  		// TODO: verify that the size if non 0 and try again otherwise?
   133  		if len(maybeKey) != len(recoveryKey) {
   134  			return fmt.Errorf("cannot use existing recovery key of size %v", len(maybeKey))
   135  		}
   136  		copy(recoveryKey[:], maybeKey[:])
   137  	}
   138  	// add the recovery key to each device; keys are always added to the
   139  	// same keyslot, so when the key existed on disk, assume that the key
   140  	// was already added to the device in case we hit an error with keyslot
   141  	// being already used
   142  	for i, dev := range c.Devices {
   143  		authz := c.Authorizations[i]
   144  		switch {
   145  		case authz == "keyring":
   146  			if err := keymgrAddRecoveryKeyToLUKSDevice(recoveryKey, dev); err != nil {
   147  				if !alreadyExists || !keymgr.IsKeyslotAlreadyUsed(err) {
   148  					return fmt.Errorf("cannot add recovery key to LUKS device: %v", err)
   149  				}
   150  			}
   151  		case strings.HasPrefix(authz, "file:"):
   152  			authzKey, err := ioutil.ReadFile(authz[len("file:"):])
   153  			if err != nil {
   154  				return fmt.Errorf("cannot load authorization key: %v", err)
   155  			}
   156  			if err := keymgrAddRecoveryKeyToLUKSDeviceUsingKey(recoveryKey, authzKey, dev); err != nil {
   157  				if !alreadyExists || !keymgr.IsKeyslotAlreadyUsed(err) {
   158  					return fmt.Errorf("cannot add recovery key to LUKS device using authorization key: %v", err)
   159  				}
   160  			}
   161  		}
   162  	}
   163  	return nil
   164  }
   165  
   166  func (c *cmdRemoveRecoveryKey) Execute(args []string) error {
   167  	if len(c.Authorizations) != len(c.Devices) {
   168  		return fmt.Errorf("cannot remove recovery keys: mismatch in the number of devices and authorizations")
   169  	}
   170  	if err := validateAuthorizations(c.Authorizations); err != nil {
   171  		return fmt.Errorf("cannot remove recovery keys with invalid authorizations: %v", err)
   172  	}
   173  	for i, dev := range c.Devices {
   174  		authz := c.Authorizations[i]
   175  		switch {
   176  		case authz == "keyring":
   177  			if err := keymgrRemoveRecoveryKeyFromLUKSDevice(dev); err != nil {
   178  				return fmt.Errorf("cannot remove recovery key from LUKS device: %v", err)
   179  			}
   180  		case strings.HasPrefix(authz, "file:"):
   181  			authzKey, err := ioutil.ReadFile(authz[len("file:"):])
   182  			if err != nil {
   183  				return fmt.Errorf("cannot load authorization key: %v", err)
   184  			}
   185  			if err := keymgrRemoveRecoveryKeyFromLUKSDeviceUsingKey(authzKey, dev); err != nil {
   186  				return fmt.Errorf("cannot remove recovery key from device using authorization key: %v", err)
   187  			}
   188  		}
   189  	}
   190  	var rmErrors []string
   191  	for _, kf := range c.KeyFiles {
   192  		if err := os.Remove(kf); err != nil && !os.IsNotExist(err) {
   193  			rmErrors = append(rmErrors, err.Error())
   194  		}
   195  	}
   196  	if len(rmErrors) != 0 {
   197  		return fmt.Errorf("cannot remove key files:\n%s", strings.Join(rmErrors, "\n"))
   198  	}
   199  	return nil
   200  }
   201  
   202  type newKey struct {
   203  	Key []byte `json:"key"`
   204  }
   205  
   206  func (c *cmdChangeEncryptionKey) Execute(args []string) error {
   207  	if c.Stage && c.Transition {
   208  		return fmt.Errorf("cannot both stage and transition the encryption key change")
   209  	}
   210  	if !c.Stage && !c.Transition {
   211  		return fmt.Errorf("cannot change encryption key without stage or transition request")
   212  	}
   213  
   214  	var newEncryptionKeyData newKey
   215  	dec := json.NewDecoder(osStdin)
   216  	if err := dec.Decode(&newEncryptionKeyData); err != nil {
   217  		return fmt.Errorf("cannot obtain new encryption key: %v", err)
   218  	}
   219  	switch {
   220  	case c.Stage:
   221  		// staging the key change authorizes the operation using a key
   222  		// from the keyring
   223  		if err := keymgrStageLUKSDeviceEncryptionKeyChange(newEncryptionKeyData.Key, c.Device); err != nil {
   224  			return fmt.Errorf("cannot stage LUKS device encryption key change: %v", err)
   225  		}
   226  	case c.Transition:
   227  		// transitioning the key change authorizes the operation using
   228  		// the currently provided key (which must have been staged
   229  		// before hence the op will be authorized successfully)
   230  		if err := keymgrTransitionLUKSDeviceEncryptionKeyChange(newEncryptionKeyData.Key, c.Device); err != nil {
   231  			return fmt.Errorf("cannot transition LUKS device encryption key change: %v", err)
   232  		}
   233  	}
   234  	return nil
   235  }
   236  
   237  func run(osArgs1 []string) error {
   238  	var opts options
   239  	p := flags.NewParser(&opts, flags.HelpFlag|flags.PassDoubleDash)
   240  	if _, err := p.ParseArgs(osArgs1); err != nil {
   241  		return err
   242  	}
   243  	return nil
   244  }
   245  
   246  func main() {
   247  	if err := run(os.Args[1:]); err != nil {
   248  		fmt.Fprintf(os.Stderr, "error: %v\n", err)
   249  		os.Exit(1)
   250  	}
   251  }