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

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  //go:build !nosecboot
     3  // +build !nosecboot
     4  
     5  /*
     6   * Copyright (C) 2022 Canonical Ltd
     7   *
     8   * This program is free software: you can redistribute it and/or modify
     9   * it under the terms of the GNU General Public License version 3 as
    10   * published by the Free Software Foundation.
    11   *
    12   * This program is distributed in the hope that it will be useful,
    13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    15   * GNU General Public License for more details.
    16   *
    17   * You should have received a copy of the GNU General Public License
    18   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    19   *
    20   */
    21  
    22  package secboot_test
    23  
    24  import (
    25  	"bytes"
    26  	"encoding/json"
    27  	"errors"
    28  	"fmt"
    29  	"path/filepath"
    30  
    31  	sb "gitee.com/mysnapcore/mysecboot"
    32  	. "gopkg.in/check.v1"
    33  
    34  	"gitee.com/mysnapcore/mysnapd/dirs"
    35  	"gitee.com/mysnapcore/mysnapd/osutil"
    36  	"gitee.com/mysnapcore/mysnapd/secboot"
    37  	"gitee.com/mysnapcore/mysnapd/secboot/keys"
    38  	"gitee.com/mysnapcore/mysnapd/snap/snaptest"
    39  	"gitee.com/mysnapcore/mysnapd/snapdtool"
    40  	"gitee.com/mysnapcore/mysnapd/testutil"
    41  )
    42  
    43  func (s *encryptSuite) TestFormatEncryptedDevice(c *C) {
    44  	for _, tc := range []struct {
    45  		initErr error
    46  		err     string
    47  	}{
    48  		{initErr: nil, err: ""},
    49  		{initErr: errors.New("some error"), err: "some error"},
    50  	} {
    51  		// create empty key to prevent blocking on lack of system entropy
    52  		myKey := keys.EncryptionKey{}
    53  		for i := range myKey {
    54  			myKey[i] = byte(i)
    55  		}
    56  
    57  		calls := 0
    58  		restore := secboot.MockSbInitializeLUKS2Container(func(devicePath, label string, key []byte,
    59  			opts *sb.InitializeLUKS2ContainerOptions) error {
    60  			calls++
    61  			c.Assert(devicePath, Equals, "/dev/node")
    62  			c.Assert(label, Equals, "my label")
    63  			c.Assert(key, DeepEquals, []byte(myKey))
    64  			c.Assert(opts, DeepEquals, &sb.InitializeLUKS2ContainerOptions{
    65  				MetadataKiBSize:     2048,
    66  				KeyslotsAreaKiBSize: 2560,
    67  				KDFOptions: &sb.KDFOptions{
    68  					MemoryKiB:       32,
    69  					ForceIterations: 4,
    70  				},
    71  			})
    72  			return tc.initErr
    73  		})
    74  		defer restore()
    75  
    76  		err := secboot.FormatEncryptedDevice(myKey, "my label", "/dev/node")
    77  		c.Assert(calls, Equals, 1)
    78  		if tc.err == "" {
    79  			c.Assert(err, IsNil)
    80  		} else {
    81  			c.Assert(err, ErrorMatches, tc.err)
    82  		}
    83  	}
    84  }
    85  
    86  type keymgrSuite struct {
    87  	testutil.BaseTest
    88  
    89  	d             string
    90  	keymgrCmd     *testutil.MockCmd
    91  	udevadmCmd    *testutil.MockCmd
    92  	systemdRunCmd *testutil.MockCmd
    93  }
    94  
    95  var _ = Suite(&keymgrSuite{})
    96  
    97  func (s *keymgrSuite) SetUpTest(c *C) {
    98  	s.BaseTest.SetUpTest(c)
    99  
   100  	s.d = c.MkDir()
   101  	s.systemdRunCmd = testutil.MockCommand(c, "systemd-run", `
   102  while true; do
   103      case "$1" in
   104          --*)
   105              shift
   106              ;;
   107          *)
   108              exec "$@"
   109              ;;
   110      esac
   111  done
   112  `)
   113  	s.AddCleanup(s.systemdRunCmd.Restore)
   114  	s.keymgrCmd = testutil.MockCommand(c, "snap-fde-keymgr", fmt.Sprintf(`
   115  set -e
   116  if [ "$1" = "change-encryption-key" ]; then
   117      cat > %s/input
   118      exit 0
   119  fi
   120  if [ "$1" = "add-recovery-key" ]; then
   121      while true; do
   122          case "$1" in
   123              --key-file)
   124                  shift
   125                  printf "recovery11111111" > "$1"
   126                  exit 0
   127                  ;;
   128              *) shift ;;
   129          esac
   130      done
   131  fi
   132  if [ "$1" = "remove-recovery-key" ]; then
   133      while [ "$#" -gt 1 ]; do
   134          case "$1" in
   135              --key-file)
   136                  shift
   137                  rm -f "$1"
   138                  ;;
   139              *) shift ;;
   140          esac
   141      done
   142  fi
   143  
   144  `, s.d))
   145  	s.AddCleanup(s.keymgrCmd.Restore)
   146  
   147  	s.udevadmCmd = testutil.MockCommand(c, "udevadm", `
   148  	echo "ID_PART_ENTRY_UUID=something"
   149  `)
   150  	s.AddCleanup(s.udevadmCmd.Restore)
   151  
   152  	restore := snapdtool.MockOsReadlink(func(string) (string, error) {
   153  		return filepath.Join(filepath.Dir(s.keymgrCmd.Exe()), "snapd"), nil
   154  	})
   155  	s.AddCleanup(restore)
   156  }
   157  
   158  var (
   159  	key = keys.EncryptionKey{'e', 'n', 'c', 'r', 'y', 'p', 't', 1, 1, 1, 1}
   160  )
   161  
   162  func (s *keymgrSuite) TestStageEncryptionKeyHappy(c *C) {
   163  	err := secboot.StageEncryptionKeyChange("/dev/foo/bar", key)
   164  	c.Assert(err, IsNil)
   165  	c.Check(s.udevadmCmd.Calls(), DeepEquals, [][]string{
   166  		{"udevadm", "info", "--query", "property", "--name", "/dev/foo/bar"},
   167  	})
   168  	c.Check(s.systemdRunCmd.Calls(), DeepEquals, [][]string{
   169  		{
   170  			"systemd-run",
   171  			"--wait", "--pipe", "--collect", "--service-type=exec", "--quiet",
   172  			"--property=KeyringMode=inherit", "--",
   173  			s.keymgrCmd.Exe(), "change-encryption-key", "--device", "/dev/disk/by-partuuid/something",
   174  			"--stage",
   175  		},
   176  	})
   177  	c.Check(s.keymgrCmd.Calls(), DeepEquals, [][]string{
   178  		{"snap-fde-keymgr", "change-encryption-key", "--device", "/dev/disk/by-partuuid/something", "--stage"},
   179  	})
   180  	var b bytes.Buffer
   181  	json.NewEncoder(&b).Encode(struct {
   182  		Key []byte `json:"key"`
   183  	}{
   184  		Key: key,
   185  	})
   186  	c.Check(filepath.Join(s.d, "input"), testutil.FileEquals, b.String())
   187  }
   188  
   189  func (s *keymgrSuite) TestStageEncryptionKeyBadUdev(c *C) {
   190  	udevadmCmd := testutil.MockCommand(c, "udevadm", `
   191  	echo "unhappy udev"
   192  `)
   193  	defer udevadmCmd.Restore()
   194  	err := secboot.StageEncryptionKeyChange("/dev/foo/bar", key)
   195  	c.Assert(err, ErrorMatches, "cannot get UUID of partition /dev/foo/bar: cannot get required udev partition UUID property")
   196  	c.Check(udevadmCmd.Calls(), DeepEquals, [][]string{
   197  		{"udevadm", "info", "--query", "property", "--name", "/dev/foo/bar"},
   198  	})
   199  	c.Check(s.systemdRunCmd.Calls(), HasLen, 0)
   200  	c.Check(s.keymgrCmd.Calls(), HasLen, 0)
   201  }
   202  
   203  func (s *keymgrSuite) TestStageTransitionEncryptionKeyBadKeymgr(c *C) {
   204  	keymgrCmd := testutil.MockCommand(c, "snap-fde-keymgr", `echo keymgr very unhappy; exit 1`)
   205  	defer keymgrCmd.Restore()
   206  	// update where /proc/self/exe resolves to
   207  	restore := snapdtool.MockOsReadlink(func(string) (string, error) {
   208  		return filepath.Join(filepath.Dir(keymgrCmd.Exe()), "snapd"), nil
   209  	})
   210  	defer restore()
   211  
   212  	err := secboot.StageEncryptionKeyChange("/dev/foo/bar", key)
   213  	c.Assert(err, ErrorMatches, "cannot run FDE key manager tool: cannot run .*: keymgr very unhappy")
   214  
   215  	c.Check(s.systemdRunCmd.Calls(), DeepEquals, [][]string{
   216  		{
   217  			"systemd-run",
   218  			"--wait", "--pipe", "--collect", "--service-type=exec", "--quiet",
   219  			"--property=KeyringMode=inherit", "--",
   220  			keymgrCmd.Exe(), "change-encryption-key", "--device", "/dev/disk/by-partuuid/something",
   221  			"--stage",
   222  		},
   223  	})
   224  	c.Check(keymgrCmd.Calls(), DeepEquals, [][]string{
   225  		{"snap-fde-keymgr", "change-encryption-key", "--device", "/dev/disk/by-partuuid/something", "--stage"},
   226  	})
   227  
   228  	s.systemdRunCmd.ForgetCalls()
   229  	keymgrCmd.ForgetCalls()
   230  
   231  	s.mocksForDeviceMounts(c)
   232  	err = secboot.TransitionEncryptionKeyChange("/foo", key)
   233  	c.Assert(err, ErrorMatches, "cannot run FDE key manager tool: cannot run .*: keymgr very unhappy")
   234  
   235  	c.Check(s.systemdRunCmd.Calls(), DeepEquals, [][]string{
   236  		{
   237  			"systemd-run",
   238  			"--wait", "--pipe", "--collect", "--service-type=exec", "--quiet",
   239  			"--property=KeyringMode=inherit", "--",
   240  			keymgrCmd.Exe(), "change-encryption-key", "--device", "/dev/disk/by-partuuid/foo-uuid",
   241  			"--transition",
   242  		},
   243  	})
   244  	c.Check(keymgrCmd.Calls(), DeepEquals, [][]string{
   245  		{"snap-fde-keymgr", "change-encryption-key", "--device", "/dev/disk/by-partuuid/foo-uuid", "--transition"},
   246  	})
   247  }
   248  
   249  func (s *keymgrSuite) TestTransitionEncryptionKeyNoMountDev(c *C) {
   250  	restore := osutil.MockMountInfo(`
   251  27 27 600:3 / /foo rw,relatime shared:7 - vfat /dev/mapper/foo rw
   252  `[1:])
   253  	s.AddCleanup(restore)
   254  
   255  	udevadmCmd := testutil.MockCommand(c, "udevadm", `echo nope; exit 1`)
   256  	defer udevadmCmd.Restore()
   257  
   258  	err := secboot.TransitionEncryptionKeyChange("/foo", key)
   259  	c.Assert(err, ErrorMatches, "cannot find matching device: cannot partition for mount /foo: cannot process udev properties of /dev/mapper/foo: nope")
   260  }
   261  
   262  func (s *keymgrSuite) TestTransitionEncryptionKeyHappy(c *C) {
   263  	udevadmCmd := s.mocksForDeviceMounts(c)
   264  
   265  	err := secboot.TransitionEncryptionKeyChange("/foo", key)
   266  	c.Assert(err, IsNil)
   267  	c.Check(udevadmCmd.Calls(), DeepEquals, [][]string{
   268  		{"udevadm", "info", "--query", "property", "--name", "/dev/mapper/foo"},
   269  		{"udevadm", "info", "--query", "property", "--name", "/dev/disk/by-uuid/5a522809-c87e-4dfa-81a8-8dc5667d1304"},
   270  	})
   271  	c.Check(s.systemdRunCmd.Calls(), DeepEquals, [][]string{
   272  		{
   273  			"systemd-run",
   274  			"--wait", "--pipe", "--collect", "--service-type=exec", "--quiet",
   275  			"--property=KeyringMode=inherit", "--",
   276  			s.keymgrCmd.Exe(), "change-encryption-key", "--device", "/dev/disk/by-partuuid/foo-uuid",
   277  			"--transition",
   278  		},
   279  	})
   280  	c.Check(s.keymgrCmd.Calls(), DeepEquals, [][]string{
   281  		{"snap-fde-keymgr", "change-encryption-key", "--device", "/dev/disk/by-partuuid/foo-uuid", "--transition"},
   282  	})
   283  	var b bytes.Buffer
   284  	json.NewEncoder(&b).Encode(struct {
   285  		Key []byte `json:"key"`
   286  	}{
   287  		Key: key,
   288  	})
   289  	c.Check(filepath.Join(s.d, "input"), testutil.FileEquals, b.String())
   290  }
   291  
   292  func (s *keymgrSuite) mocksForDeviceMounts(c *C) (udevadmCmd *testutil.MockCmd) {
   293  	restore := osutil.MockMountInfo(`
   294  27 27 600:3 / /foo rw,relatime shared:7 - vfat /dev/mapper/foo rw
   295  27 27 600:4 / /bar rw,relatime shared:7 - vfat /dev/mapper/bar rw
   296  `[1:])
   297  	s.AddCleanup(restore)
   298  
   299  	udevadmCmd = testutil.MockCommand(c, "udevadm", `
   300  while [ "$#" -gt 1 ]; do
   301      case "$1" in
   302          --name)
   303              shift
   304              case "$1" in
   305                  /dev/mapper/foo)
   306                      echo "DEVTYPE=disk"
   307                      echo "MAJOR=600"
   308                      echo "MINOR=3"
   309                      ;;
   310                  /dev/mapper/bar)
   311                      echo "DEVTYPE=disk"
   312                      echo "MAJOR=600"
   313                      echo "MINOR=4"
   314                      ;;
   315                  /dev/disk/by-uuid/5a522809-c87e-4dfa-81a8-8dc5667d1304)
   316                      echo "ID_PART_ENTRY_UUID=foo-uuid"
   317                      ;;
   318                  /dev/disk/by-uuid/5a522809-c87e-4dfa-81a8-8dc5667d1305)
   319                      echo "ID_PART_ENTRY_UUID=bar-uuid"
   320                      ;;
   321              esac
   322              ;;
   323          *)
   324              shift
   325              ;;
   326      esac
   327  done
   328  `)
   329  	s.AddCleanup(udevadmCmd.Restore)
   330  
   331  	s.AddCleanup(func() { dirs.SetRootDir(dirs.GlobalRootDir) })
   332  	dirs.SetRootDir(s.d)
   333  
   334  	snaptest.PopulateDir(s.d, [][]string{
   335  		{"/sys/dev/block/600:3/dm/uuid", "CRYPT-LUKS2-5a522809c87e4dfa81a88dc5667d1304-foo"},
   336  		{"/sys/dev/block/600:3/dm/name", "foo"},
   337  		{"/sys/dev/block/600:4/dm/uuid", "CRYPT-LUKS2-5a522809c87e4dfa81a88dc5667d1305-bar"},
   338  		{"/sys/dev/block/600:4/dm/name", "bar"},
   339  	})
   340  	return udevadmCmd
   341  }
   342  
   343  func (s *keymgrSuite) TestEnsureRecoveryKey(c *C) {
   344  	udevadmCmd := s.mocksForDeviceMounts(c)
   345  
   346  	rkey, err := secboot.EnsureRecoveryKey(filepath.Join(s.d, "recovery.key"), []secboot.RecoveryKeyDevice{
   347  		{Mountpoint: "/foo"},
   348  		{Mountpoint: "/bar", AuthorizingKeyFile: "/authz/key.file"},
   349  	})
   350  	c.Assert(err, IsNil)
   351  	c.Check(udevadmCmd.Calls(), DeepEquals, [][]string{
   352  		{"udevadm", "info", "--query", "property", "--name", "/dev/mapper/foo"},
   353  		{"udevadm", "info", "--query", "property", "--name", "/dev/disk/by-uuid/5a522809-c87e-4dfa-81a8-8dc5667d1304"},
   354  		{"udevadm", "info", "--query", "property", "--name", "/dev/mapper/bar"},
   355  		{"udevadm", "info", "--query", "property", "--name", "/dev/disk/by-uuid/5a522809-c87e-4dfa-81a8-8dc5667d1305"},
   356  	})
   357  	c.Check(s.systemdRunCmd.Calls(), DeepEquals, [][]string{
   358  		{
   359  			"systemd-run",
   360  			"--wait", "--pipe", "--collect", "--service-type=exec", "--quiet",
   361  			"--property=KeyringMode=inherit", "--",
   362  			s.keymgrCmd.Exe(), "add-recovery-key",
   363  			"--key-file", filepath.Join(s.d, "recovery.key"),
   364  			"--devices", "/dev/disk/by-partuuid/foo-uuid",
   365  			"--authorizations", "keyring",
   366  			"--devices", "/dev/disk/by-partuuid/bar-uuid",
   367  			"--authorizations", "file:/authz/key.file",
   368  		},
   369  	})
   370  	c.Check(s.keymgrCmd.Calls(), DeepEquals, [][]string{
   371  		{
   372  			"snap-fde-keymgr", "add-recovery-key",
   373  			"--key-file", filepath.Join(s.d, "recovery.key"),
   374  			"--devices", "/dev/disk/by-partuuid/foo-uuid", "--authorizations", "keyring",
   375  			"--devices", "/dev/disk/by-partuuid/bar-uuid", "--authorizations", "file:/authz/key.file",
   376  		},
   377  	})
   378  	c.Check(rkey, DeepEquals, keys.RecoveryKey{'r', 'e', 'c', 'o', 'v', 'e', 'r', 'y', '1', '1', '1', '1', '1', '1', '1', '1'})
   379  }
   380  
   381  func (s *keymgrSuite) TestRemoveRecoveryKey(c *C) {
   382  	udevadmCmd := s.mocksForDeviceMounts(c)
   383  
   384  	snaptest.PopulateDir(s.d, [][]string{
   385  		{"recovery.key", "foobar"},
   386  	})
   387  	// only one of the key files exists
   388  	err := secboot.RemoveRecoveryKeys(map[secboot.RecoveryKeyDevice]string{
   389  		{Mountpoint: "/foo"}: filepath.Join(s.d, "recovery.key"),
   390  		{Mountpoint: "/bar", AuthorizingKeyFile: "/authz/key.file"}: filepath.Join(s.d, "missing-recovery.key"),
   391  	})
   392  	c.Assert(err, IsNil)
   393  
   394  	expectedUdevCalls := [][]string{
   395  		// order can change depending on map iteration
   396  		{"udevadm", "info", "--query", "property", "--name", "/dev/mapper/foo"},
   397  		{"udevadm", "info", "--query", "property", "--name", "/dev/disk/by-uuid/5a522809-c87e-4dfa-81a8-8dc5667d1304"},
   398  		{"udevadm", "info", "--query", "property", "--name", "/dev/mapper/bar"},
   399  		{"udevadm", "info", "--query", "property", "--name", "/dev/disk/by-uuid/5a522809-c87e-4dfa-81a8-8dc5667d1305"},
   400  	}
   401  	expectedSystemdRunCalls := [][]string{
   402  		{
   403  			"systemd-run",
   404  			"--wait", "--pipe", "--collect", "--service-type=exec", "--quiet",
   405  			"--property=KeyringMode=inherit", "--",
   406  			s.keymgrCmd.Exe(), "remove-recovery-key",
   407  			// order can change depending on map iteration
   408  			"--devices", "/dev/disk/by-partuuid/foo-uuid", "--authorizations", "keyring",
   409  			"--key-files", filepath.Join(s.d, "recovery.key"),
   410  			"--devices", "/dev/disk/by-partuuid/bar-uuid", "--authorizations", "file:/authz/key.file",
   411  			"--key-files", filepath.Join(s.d, "missing-recovery.key"),
   412  		},
   413  	}
   414  	expectedKeymgrCalls := [][]string{
   415  		{
   416  			"snap-fde-keymgr", "remove-recovery-key",
   417  			// order can change depending on map iteration
   418  			"--devices", "/dev/disk/by-partuuid/foo-uuid", "--authorizations", "keyring",
   419  			"--key-files", filepath.Join(s.d, "recovery.key"),
   420  			"--devices", "/dev/disk/by-partuuid/bar-uuid", "--authorizations", "file:/authz/key.file",
   421  			"--key-files", filepath.Join(s.d, "missing-recovery.key"),
   422  		},
   423  	}
   424  
   425  	udevCalls := udevadmCmd.Calls()
   426  	c.Assert(udevCalls, HasLen, len(expectedUdevCalls))
   427  	// iteration order can be different though
   428  	c.Assert(udevCalls[0], HasLen, 6)
   429  	firstFoo := udevCalls[0][5] == "/dev/mapper/foo"
   430  
   431  	if !firstFoo {
   432  		// flip the order of foo and bar
   433  		expectedUdevCalls = append(expectedUdevCalls[2:], expectedUdevCalls[0:2]...)
   434  		expectedSystemdRunCalls[0] = append(expectedSystemdRunCalls[0][0:10],
   435  			append(expectedSystemdRunCalls[0][16:], expectedSystemdRunCalls[0][10:16]...)...)
   436  		expectedKeymgrCalls[0] = append(expectedKeymgrCalls[0][0:2],
   437  			append(expectedKeymgrCalls[0][8:], expectedKeymgrCalls[0][2:8]...)...)
   438  	}
   439  
   440  	c.Check(s.systemdRunCmd.Calls(), DeepEquals, expectedSystemdRunCalls)
   441  	c.Check(s.keymgrCmd.Calls(), DeepEquals, expectedKeymgrCalls)
   442  }