diff --git a/gadget/install/encrypt.go b/gadget/install/encrypt.go
index a1ac41ac79c..ff8c393dc7b 100644
--- a/gadget/install/encrypt.go
+++ b/gadget/install/encrypt.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+// +build !nosecboot
/*
* Copyright (C) 2020 Canonical Ltd
@@ -23,12 +24,8 @@ import (
"bytes"
"fmt"
"io/ioutil"
- "os"
"os/exec"
- "path/filepath"
- "syscall"
- "github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/gadget"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/secboot"
@@ -38,6 +35,11 @@ var (
tempFile = ioutil.TempFile
)
+var (
+ secbootFormatEncryptedDevice = secboot.FormatEncryptedDevice
+ secbootAddRecoveryKey = secboot.AddRecoveryKey
+)
+
// encryptedDevice represents a LUKS-backed encrypted block device.
type encryptedDevice struct {
parent *gadget.OnDiskStructure
@@ -57,7 +59,7 @@ func newEncryptedDevice(part *gadget.OnDiskStructure, key secboot.EncryptionKey,
Node: fmt.Sprintf("/dev/mapper/%s", name),
}
- if err := cryptsetupFormat(key, name+"-enc", part.Node); err != nil {
+ if err := secbootFormatEncryptedDevice(key, name+"-enc", part.Node); err != nil {
return nil, fmt.Errorf("cannot format encrypted device: %v", err)
}
@@ -69,47 +71,13 @@ func newEncryptedDevice(part *gadget.OnDiskStructure, key secboot.EncryptionKey,
}
func (dev *encryptedDevice) AddRecoveryKey(key secboot.EncryptionKey, rkey secboot.RecoveryKey) error {
- return cryptsetupAddKey(key, rkey, dev.parent.Node)
+ return secbootAddRecoveryKey(key, rkey, dev.parent.Node)
}
func (dev *encryptedDevice) Close() error {
return cryptsetupClose(dev.name)
}
-func cryptsetupFormat(key secboot.EncryptionKey, label, node string) error {
- // We use a keyfile with the same entropy as the derived key so we can
- // keep the KDF iteration count to a minimum. Longer processing will not
- // increase security in this case.
- args := []string{
- // batch processing, no password verification
- "-q",
- // formatting a new device
- "luksFormat",
- // use LUKS2
- "--type", "luks2",
- // read key from stdin
- "--key-file", "-",
- // use AES-256 with XTS block cipher mode (XTS requires 2 keys)
- "--cipher", "aes-xts-plain64", "--key-size", "512",
- // use --iter-time 1 with the default KDF argon2i so
- // to do virtually no derivation, here key is a random
- // key with good entropy, not a passphrase, so
- // spending time deriving from it is not necessary or
- // makes sense
- "--pbkdf", "argon2i", "--iter-time", "1",
- // set LUKS2 label
- "--label", label,
- // device to format
- node,
- }
- cmd := exec.Command("cryptsetup", args...)
- cmd.Stdin = bytes.NewReader(key[:])
- if output, err := cmd.CombinedOutput(); err != nil {
- return osutil.OutputErr(output, err)
- }
- return nil
-}
-
func cryptsetupOpen(key secboot.EncryptionKey, node, name string) error {
cmd := exec.Command("cryptsetup", "open", "--key-file", "-", node, name)
cmd.Stdin = bytes.NewReader(key[:])
@@ -125,66 +93,3 @@ func cryptsetupClose(name string) error {
}
return nil
}
-
-func cryptsetupAddKey(key secboot.EncryptionKey, rkey secboot.RecoveryKey, node string) error {
- // create a named pipe to pass the recovery key
- fpath := filepath.Join(dirs.SnapRunDir, "tmp-rkey")
- if err := os.MkdirAll(dirs.SnapRunDir, 0755); err != nil {
- return err
- }
- if err := syscall.Mkfifo(fpath, 0600); err != nil {
- return fmt.Errorf("cannot create named pipe: %v", err)
- }
- defer os.RemoveAll(fpath)
-
- // add a new key to slot 1 reading the passphrase from the named pipe
- // (explicitly choose keyslot 1 to ensure we have a predictable slot
- // number in case we decide to kill all other slots later)
- args := []string{
- // add a new key
- "luksAddKey",
- // the encrypted device
- node,
- // batch processing, no password verification
- "-q",
- // read existing key from stdin
- "--key-file", "-",
- // store it in keyslot 1
- "--key-slot", "1",
- // the named pipe
- fpath,
- }
-
- cmd := exec.Command("cryptsetup", args...)
- cmd.Stdin = bytes.NewReader(key[:])
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- if err := cmd.Start(); err != nil {
- return err
- }
-
- // open the named pipe and write the recovery key
- file, err := os.OpenFile(fpath, os.O_WRONLY, 0600)
- if err != nil {
- return fmt.Errorf("cannot open recovery key pipe: %v", err)
- }
- n, err := file.Write(rkey[:])
- if n != len(rkey) {
- file.Close()
- return fmt.Errorf("cannot write recovery key: short write (%d bytes written)", n)
- }
- if err != nil {
- cmd.Process.Kill()
- file.Close()
- return fmt.Errorf("cannot write recovery key: %v", err)
- }
- if err := file.Close(); err != nil {
- cmd.Process.Kill()
- return fmt.Errorf("cannot close recovery key pipe: %v", err)
- }
- if err := cmd.Wait(); err != nil {
- return fmt.Errorf("cannot add recovery key: %v", err)
- }
-
- return nil
-}
diff --git a/gadget/install/encrypt_test.go b/gadget/install/encrypt_test.go
index 476d14d35ce..efc5894bd52 100644
--- a/gadget/install/encrypt_test.go
+++ b/gadget/install/encrypt_test.go
@@ -20,9 +20,9 @@
package install_test
import (
+ "errors"
"fmt"
"os"
- "path/filepath"
. "gopkg.in/check.v1"
@@ -37,6 +37,9 @@ type encryptSuite struct {
testutil.BaseTest
mockCryptsetup *testutil.MockCmd
+
+ mockedEncryptionKey secboot.EncryptionKey
+ mockedRecoveryKey secboot.RecoveryKey
}
var _ = Suite(&encryptSuite{})
@@ -56,80 +59,119 @@ var mockDeviceStructure = gadget.OnDiskStructure{
func (s *encryptSuite) SetUpTest(c *C) {
dirs.SetRootDir(c.MkDir())
c.Assert(os.MkdirAll(dirs.SnapRunDir, 0755), IsNil)
-}
-
-func (s *encryptSuite) TestEncryptHappy(c *C) {
- s.mockCryptsetup = testutil.MockCommand(c, "cryptsetup", "")
- s.AddCleanup(s.mockCryptsetup.Restore)
// create empty key to prevent blocking on lack of system entropy
- key := secboot.EncryptionKey{}
- dev, err := install.NewEncryptedDevice(&mockDeviceStructure, key, "some-label")
- c.Assert(err, IsNil)
- c.Assert(dev.Node, Equals, "/dev/mapper/some-label")
-
- c.Assert(s.mockCryptsetup.Calls(), DeepEquals, [][]string{
- {
- "cryptsetup", "-q", "luksFormat", "--type", "luks2", "--key-file", "-",
- "--cipher", "aes-xts-plain64", "--key-size", "512", "--pbkdf", "argon2i",
- "--iter-time", "1", "--label", "some-label-enc", "/dev/node1",
- },
- {
- "cryptsetup", "open", "--key-file", "-", "/dev/node1", "some-label",
- },
- })
-
- err = dev.Close()
- c.Assert(err, IsNil)
+ s.mockedEncryptionKey = secboot.EncryptionKey{}
+ for i := range s.mockedEncryptionKey {
+ s.mockedEncryptionKey[i] = byte(i)
+ }
+ s.mockedRecoveryKey = secboot.RecoveryKey{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
}
-func (s *encryptSuite) TestEncryptFormatError(c *C) {
- s.mockCryptsetup = testutil.MockCommand(c, "cryptsetup", `[ "$2" == "luksFormat" ] && exit 127 || exit 0`)
- s.AddCleanup(s.mockCryptsetup.Restore)
-
- key := secboot.EncryptionKey{}
- _, err := install.NewEncryptedDevice(&mockDeviceStructure, key, "some-label")
- c.Assert(err, ErrorMatches, "cannot format encrypted device:.*")
-}
-
-func (s *encryptSuite) TestEncryptOpenError(c *C) {
- s.mockCryptsetup = testutil.MockCommand(c, "cryptsetup", `[ "$1" == "open" ] && exit 127 || exit 0`)
- s.AddCleanup(s.mockCryptsetup.Restore)
-
- key := secboot.EncryptionKey{}
- _, err := install.NewEncryptedDevice(&mockDeviceStructure, key, "some-label")
- c.Assert(err, ErrorMatches, "cannot open encrypted device on /dev/node1:.*")
-}
-
-func (s *encryptSuite) TestEncryptAddKey(c *C) {
- capturedFifo := filepath.Join(c.MkDir(), "captured-stdin")
- s.mockCryptsetup = testutil.MockCommand(c, "cryptsetup", fmt.Sprintf(`[ "$1" == "luksAddKey" ] && cat %s/tmp-rkey > %s || exit 0`, dirs.SnapRunDir, capturedFifo))
- s.AddCleanup(s.mockCryptsetup.Restore)
-
- key := secboot.EncryptionKey{}
- dev, err := install.NewEncryptedDevice(&mockDeviceStructure, key, "some-label")
- c.Assert(err, IsNil)
-
- rkey := secboot.RecoveryKey{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
- err = dev.AddRecoveryKey(key, rkey)
- c.Assert(err, IsNil)
-
- c.Assert(s.mockCryptsetup.Calls(), DeepEquals, [][]string{
+func (s *encryptSuite) TestNewEncryptedDevice(c *C) {
+ for _, tc := range []struct {
+ mockedFormatErr error
+ mockedOpenErr string
+ expectedErr string
+ }{
{
- "cryptsetup", "-q", "luksFormat", "--type", "luks2", "--key-file", "-",
- "--cipher", "aes-xts-plain64", "--key-size", "512", "--pbkdf", "argon2i",
- "--iter-time", "1", "--label", "some-label-enc", "/dev/node1",
+ mockedFormatErr: nil,
+ mockedOpenErr: "",
+ expectedErr: "",
},
{
- "cryptsetup", "open", "--key-file", "-", "/dev/node1", "some-label",
+ mockedFormatErr: errors.New("format error"),
+ mockedOpenErr: "",
+ expectedErr: "cannot format encrypted device: format error",
},
{
- "cryptsetup", "luksAddKey", "/dev/node1", "-q", "--key-file", "-",
- "--key-slot", "1", filepath.Join(dirs.SnapRunDir, "tmp-rkey"),
+ mockedFormatErr: nil,
+ mockedOpenErr: "open error",
+ expectedErr: "cannot open encrypted device on /dev/node1: open error",
},
- })
- c.Assert(capturedFifo, testutil.FileEquals, rkey[:])
+ } {
+ script := ""
+ if tc.mockedOpenErr != "" {
+ script = fmt.Sprintf("echo '%s'>&2; exit 1", tc.mockedOpenErr)
+
+ }
+ s.mockCryptsetup = testutil.MockCommand(c, "cryptsetup", script)
+ s.AddCleanup(s.mockCryptsetup.Restore)
+
+ calls := 0
+ restore := install.MockSecbootFormatEncryptedDevice(func(key secboot.EncryptionKey, label, node string) error {
+ calls++
+ c.Assert(key, DeepEquals, s.mockedEncryptionKey)
+ c.Assert(label, Equals, "some-label-enc")
+ c.Assert(node, Equals, "/dev/node1")
+ return tc.mockedFormatErr
+ })
+ defer restore()
+
+ dev, err := install.NewEncryptedDevice(&mockDeviceStructure, s.mockedEncryptionKey, "some-label")
+ c.Assert(calls, Equals, 1)
+ if tc.expectedErr == "" {
+ c.Assert(err, IsNil)
+ } else {
+ c.Assert(err, ErrorMatches, tc.expectedErr)
+ continue
+ }
+ c.Assert(dev.Node, Equals, "/dev/mapper/some-label")
+
+ err = dev.Close()
+ c.Assert(err, IsNil)
+
+ c.Assert(s.mockCryptsetup.Calls(), DeepEquals, [][]string{
+ {"cryptsetup", "open", "--key-file", "-", "/dev/node1", "some-label"},
+ {"cryptsetup", "close", "some-label"},
+ })
+ }
+}
- err = dev.Close()
- c.Assert(err, IsNil)
+func (s *encryptSuite) TestAddRecoveryKey(c *C) {
+ for _, tc := range []struct {
+ mockedAddErr error
+ expectedErr string
+ }{
+ {mockedAddErr: nil, expectedErr: ""},
+ {mockedAddErr: errors.New("add key error"), expectedErr: "add key error"},
+ } {
+ s.mockCryptsetup = testutil.MockCommand(c, "cryptsetup", "")
+ s.AddCleanup(s.mockCryptsetup.Restore)
+
+ restore := install.MockSecbootFormatEncryptedDevice(func(key secboot.EncryptionKey, label, node string) error {
+ return nil
+ })
+ defer restore()
+
+ calls := 0
+ restore = install.MockSecbootAddRecoveryKey(func(key secboot.EncryptionKey, rkey secboot.RecoveryKey, node string) error {
+ calls++
+ c.Assert(key, DeepEquals, s.mockedEncryptionKey)
+ c.Assert(rkey, DeepEquals, s.mockedRecoveryKey)
+ c.Assert(node, Equals, "/dev/node1")
+ return tc.mockedAddErr
+ })
+ defer restore()
+
+ dev, err := install.NewEncryptedDevice(&mockDeviceStructure, s.mockedEncryptionKey, "some-label")
+ c.Assert(err, IsNil)
+
+ err = dev.AddRecoveryKey(s.mockedEncryptionKey, s.mockedRecoveryKey)
+ c.Assert(calls, Equals, 1)
+ if tc.expectedErr == "" {
+ c.Assert(err, IsNil)
+ } else {
+ c.Assert(err, ErrorMatches, tc.expectedErr)
+ continue
+ }
+
+ err = dev.Close()
+ c.Assert(err, IsNil)
+
+ c.Assert(s.mockCryptsetup.Calls(), DeepEquals, [][]string{
+ {"cryptsetup", "open", "--key-file", "-", "/dev/node1", "some-label"},
+ {"cryptsetup", "close", "some-label"},
+ })
+ }
}
diff --git a/gadget/install/export_test.go b/gadget/install/export_test.go
index 60888e2f4ee..40ad14dd59c 100644
--- a/gadget/install/export_test.go
+++ b/gadget/install/export_test.go
@@ -23,6 +23,7 @@ import (
"time"
"github.com/snapcore/snapd/gadget"
+ "github.com/snapcore/snapd/secboot"
)
var (
@@ -70,3 +71,19 @@ func MockEnsureNodesExist(f func(dss []gadget.OnDiskStructure, timeout time.Dura
ensureNodesExist = old
}
}
+
+func MockSecbootFormatEncryptedDevice(f func(key secboot.EncryptionKey, label, node string) error) (restore func()) {
+ old := secbootFormatEncryptedDevice
+ secbootFormatEncryptedDevice = f
+ return func() {
+ secbootFormatEncryptedDevice = old
+ }
+}
+
+func MockSecbootAddRecoveryKey(f func(key secboot.EncryptionKey, rkey secboot.RecoveryKey, node string) error) (restore func()) {
+ old := secbootAddRecoveryKey
+ secbootAddRecoveryKey = f
+ return func() {
+ secbootAddRecoveryKey = old
+ }
+}
diff --git a/secboot/encrypt_test.go b/secboot/encrypt_test.go
index 373ad8abcd5..14860c4e78c 100644
--- a/secboot/encrypt_test.go
+++ b/secboot/encrypt_test.go
@@ -28,11 +28,11 @@ import (
"github.com/snapcore/snapd/secboot"
)
-type encryptionKeyTestSuite struct{}
+type encryptSuite struct{}
-var _ = Suite(&encryptionKeyTestSuite{})
+var _ = Suite(&encryptSuite{})
-func (s *encryptionKeyTestSuite) TestRecoveryKeySave(c *C) {
+func (s *encryptSuite) TestRecoveryKeySave(c *C) {
rkey := secboot.RecoveryKey{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 255}
err := rkey.Save("test-key")
c.Assert(err, IsNil)
diff --git a/secboot/encrypt_tpm.go b/secboot/encrypt_tpm.go
new file mode 100644
index 00000000000..a1e918e88d5
--- /dev/null
+++ b/secboot/encrypt_tpm.go
@@ -0,0 +1,40 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+// +build !nosecboot
+
+/*
+ * Copyright (C) 2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package secboot
+
+import (
+ sb "github.com/snapcore/secboot"
+)
+
+var (
+ sbInitializeLUKS2Container = sb.InitializeLUKS2Container
+ sbAddRecoveryKeyToLUKS2Container = sb.AddRecoveryKeyToLUKS2Container
+)
+
+// FormatEncryptedDevice
+func FormatEncryptedDevice(key EncryptionKey, label, node string) error {
+ return sbInitializeLUKS2Container(node, label, key[:])
+}
+
+// AddRecoveryKey
+func AddRecoveryKey(key EncryptionKey, rkey RecoveryKey, node string) error {
+ return sbAddRecoveryKeyToLUKS2Container(node, key[:], rkey)
+}
diff --git a/secboot/encrypt_tpm_test.go b/secboot/encrypt_tpm_test.go
new file mode 100644
index 00000000000..12b984c34dd
--- /dev/null
+++ b/secboot/encrypt_tpm_test.go
@@ -0,0 +1,99 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+// +build !nosecboot
+
+/*
+ * Copyright (C) 2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package secboot_test
+
+import (
+ "errors"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/secboot"
+)
+
+func (s *encryptSuite) TestFormatEncryptedDevice(c *C) {
+ for _, tc := range []struct {
+ initErr error
+ err string
+ }{
+ {initErr: nil, err: ""},
+ {initErr: errors.New("some error"), err: "some error"},
+ } {
+ // create empty key to prevent blocking on lack of system entropy
+ myKey := secboot.EncryptionKey{}
+ for i := range myKey {
+ myKey[i] = byte(i)
+ }
+
+ calls := 0
+ restore := secboot.MockSbInitializeLUKS2Container(func(devicePath, label string, key []byte) error {
+ calls++
+ c.Assert(devicePath, Equals, "/dev/node")
+ c.Assert(label, Equals, "my label")
+ c.Assert(key, DeepEquals, myKey[:])
+ return tc.initErr
+ })
+ defer restore()
+
+ err := secboot.FormatEncryptedDevice(myKey, "my label", "/dev/node")
+ c.Assert(calls, Equals, 1)
+ if tc.err == "" {
+ c.Assert(err, IsNil)
+ } else {
+ c.Assert(err, ErrorMatches, tc.err)
+ }
+ }
+}
+
+func (s *encryptSuite) TestAddRecoveryKey(c *C) {
+ for _, tc := range []struct {
+ addErr error
+ err string
+ }{
+ {addErr: nil, err: ""},
+ {addErr: errors.New("some error"), err: "some error"},
+ } {
+ // create empty key to prevent blocking on lack of system entropy
+ myKey := secboot.EncryptionKey{}
+ for i := range myKey {
+ myKey[i] = byte(i)
+ }
+
+ myRecoveryKey := secboot.RecoveryKey{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
+
+ calls := 0
+ restore := secboot.MockSbAddRecoveryKeyToLUKS2Container(func(devicePath string, key []byte, recoveryKey [16]byte) error {
+ calls++
+ c.Assert(devicePath, Equals, "/dev/node")
+ c.Assert(recoveryKey[:], DeepEquals, myRecoveryKey[:])
+ c.Assert(key, DeepEquals, myKey[:])
+ return tc.addErr
+ })
+ defer restore()
+
+ err := secboot.AddRecoveryKey(myKey, myRecoveryKey, "/dev/node")
+ c.Assert(calls, Equals, 1)
+ if tc.err == "" {
+ c.Assert(err, IsNil)
+ } else {
+ c.Assert(err, ErrorMatches, tc.err)
+ }
+ }
+}
diff --git a/secboot/export_test.go b/secboot/export_test.go
index 3b480571ab3..0383430d25f 100644
--- a/secboot/export_test.go
+++ b/secboot/export_test.go
@@ -134,6 +134,22 @@ func MockRandomKernelUUID(f func() string) (restore func()) {
}
}
+func MockSbInitializeLUKS2Container(f func(devicePath, label string, key []byte) error) (restore func()) {
+ old := sbInitializeLUKS2Container
+ sbInitializeLUKS2Container = f
+ return func() {
+ sbInitializeLUKS2Container = old
+ }
+}
+
+func MockSbAddRecoveryKeyToLUKS2Container(f func(devicePath string, key []byte, recoveryKey [16]byte) error) (restore func()) {
+ old := sbAddRecoveryKeyToLUKS2Container
+ sbAddRecoveryKeyToLUKS2Container = f
+ return func() {
+ sbAddRecoveryKeyToLUKS2Container = old
+ }
+}
+
func MockIsTPMEnabled(f func(tpm *sb.TPMConnection) bool) (restore func()) {
old := isTPMEnabled
isTPMEnabled = f
diff --git a/vendor/vendor.json b/vendor/vendor.json
index 320788eed66..16829624617 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -110,16 +110,16 @@
"revisionTime": "2017-09-28T14:21:59Z"
},
{
- "checksumSHA1": "UUnaKjQAEIclOm5Aqe2VmrMiQJY=",
+ "checksumSHA1": "fqejS2llZXw3gLnOYhg7pcSlY+Q=",
"path": "github.com/snapcore/secboot",
- "revision": "cfffb144f9df6f8aa65c4c4ee0786602a79d1b60",
- "revisionTime": "2020-07-02T10:51:30Z"
+ "revision": "79ab430e52a5c8284ff2822839c32736872643fe",
+ "revisionTime": "2020-07-07T19:41:53Z"
},
{
"checksumSHA1": "loFEiH6evGaDnDSlQgk3ugemkcU=",
"path": "github.com/snapcore/secboot/internal/pe1.14",
- "revision": "cfffb144f9df6f8aa65c4c4ee0786602a79d1b60",
- "revisionTime": "2020-07-02T10:51:30Z"
+ "revision": "79ab430e52a5c8284ff2822839c32736872643fe",
+ "revisionTime": "2020-07-07T19:41:53Z"
},
{
"checksumSHA1": "3AmEm18mKj8XxBuru/ix4OOpRkE=",