gitee.com/mysnapcore/mysnapd@v0.1.0/secboot/keymgr/keymgr_luks2_test.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  package keymgr_test
    20  
    21  import (
    22  	"bytes"
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  	"testing"
    28  
    29  	sb "gitee.com/mysnapcore/mysecboot"
    30  	. "gopkg.in/check.v1"
    31  
    32  	"gitee.com/mysnapcore/mysnapd/dirs"
    33  	"gitee.com/mysnapcore/mysnapd/osutil"
    34  	"gitee.com/mysnapcore/mysnapd/secboot/keymgr"
    35  	"gitee.com/mysnapcore/mysnapd/secboot/keys"
    36  	"gitee.com/mysnapcore/mysnapd/secboot/luks2"
    37  	"gitee.com/mysnapcore/mysnapd/testutil"
    38  )
    39  
    40  type keymgrSuite struct {
    41  	testutil.BaseTest
    42  
    43  	rootDir       string
    44  	cryptsetupCmd *testutil.MockCmd
    45  }
    46  
    47  var _ = Suite(&keymgrSuite{})
    48  
    49  func TestT(t *testing.T) {
    50  	TestingT(t)
    51  }
    52  
    53  const mockedMeminfo = `MemTotal:         929956 kB
    54  CmaTotal:         131072 kB
    55  `
    56  
    57  var mockRecoveryKey = keys.RecoveryKey{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
    58  
    59  func (s *keymgrSuite) SetUpTest(c *C) {
    60  	s.BaseTest.SetUpTest(c)
    61  
    62  	s.cryptsetupCmd = testutil.MockCommand(c, "cryptsetup", ``)
    63  	s.AddCleanup(s.cryptsetupCmd.Restore)
    64  	s.rootDir = c.MkDir()
    65  	dirs.SetRootDir(s.rootDir)
    66  	s.AddCleanup(func() { dirs.SetRootDir("/") })
    67  	c.Assert(os.MkdirAll(dirs.RunDir, 0755), IsNil)
    68  
    69  	mockedMeminfoFile := filepath.Join(c.MkDir(), "meminfo")
    70  	err := ioutil.WriteFile(mockedMeminfoFile, []byte(mockedMeminfo), 0644)
    71  	c.Assert(err, IsNil)
    72  	s.AddCleanup(osutil.MockProcMeminfo(mockedMeminfoFile))
    73  }
    74  
    75  func (s *keymgrSuite) mockCryptsetupForAddKey(c *C) *testutil.MockCmd {
    76  	cmd := testutil.MockCommand(c, "cryptsetup", fmt.Sprintf(`
    77  while [ "$#" -gt 1 ]; do
    78    case "$1" in
    79      luksAddKey)
    80        cat > %s
    81        shift
    82        ;;
    83      --key-file)
    84        cat "$2" > %s
    85        shift 2
    86        ;;
    87      *)
    88        shift
    89        ;;
    90    esac
    91  done
    92  `, filepath.Join(s.rootDir, "new.key"), filepath.Join(s.rootDir, "unlock.key")))
    93  	return cmd
    94  }
    95  
    96  func (s *keymgrSuite) verifyCryptsetupAddKey(c *C, cmd *testutil.MockCmd, unlockKey, newKey []byte) {
    97  	c.Assert(cmd, NotNil)
    98  	calls := cmd.Calls()
    99  	c.Assert(calls, HasLen, 2)
   100  	c.Assert(calls[0], HasLen, 16)
   101  	c.Assert(calls[0][5], testutil.Contains, s.rootDir)
   102  	calls[0][5] = "<fifo>"
   103  	c.Assert(calls[0], DeepEquals, []string{
   104  		"cryptsetup", "luksAddKey", "--type", "luks2",
   105  		"--key-file", "<fifo>",
   106  		"--pbkdf", "argon2i",
   107  		"--pbkdf-force-iterations", "4",
   108  		"--pbkdf-memory", "202834",
   109  		"--key-slot", "1",
   110  		"/dev/foobar", "-",
   111  	})
   112  	c.Assert(calls[1], DeepEquals, []string{
   113  		"cryptsetup", "config", "--priority", "prefer", "--key-slot", "0", "/dev/foobar",
   114  	})
   115  	c.Check(filepath.Join(s.rootDir, "unlock.key"), testutil.FileEquals, unlockKey)
   116  	c.Check(filepath.Join(s.rootDir, "new.key"), testutil.FileEquals, newKey)
   117  }
   118  
   119  func (s *keymgrSuite) TestAddRecoveryKeyToDeviceUnlockFromKeyring(c *C) {
   120  	unlockKey := "1234abcd"
   121  	getCalls := 0
   122  	restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) {
   123  		getCalls++
   124  		c.Check(devicePath, Equals, "/dev/foobar")
   125  		c.Check(remove, Equals, false)
   126  		c.Check(prefix, Equals, "ubuntu-fde")
   127  		return []byte(unlockKey), nil
   128  	})
   129  	defer restore()
   130  
   131  	cmd := s.mockCryptsetupForAddKey(c)
   132  	defer cmd.Restore()
   133  	err := keymgr.AddRecoveryKeyToLUKSDevice(mockRecoveryKey, "/dev/foobar")
   134  	c.Assert(err, IsNil)
   135  	c.Assert(getCalls, Equals, 1)
   136  	s.verifyCryptsetupAddKey(c, cmd, []byte(unlockKey), mockRecoveryKey[:])
   137  }
   138  
   139  func (s *keymgrSuite) TestAddRecoveryKeyToDeviceNoUnlockKey(c *C) {
   140  	getCalls := 0
   141  	restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) {
   142  		getCalls++
   143  		c.Check(devicePath, Equals, "/dev/foobar")
   144  		return nil, fmt.Errorf("cannot find key in kernel keyring")
   145  	})
   146  	defer restore()
   147  
   148  	cmd := s.mockCryptsetupForAddKey(c)
   149  	defer cmd.Restore()
   150  	err := keymgr.AddRecoveryKeyToLUKSDevice(mockRecoveryKey, "/dev/foobar")
   151  	c.Assert(err, ErrorMatches, "cannot obtain current unlock key for /dev/foobar: cannot find key in kernel keyring")
   152  	c.Assert(getCalls, Equals, 1)
   153  	c.Assert(cmd.Calls(), HasLen, 0)
   154  }
   155  
   156  func (s *keymgrSuite) TestAddRecoveryKeyToDeviceCryptsetupFail(c *C) {
   157  	unlockKey := "1234abcd"
   158  	restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) {
   159  		return []byte(unlockKey), nil
   160  	})
   161  	defer restore()
   162  
   163  	cmd := testutil.MockCommand(c, "cryptsetup", `
   164  while [ "$#" -gt 1 ]; do
   165    case "$1" in
   166      --key-file)
   167        cat "$2" > /dev/null
   168        shift 2
   169        ;;
   170      *)
   171        shift 1
   172        ;;
   173    esac
   174  done
   175  echo "Other error, cryptsetup boom"
   176  exit 1
   177  `)
   178  	defer cmd.Restore()
   179  	err := keymgr.AddRecoveryKeyToLUKSDevice(mockRecoveryKey, "/dev/foobar")
   180  	c.Assert(err, ErrorMatches, "cannot add key: cryptsetup failed with: Other error, cryptsetup boom")
   181  	// should match the keyslot full error too
   182  	c.Assert(keymgr.IsKeyslotAlreadyUsed(err), Equals, false)
   183  }
   184  
   185  func (s *keymgrSuite) TestAddRecoveryKeyToDeviceOccupiedSlot(c *C) {
   186  	unlockKey := "1234abcd"
   187  	getCalls := 0
   188  	restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) {
   189  		getCalls++
   190  		c.Check(devicePath, Equals, "/dev/foobar")
   191  		c.Check(remove, Equals, false)
   192  		c.Check(prefix, Equals, "ubuntu-fde")
   193  		return []byte(unlockKey), nil
   194  	})
   195  	defer restore()
   196  
   197  	cmd := testutil.MockCommand(c, "cryptsetup", `
   198  while [ "$#" -gt 1 ]; do
   199    case "$1" in
   200      --key-file)
   201        cat "$2" > /dev/null
   202        shift 2
   203        ;;
   204      *)
   205        shift 1
   206        ;;
   207    esac
   208  done
   209  echo "Key slot 1 is full, please select another one." >&2
   210  exit 1
   211  `)
   212  	defer cmd.Restore()
   213  	err := keymgr.AddRecoveryKeyToLUKSDevice(mockRecoveryKey, "/dev/foobar")
   214  	c.Assert(err, ErrorMatches, "cannot add key: cryptsetup failed with: Key slot 1 is full.*")
   215  	c.Assert(getCalls, Equals, 1)
   216  	calls := cmd.Calls()
   217  	c.Assert(calls, HasLen, 1)
   218  	c.Assert(calls[0], HasLen, 16)
   219  	c.Assert(calls[0][:2], DeepEquals, []string{"cryptsetup", "luksAddKey"})
   220  	// should match the keyslot full error too
   221  	c.Assert(keymgr.IsKeyslotAlreadyUsed(err), Equals, true)
   222  }
   223  
   224  func (s *keymgrSuite) TestAddRecoveryKeyToDeviceUsingExistingKey(c *C) {
   225  	restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) {
   226  		return nil, fmt.Errorf("unexpected call")
   227  	})
   228  	defer restore()
   229  
   230  	cmd := s.mockCryptsetupForAddKey(c)
   231  	defer cmd.Restore()
   232  	key := bytes.Repeat([]byte{1}, 32)
   233  	err := keymgr.AddRecoveryKeyToLUKSDeviceUsingKey(mockRecoveryKey, keys.EncryptionKey(key), "/dev/foobar")
   234  	c.Assert(err, IsNil)
   235  	s.verifyCryptsetupAddKey(c, cmd, []byte(key), mockRecoveryKey[:])
   236  }
   237  
   238  func (s *keymgrSuite) TestRemoveRecoveryKeyFromDevice(c *C) {
   239  	unlockKey := "1234abcd"
   240  	getCalls := 0
   241  	restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) {
   242  		getCalls++
   243  		c.Check(devicePath, Equals, "/dev/foobar")
   244  		c.Check(remove, Equals, false)
   245  		c.Check(prefix, Equals, "ubuntu-fde")
   246  		return []byte(unlockKey), nil
   247  	})
   248  	defer restore()
   249  
   250  	err := keymgr.RemoveRecoveryKeyFromLUKSDevice("/dev/foobar")
   251  	c.Assert(err, IsNil)
   252  	c.Assert(getCalls, Equals, 1)
   253  	calls := s.cryptsetupCmd.Calls()
   254  	c.Assert(calls, DeepEquals, [][]string{
   255  		{"cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "1"},
   256  	})
   257  }
   258  
   259  func (s *keymgrSuite) TestRemoveRecoveryKeyFromDeviceAlreadyEmpty(c *C) {
   260  	unlockKey := "1234abcd"
   261  	getCalls := 0
   262  	restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) {
   263  		getCalls++
   264  		return []byte(unlockKey), nil
   265  	})
   266  	defer restore()
   267  
   268  	cmd := testutil.MockCommand(c, "cryptsetup", `
   269  echo "Keyslot 1 is not active." >&2
   270  exit 1
   271  `)
   272  	defer cmd.Restore()
   273  
   274  	err := keymgr.RemoveRecoveryKeyFromLUKSDevice("/dev/foobar")
   275  	c.Assert(err, IsNil)
   276  	c.Assert(getCalls, Equals, 1)
   277  	calls := cmd.Calls()
   278  	c.Assert(calls, DeepEquals, [][]string{
   279  		{"cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "1"},
   280  	})
   281  }
   282  
   283  func (s *keymgrSuite) TestRemoveRecoveryKeyFromDeviceKeyNotInKeyring(c *C) {
   284  	getCalls := 0
   285  	restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) {
   286  		getCalls++
   287  		c.Check(devicePath, Equals, "/dev/foobar")
   288  		return nil, fmt.Errorf("cannot find key in kernel keyring")
   289  	})
   290  	defer restore()
   291  
   292  	err := keymgr.RemoveRecoveryKeyFromLUKSDevice("/dev/foobar")
   293  	c.Assert(err, ErrorMatches, "cannot obtain current unlock key for /dev/foobar: cannot find key in kernel keyring")
   294  	c.Assert(getCalls, Equals, 1)
   295  	c.Assert(s.cryptsetupCmd.Calls(), HasLen, 0)
   296  }
   297  
   298  func (s *keymgrSuite) TestRemoveRecoveryKeyFromDeviceUsingKey(c *C) {
   299  	restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) {
   300  		c.Fail()
   301  		return nil, fmt.Errorf("unexpected call")
   302  	})
   303  	defer restore()
   304  
   305  	cryptsetupCmd := testutil.MockCommand(c, "cryptsetup", fmt.Sprintf(`
   306  while [ "$#" -gt 1 ]; do
   307    case "$1" in
   308      luksKillSlot)
   309        cat > %s
   310        shift
   311        ;;
   312      *)
   313        shift
   314        ;;
   315    esac
   316  done
   317  `, filepath.Join(s.rootDir, "unlock.key")))
   318  	defer cryptsetupCmd.Restore()
   319  
   320  	key := bytes.Repeat([]byte{1}, 32)
   321  	err := keymgr.RemoveRecoveryKeyFromLUKSDeviceUsingKey(keys.EncryptionKey(key), "/dev/foobar")
   322  	c.Assert(err, IsNil)
   323  	calls := cryptsetupCmd.Calls()
   324  	c.Assert(calls, DeepEquals, [][]string{
   325  		{"cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "1"},
   326  	})
   327  	c.Assert(filepath.Join(s.rootDir, "unlock.key"), testutil.FileEquals, key)
   328  }
   329  
   330  func (s *keymgrSuite) TestStageEncryptionKeyHappy(c *C) {
   331  	unlockKey := "1234abcd"
   332  	getCalls := 0
   333  	restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) {
   334  		getCalls++
   335  		c.Check(devicePath, Equals, "/dev/foobar")
   336  		c.Check(remove, Equals, false)
   337  		c.Check(prefix, Equals, "ubuntu-fde")
   338  		return []byte(unlockKey), nil
   339  	})
   340  	defer restore()
   341  
   342  	cmd := testutil.MockCommand(c, "cryptsetup", `
   343  while [ "$#" -gt 1 ]; do
   344    case "$1" in
   345      --key-file)
   346        cat "$2"
   347        shift 2
   348        ;;
   349      *)
   350        shift 1
   351        ;;
   352    esac
   353  done
   354  `)
   355  	defer cmd.Restore()
   356  	// try a too short key
   357  	key := bytes.Repeat([]byte{1}, 12)
   358  	err := keymgr.StageLUKSDeviceEncryptionKeyChange(key, "/dev/foobar")
   359  	c.Assert(err, ErrorMatches, "cannot use a key of size different than 32")
   360  
   361  	key = bytes.Repeat([]byte{1}, 32)
   362  	err = keymgr.StageLUKSDeviceEncryptionKeyChange(key, "/dev/foobar")
   363  	c.Assert(err, IsNil)
   364  	c.Assert(getCalls, Equals, 1)
   365  	calls := cmd.Calls()
   366  	c.Assert(calls, HasLen, 2)
   367  	c.Assert(calls[0], DeepEquals, []string{
   368  		"cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "2",
   369  	})
   370  	c.Assert(calls[1], HasLen, 14)
   371  	c.Assert(calls[1][5], testutil.Contains, dirs.RunDir)
   372  	calls[1][5] = "<fifo>"
   373  	// temporary key
   374  	c.Assert(calls[1], DeepEquals, []string{
   375  		"cryptsetup", "luksAddKey", "--type", "luks2",
   376  		"--key-file", "<fifo>",
   377  		"--pbkdf", "argon2i",
   378  		"--iter-time", "100",
   379  		"--key-slot", "2",
   380  		"/dev/foobar", "-",
   381  	})
   382  }
   383  
   384  func (s *keymgrSuite) TestStageEncryptionKeyKilledSlotAlreadyEmpty(c *C) {
   385  	unlockKey := "1234abcd"
   386  	restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) {
   387  		return []byte(unlockKey), nil
   388  	})
   389  	defer restore()
   390  
   391  	cmd := testutil.MockCommand(c, "cryptsetup", `
   392  while [ "$#" -gt 1 ]; do
   393    case "$1" in
   394      luksKillSlot)
   395        killslot=1
   396        shift
   397        ;;
   398      --key-file)
   399        cat "$2" > /dev/null
   400        shift 2
   401        ;;
   402      *)
   403        shift
   404        ;;
   405    esac
   406  done
   407  if [ "$killslot" = "1" ]; then
   408    echo "Keyslot 1 is not active." >&2
   409    exit 1
   410  fi
   411  `)
   412  	defer cmd.Restore()
   413  
   414  	key := bytes.Repeat([]byte{1}, 32)
   415  	err := keymgr.StageLUKSDeviceEncryptionKeyChange(key, "/dev/foobar")
   416  	c.Assert(err, IsNil)
   417  	calls := cmd.Calls()
   418  	c.Assert(calls, HasLen, 2)
   419  	c.Assert(calls[0], DeepEquals, []string{
   420  		"cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "2",
   421  	})
   422  	c.Assert(calls[1], HasLen, 14)
   423  	c.Assert(calls[1][5], testutil.Contains, dirs.RunDir)
   424  	calls[1][5] = "<fifo>"
   425  	// temporary key
   426  	c.Assert(calls[1], DeepEquals, []string{
   427  		"cryptsetup", "luksAddKey", "--type", "luks2",
   428  		"--key-file", "<fifo>",
   429  		"--pbkdf", "argon2i",
   430  		"--iter-time", "100",
   431  		"--key-slot", "2",
   432  		"/dev/foobar", "-",
   433  	})
   434  }
   435  
   436  func (s *keymgrSuite) TestStageEncryptionKeyGetUnlockFail(c *C) {
   437  	getCalls := 0
   438  	restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) {
   439  		getCalls++
   440  		c.Check(devicePath, Equals, "/dev/foobar")
   441  		return nil, fmt.Errorf("cannot find key in kernel keyring")
   442  	})
   443  	defer restore()
   444  
   445  	key := bytes.Repeat([]byte{1}, 32)
   446  	err := keymgr.StageLUKSDeviceEncryptionKeyChange(key, "/dev/foobar")
   447  	c.Assert(err, ErrorMatches, "cannot obtain current unlock key for /dev/foobar: cannot find key in kernel keyring")
   448  	c.Assert(s.cryptsetupCmd.Calls(), HasLen, 0)
   449  }
   450  
   451  func (s *keymgrSuite) TestChangeEncryptionTempKeyFails(c *C) {
   452  	unlockKey := "1234abcd"
   453  	getCalls := 0
   454  	restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) {
   455  		getCalls++
   456  		return []byte(unlockKey), nil
   457  	})
   458  	defer restore()
   459  
   460  	cmd := testutil.MockCommand(c, "cryptsetup", `
   461  while [ "$#" -gt 1 ]; do
   462    case "$1" in
   463      --key-file)
   464        cat "$2" > /dev/null
   465        shift
   466        ;;
   467      luksAddKey)
   468        add=1
   469        ;;
   470      --key-slot)
   471        if [ "$add" = "1" ] && [ "$2" = "2" ]; then
   472            echo "mock failure" >&2
   473            exit 1
   474        fi
   475        ;;
   476      *)
   477        ;;
   478    esac
   479    shift
   480  done
   481  `)
   482  	defer cmd.Restore()
   483  
   484  	key := bytes.Repeat([]byte{1}, 32)
   485  	err := keymgr.StageLUKSDeviceEncryptionKeyChange(key, "/dev/foobar")
   486  	c.Assert(err, ErrorMatches, "cannot add temporary key: cryptsetup failed with: mock failure")
   487  	c.Assert(getCalls, Equals, 1)
   488  	calls := cmd.Calls()
   489  	c.Assert(calls, HasLen, 2)
   490  }
   491  
   492  func (s *keymgrSuite) TestTransitionEncryptionKeyExpectedHappy(c *C) {
   493  	restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) {
   494  		c.Errorf("unepected call")
   495  		return nil, fmt.Errorf("unexpected call")
   496  	})
   497  	defer restore()
   498  
   499  	cmd := testutil.MockCommand(c, "cryptsetup", `
   500  while [ "$#" -gt 1 ]; do
   501    case "$1" in
   502      luksAddKey)
   503        keyadd=1
   504        shift
   505        ;;
   506      --key-slot)
   507        keyslot="$2"
   508        shift 2
   509        ;;
   510      --key-file)
   511        cat "$2" > /dev/null
   512        shift 2
   513        ;;
   514      *)
   515        shift 1
   516        ;;
   517    esac
   518  done
   519  if [ "$keyadd" = "1" ] && [ "$keyslot" = "2" ]; then
   520    echo "Key slot 2 is full, please select another one." >&2
   521    exit 1
   522  fi
   523  `)
   524  	defer cmd.Restore()
   525  	// try a too short key
   526  	key := bytes.Repeat([]byte{1}, 12)
   527  	err := keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar")
   528  	c.Assert(err, ErrorMatches, "cannot use a key of size different than 32")
   529  
   530  	key = bytes.Repeat([]byte{1}, 32)
   531  	err = keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar")
   532  	c.Assert(err, IsNil)
   533  	calls := cmd.Calls()
   534  	c.Assert(calls, HasLen, 5)
   535  	// probing the key slot use
   536  	c.Assert(calls[0], HasLen, 14)
   537  	c.Assert(calls[0][5], testutil.Contains, dirs.RunDir)
   538  	calls[0][5] = "<fifo>"
   539  	// temporary key
   540  	c.Assert(calls[0], DeepEquals, []string{
   541  		"cryptsetup", "luksAddKey", "--type", "luks2",
   542  		"--key-file", "<fifo>",
   543  		"--pbkdf", "argon2i",
   544  		"--iter-time", "100",
   545  		"--key-slot", "2",
   546  		"/dev/foobar", "-",
   547  	})
   548  	// killing the encryption key
   549  	c.Assert(calls[1], DeepEquals, []string{
   550  		"cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "0",
   551  	})
   552  	// adding the new encryption key
   553  	c.Assert(calls[2], HasLen, 14)
   554  	c.Assert(calls[2][5], testutil.Contains, dirs.RunDir)
   555  	calls[2][5] = "<fifo>"
   556  	c.Assert(calls[2], DeepEquals, []string{
   557  		"cryptsetup", "luksAddKey", "--type", "luks2",
   558  		"--key-file", "<fifo>",
   559  		"--pbkdf", "argon2i",
   560  		"--iter-time", "100",
   561  		"--key-slot", "0",
   562  		"/dev/foobar", "-",
   563  	})
   564  	// kill the temp key
   565  	c.Assert(calls[3], DeepEquals, []string{
   566  		"cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "2",
   567  	})
   568  	// set priority
   569  	c.Assert(calls[4], DeepEquals, []string{
   570  		"cryptsetup", "config", "--priority", "prefer", "--key-slot", "0", "/dev/foobar",
   571  	})
   572  }
   573  
   574  func (s *keymgrSuite) TestTransitionEncryptionKeyHappyKillSlotsInactive(c *C) {
   575  	restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) {
   576  		c.Errorf("unepected call")
   577  		return nil, fmt.Errorf("unexpected call")
   578  	})
   579  	defer restore()
   580  
   581  	cmd := testutil.MockCommand(c, "cryptsetup", `
   582  while [ "$#" -gt 1 ]; do
   583    case "$1" in
   584      luksKillSlot)
   585        killslot=1
   586        shift
   587        ;;
   588      luksAddKey)
   589        keyadd=1
   590        shift
   591        ;;
   592      --key-slot)
   593        keyslot="$2"
   594        shift 2
   595        ;;
   596      --key-file)
   597        cat "$2" > /dev/null
   598        shift 2
   599        ;;
   600      *)
   601        shift 1
   602        ;;
   603    esac
   604  done
   605  if [ "$keyadd" = "1" ] && [ "$keyslot" = "2" ]; then
   606    echo "Key slot 2 is full, please select another one." >&2
   607    exit 1
   608  elif [ "$killslot" = "1" ]; then
   609    echo "Keyslot 2 is not active." >&2
   610    exit 1
   611  fi
   612  `)
   613  	defer cmd.Restore()
   614  	// try a too short key
   615  	key := bytes.Repeat([]byte{1}, 12)
   616  	err := keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar")
   617  	c.Assert(err, ErrorMatches, "cannot use a key of size different than 32")
   618  
   619  	key = bytes.Repeat([]byte{1}, 32)
   620  	err = keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar")
   621  	c.Assert(err, IsNil)
   622  	calls := cmd.Calls()
   623  	c.Assert(calls, HasLen, 5)
   624  	// probing the key slot use
   625  	c.Assert(calls[0], HasLen, 14)
   626  	// temporary key
   627  	c.Assert(calls[0][:2], DeepEquals, []string{
   628  		"cryptsetup", "luksAddKey",
   629  	})
   630  	// killing the encryption key
   631  	c.Assert(calls[1], DeepEquals, []string{
   632  		"cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "0",
   633  	})
   634  	// adding the new encryption key
   635  	c.Assert(calls[2], HasLen, 14)
   636  	c.Assert(calls[2][5], testutil.Contains, dirs.RunDir)
   637  	calls[2][5] = "<fifo>"
   638  	c.Assert(calls[2], DeepEquals, []string{
   639  		"cryptsetup", "luksAddKey", "--type", "luks2",
   640  		"--key-file", "<fifo>",
   641  		"--pbkdf", "argon2i",
   642  		"--iter-time", "100",
   643  		"--key-slot", "0",
   644  		"/dev/foobar", "-",
   645  	})
   646  	// kill the temp key
   647  	c.Assert(calls[3], DeepEquals, []string{
   648  		"cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "2",
   649  	})
   650  	// set priority
   651  	c.Assert(calls[4], DeepEquals, []string{
   652  		"cryptsetup", "config", "--priority", "prefer", "--key-slot", "0", "/dev/foobar",
   653  	})
   654  }
   655  
   656  func (s *keymgrSuite) TestTransitionEncryptionKeyHappyOtherErrs(c *C) {
   657  	restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) {
   658  		c.Errorf("unepected call")
   659  		return nil, fmt.Errorf("unexpected call")
   660  	})
   661  	defer restore()
   662  
   663  	cmd := testutil.MockCommand(c, "cryptsetup", `
   664  while [ "$#" -gt 1 ]; do
   665    case "$1" in
   666      luksAddKey)
   667        keyadd=1
   668        shift
   669        ;;
   670      --key-slot)
   671        keyslot="$2"
   672        shift 2
   673        ;;
   674      --key-file)
   675        cat "$2" > /dev/null
   676        shift 2
   677        ;;
   678      *)
   679        shift 1
   680        ;;
   681    esac
   682  done
   683  if [ "$keyadd" = "1" ] && [ "$keyslot" = "2" ]; then
   684    echo "Key slot 2 is full, please select another one." >&2
   685    exit 1
   686  elif [ "$keyadd" = "1" ] && [ "$keyslot" = "0" ]; then
   687    echo "mock error" >&2
   688    exit 1
   689  fi
   690  `)
   691  	defer cmd.Restore()
   692  	key := bytes.Repeat([]byte{1}, 32)
   693  	err := keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar")
   694  	c.Assert(err, ErrorMatches, "cannot add new encryption key: cryptsetup failed with: mock error")
   695  	calls := cmd.Calls()
   696  	c.Assert(calls, HasLen, 3)
   697  	// probing the key slot use
   698  	c.Assert(calls[0], HasLen, 14)
   699  	// temporary key
   700  	c.Assert(calls[0][:2], DeepEquals, []string{
   701  		"cryptsetup", "luksAddKey",
   702  	})
   703  	// killing the encryption key
   704  	c.Assert(calls[1], DeepEquals, []string{
   705  		"cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "0",
   706  	})
   707  	// adding the new encryption key
   708  	c.Assert(calls[2], HasLen, 14)
   709  	c.Assert(calls[2][5], testutil.Contains, dirs.RunDir)
   710  	calls[2][5] = "<fifo>"
   711  	c.Assert(calls[2], DeepEquals, []string{
   712  		"cryptsetup", "luksAddKey", "--type", "luks2",
   713  		"--key-file", "<fifo>",
   714  		"--pbkdf", "argon2i",
   715  		"--iter-time", "100",
   716  		"--key-slot", "0",
   717  		"/dev/foobar", "-",
   718  	})
   719  }
   720  
   721  func (s *keymgrSuite) TestTransitionEncryptionKeyCannotAddKeyNotStaged(c *C) {
   722  	// conditions like when the encryption key has not been previously
   723  	// staged
   724  
   725  	restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) {
   726  		c.Errorf("unepected call")
   727  		return nil, fmt.Errorf("unexpected call")
   728  	})
   729  	defer restore()
   730  
   731  	cmd := testutil.MockCommand(c, "cryptsetup", `
   732  while [ "$#" -gt 1 ]; do
   733    case "$1" in
   734      luksAddKey)
   735        keyadd=1
   736        shift
   737        ;;
   738      --key-file)
   739        cat "$2" > /dev/null
   740        shift 2
   741        ;;
   742      *)
   743        shift 1
   744        ;;
   745    esac
   746  done
   747  if [ "$keyadd" = "1" ] ; then
   748    echo "No key available with this passphrase." >&2
   749    exit 1
   750  fi
   751  `)
   752  	defer cmd.Restore()
   753  
   754  	key := bytes.Repeat([]byte{1}, 32)
   755  	err := keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar")
   756  	c.Assert(err, ErrorMatches, "cannot add new encryption key: cryptsetup failed with: No key available with this passphrase.")
   757  	calls := cmd.Calls()
   758  	c.Assert(calls, HasLen, 1)
   759  	// probing the key slot use
   760  	c.Assert(calls[0], HasLen, 14)
   761  	c.Assert(calls[0][5], testutil.Contains, dirs.RunDir)
   762  	calls[0][5] = "<fifo>"
   763  	// temporary key
   764  	c.Assert(calls[0], DeepEquals, []string{
   765  		"cryptsetup", "luksAddKey", "--type", "luks2",
   766  		"--key-file", "<fifo>",
   767  		"--pbkdf", "argon2i",
   768  		"--iter-time", "100",
   769  		"--key-slot", "2",
   770  		"/dev/foobar", "-",
   771  	})
   772  }
   773  
   774  func (s *keymgrSuite) TestTransitionEncryptionKeyPostRebootHappy(c *C) {
   775  	restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) {
   776  		c.Errorf("unepected call")
   777  		return nil, fmt.Errorf("unexpected call")
   778  	})
   779  	defer restore()
   780  
   781  	cmd := testutil.MockCommand(c, "cryptsetup", `
   782  while [ "$#" -gt 1 ]; do
   783    case "$1" in
   784      --key-file)
   785        cat "$2" > /dev/null
   786        shift 2
   787        ;;
   788      *)
   789        shift
   790        ;;
   791    esac
   792  done
   793  `)
   794  	defer cmd.Restore()
   795  
   796  	key := bytes.Repeat([]byte{1}, 32)
   797  	err := keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar")
   798  	c.Assert(err, IsNil)
   799  	calls := cmd.Calls()
   800  	c.Assert(calls, HasLen, 2)
   801  	c.Assert(calls[0], HasLen, 14)
   802  	c.Assert(calls[0][5], testutil.Contains, dirs.RunDir)
   803  	calls[0][5] = "<fifo>"
   804  	// adding to a temporary key slot is successful, indicating a previously
   805  	// successful transition
   806  	c.Assert(calls[0], DeepEquals, []string{
   807  		"cryptsetup", "luksAddKey", "--type", "luks2",
   808  		"--key-file", "<fifo>",
   809  		"--pbkdf", "argon2i",
   810  		"--iter-time", "100",
   811  		"--key-slot", "2",
   812  		"/dev/foobar", "-",
   813  	})
   814  	// an a cleanup of the temp key slot
   815  	c.Assert(calls[1], DeepEquals, []string{
   816  		"cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "2",
   817  	})
   818  }
   819  
   820  func (s *keymgrSuite) TestTransitionEncryptionKeyPostRebootCannotKillSlot(c *C) {
   821  	// a post reboot scenario in which the luksKillSlot fails unexpectedly
   822  
   823  	restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) {
   824  		c.Errorf("unepected call")
   825  		return nil, fmt.Errorf("unexpected call")
   826  	})
   827  	defer restore()
   828  
   829  	cmd := testutil.MockCommand(c, "cryptsetup", `
   830  while [ "$#" -gt 1 ]; do
   831    case "$1" in
   832      luksKillSlot)
   833        killslot=1
   834        shift
   835        ;;
   836      --key-file)
   837        cat "$2" > /dev/null
   838        shift 2
   839        ;;
   840      *)
   841        shift
   842        ;;
   843    esac
   844  done
   845  if [ "$killslot" = "1" ]; then
   846    echo "mock error" >&2
   847    exit 5
   848  fi
   849  `)
   850  	defer cmd.Restore()
   851  
   852  	key := bytes.Repeat([]byte{1}, 32)
   853  	err := keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar")
   854  	c.Assert(err, ErrorMatches, "cannot kill temporary key slot: cryptsetup failed with: mock error")
   855  	calls := cmd.Calls()
   856  	c.Assert(calls, HasLen, 2)
   857  	c.Assert(calls[0], HasLen, 14)
   858  	c.Assert(calls[0][:2], DeepEquals, []string{
   859  		"cryptsetup", "luksAddKey",
   860  	})
   861  	// an a cleanup of the temp key slot
   862  	c.Assert(calls[1], DeepEquals, []string{
   863  		"cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "2",
   864  	})
   865  }
   866  
   867  func (s *keymgrSuite) TestRecoveryKDF(c *C) {
   868  	mockedMeminfoFile := filepath.Join(c.MkDir(), "meminfo")
   869  	s.AddCleanup(osutil.MockProcMeminfo(mockedMeminfoFile))
   870  
   871  	_, err := keymgr.RecoveryKDF()
   872  	c.Assert(err, ErrorMatches, "cannot get usable memory for KDF parameters when adding the recovery key: open .*")
   873  
   874  	c.Assert(ioutil.WriteFile(mockedMeminfoFile, []byte(mockedMeminfo), 0644), IsNil)
   875  
   876  	opts, err := keymgr.RecoveryKDF()
   877  	c.Assert(err, IsNil)
   878  	c.Assert(opts, DeepEquals, &luks2.KDFOptions{
   879  		MemoryKiB:       202834,
   880  		ForceIterations: 4,
   881  	})
   882  
   883  	const lotsOfMem = `MemTotal:         2097152 kB
   884  CmaTotal:         131072 kB
   885  `
   886  	c.Assert(ioutil.WriteFile(mockedMeminfoFile, []byte(lotsOfMem), 0644), IsNil)
   887  	opts, err = keymgr.RecoveryKDF()
   888  	c.Assert(err, IsNil)
   889  	c.Assert(opts, DeepEquals, &luks2.KDFOptions{
   890  		MemoryKiB:       786432,
   891  		ForceIterations: 4,
   892  	})
   893  
   894  	const littleMem = `MemTotal:         262144 kB
   895  CmaTotal:         131072 kB
   896  `
   897  	c.Assert(ioutil.WriteFile(mockedMeminfoFile, []byte(littleMem), 0644), IsNil)
   898  	opts, err = keymgr.RecoveryKDF()
   899  	c.Assert(err, IsNil)
   900  	c.Assert(opts, DeepEquals, &luks2.KDFOptions{
   901  		MemoryKiB:       32,
   902  		ForceIterations: 4,
   903  	})
   904  }