diff options
| author | Ian Johnson <ian.johnson@canonical.com> | 2022-03-11 11:21:00 -0600 |
|---|---|---|
| committer | Ian Johnson <ian.johnson@canonical.com> | 2022-03-11 11:21:00 -0600 |
| commit | ac539fa26903ad06a3c6060c6cb9a0805e7f497a (patch) | |
| tree | 0953e8032904896d1abecf5a411ada6b0e5f1ae9 /kernel | |
| parent | 314af6e91282fbecc86da3d0fb8ce78f49070c68 (diff) | |
| parent | c1255a113b76c59c1ac2eada93968e021c283082 (diff) | |
Merge branch 'master' into ice/fde-device-unlock
Signed-off-by: Ian Johnson <ian.johnson@canonical.com>
Diffstat (limited to 'kernel')
| -rw-r--r-- | kernel/fde/fde.go | 64 | ||||
| -rw-r--r-- | kernel/fde/fde_test.go | 70 | ||||
| -rw-r--r-- | kernel/fde/mapper.go | 52 |
3 files changed, 184 insertions, 2 deletions
diff --git a/kernel/fde/fde.go b/kernel/fde/fde.go index d65bd873bb..de7b32438d 100644 --- a/kernel/fde/fde.go +++ b/kernel/fde/fde.go @@ -29,6 +29,8 @@ import ( "encoding/json" "fmt" "os/exec" + + "github.com/snapcore/snapd/osutil" ) // HasRevealKey return true if the current system has a "fde-reveal-key" @@ -80,16 +82,25 @@ func unmarshalInitialSetupResult(hookOutput []byte) (*InitialSetupResult, error) return &res, nil } +// TODO: unexport this because how the hook is driven is an implemenation +// detail. It creates quite a bit of churn unfortunately, see +// https://github.com/snapcore/snapd/compare/master...mvo5:ice/refactor-fde?expand=1 +// // SetupRequest carries the operation and parameters for the fde-setup hooks // made available to them via the snapctl fde-setup-request command. type SetupRequest struct { - // XXX: make "op" a type: "features", "initial-setup", "update" ? Op string `json:"op"` // This needs to be a []byte so that Go's standard library will base64 // encode it automatically for us - Key []byte `json:"key,omitempty"` + Key []byte `json:"key,omitempty"` + + // Only used when called with "initial-setup" KeyName string `json:"key-name,omitempty"` + + // The part of the device kernel path for a "setup-device" call. + // Only used when called with "device-setup" + Device string `json:"device,omitempty"` } // A RunSetupHookFunc implements running the fde-setup kernel hook. @@ -126,3 +137,52 @@ func InitialSetup(runSetupHook RunSetupHookFunc, params *InitialSetupParams) (*I } return res, nil } + +// CheckFeatures returns the features of fde-setup hook. +func CheckFeatures(runSetupHook RunSetupHookFunc) ([]string, error) { + req := &SetupRequest{ + Op: "features", + } + output, err := runSetupHook(req) + if err != nil { + return nil, err + } + var res struct { + Features []string `json:"features"` + Error string `json:"error"` + } + if err := json.Unmarshal(output, &res); err != nil { + return nil, fmt.Errorf("cannot parse hook output %q: %v", output, err) + } + if res.Features == nil && res.Error == "" { + return nil, fmt.Errorf(`cannot use hook: neither "features" nor "error" returned`) + } + if res.Error != "" { + return nil, fmt.Errorf("cannot use hook: it returned error: %v", res.Error) + } + return res.Features, nil +} + +// DeviceSetupParams contains the inputs for the fde-setup hook. +// The encryption key and the device (partition) are passed in. +type DeviceSetupParams struct { + Key []byte + Device string +} + +// DeviceSetup invokes the "device-setup" op running the fde-setup +// hook via runSetupHook. This can be used to e.g. initialize +// inline crypto hardware. +func DeviceSetup(runSetupHook RunSetupHookFunc, params *DeviceSetupParams) error { + req := &SetupRequest{ + Op: "device-setup", + Key: params.Key, + Device: params.Device, + } + hookOutput, err := runSetupHook(req) + if err != nil { + return fmt.Errorf("device setup failed with: %v", osutil.OutputErr(hookOutput, err)) + } + + return nil +} diff --git a/kernel/fde/fde_test.go b/kernel/fde/fde_test.go index 3b53743fa7..ca2819a5e9 100644 --- a/kernel/fde/fde_test.go +++ b/kernel/fde/fde_test.go @@ -528,6 +528,52 @@ func (s *fdeSuite) TestRevealErr(c *C) { c.Check(osutil.FileExists(filepath.Join(dirs.GlobalRootDir, "/run/fde-reveal-key")), Equals, false) } +func (s *fdeSuite) TestDeviceSetupHappy(c *C) { + mockKey := []byte{1, 2, 3, 4} + mockDevice := "/dev/sda2" + + runSetupHook := func(req *fde.SetupRequest) ([]byte, error) { + c.Check(req, DeepEquals, &fde.SetupRequest{ + Op: "device-setup", + Key: mockKey, + Device: mockDevice, + }) + // empty reply: no error + mockJSON := `{}` + return []byte(mockJSON), nil + } + + params := &fde.DeviceSetupParams{ + Key: mockKey, + Device: mockDevice, + } + err := fde.DeviceSetup(runSetupHook, params) + c.Assert(err, IsNil) +} + +func (s *fdeSuite) TestDeviceSetupError(c *C) { + mockKey := []byte{1, 2, 3, 4} + mockDevice := "/dev/sda2" + + runSetupHook := func(req *fde.SetupRequest) ([]byte, error) { + c.Check(req, DeepEquals, &fde.SetupRequest{ + Op: "device-setup", + Key: mockKey, + Device: mockDevice, + }) + // empty reply: no error + mockJSON := `something failed badly` + return []byte(mockJSON), fmt.Errorf("exit status 1") + } + + params := &fde.DeviceSetupParams{ + Key: mockKey, + Device: mockDevice, + } + err := fde.DeviceSetup(runSetupHook, params) + c.Check(err, ErrorMatches, "device setup failed with: something failed badly") +} + func (s *fdeSuite) TestDeviceUnlock(c *C) { checkSystemdRunOrSkip(c) @@ -607,3 +653,27 @@ func (s *fdeSuite) TestHasDeviceUnlock(c *C) { c.Check(fde.HasDeviceUnlock(), Equals, true) } + +func (s *fdeSuite) TestIsEncryptedDeviceMapperName(c *C) { + // matches + for _, t := range []string{ + "something-device-locked", + "foo23-device-locked", + "device-device-locked", + "WE-DON'T-CARE-WHAT-THE-PREFIX-IS-AND-YOU-CAN'T-MAKE-US-device-locked", + } { + c.Assert(fde.IsHardwareEncryptedDeviceMapperName(t), Equals, true) + } + + // doesn't match + for _, t := range []string{ + "", + "-device-locked", + "device-locked", + "-device-locked-foo", + "some-device", + "CRYPT-LUKS2-5a522809c87e4dfa81a88dc5667d1304-ubuntu-data-3776bab4-8bcc-46b7-9da2-6a84ce7f93b4", + } { + c.Assert(fde.IsHardwareEncryptedDeviceMapperName(t), Equals, false) + } +} diff --git a/kernel/fde/mapper.go b/kernel/fde/mapper.go new file mode 100644 index 0000000000..a1928a697b --- /dev/null +++ b/kernel/fde/mapper.go @@ -0,0 +1,52 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2021 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 <http://www.gnu.org/licenses/>. + * + */ + +package fde + +import ( + "path/filepath" + "strings" + + "github.com/snapcore/snapd/osutil/disks" +) + +// IsEncryptedDevice returns true when the provided device mapper name indicates +// that it is encrypted using FDE hooks. +func IsHardwareEncryptedDeviceMapperName(dmName string) bool { + // TODO: is there anything more we can use to limit the prefix of the + // dmName? + return dmName != "-device-locked" && strings.HasSuffix(dmName, "-device-locked") +} + +// DeviceUnlockKernelHookDeviceMapperBackResolver is a back resolver to be used +// with disks.RegisterDeviceMapperBackResolver for devices that implement full +// disk encryption via hardware devices with kernel snap hooks. +func DeviceUnlockKernelHookDeviceMapperBackResolver(dmUUID, dmName []byte) (dev string, ok bool) { + if !IsHardwareEncryptedDeviceMapperName(string(dmName)) { + return "", false + } + // this is a device encrypted using FDE hooks + + // the uuid of the mapper device is the same as the partuuid + return filepath.Join("/dev/disk/by-partuuid", string(dmUUID)), true +} + +func init() { + disks.RegisterDeviceMapperBackResolver("device-unlock-kernel-fde", DeviceUnlockKernelHookDeviceMapperBackResolver) +} |
