diff --git a/osutil/disks/disks_darwin.go b/osutil/disks/disks_darwin.go index afc5f81686f4..d1eafe2d71c2 100644 --- a/osutil/disks/disks_darwin.go +++ b/osutil/disks/disks_darwin.go @@ -74,3 +74,7 @@ func SectorSize(devname string) (uint64, error) { func filesystemTypeForPartition(devname string) (string, error) { return "", osutil.ErrDarwin } + +func Devlinks(node string) ([]string, error) { + return []string{}, osutil.ErrDarwin +} diff --git a/osutil/disks/disks_linux.go b/osutil/disks/disks_linux.go index 2d0ae4ad2013..9fe389c28ec2 100644 --- a/osutil/disks/disks_linux.go +++ b/osutil/disks/disks_linux.go @@ -1020,3 +1020,16 @@ func filesystemTypeForPartition(devname string) (string, error) { return props["ID_FS_TYPE"], nil } + +// Devlinks returns all the /dev symlinks for a node +func Devlinks(node string) ([]string, error) { + props, err := udevPropertiesForName(node) + if err != nil && props == nil { + return []string{}, fmt.Errorf("cannot process udev properties: %v", err) + } + devlinks := props["DEVLINKS"] + if devlinks == "" { + return []string{}, fmt.Errorf("cannot get required udev DEVLINKS property") + } + return strings.Split(devlinks," "), nil +} diff --git a/osutil/disks/disks_linux_test.go b/osutil/disks/disks_linux_test.go index 565808913606..97a50650147d 100644 --- a/osutil/disks/disks_linux_test.go +++ b/osutil/disks/disks_linux_test.go @@ -2020,3 +2020,33 @@ func (s *diskSuite) TestFilesystemUUID(c *C) { _, err = disks.FilesystemUUID("/dev/vda6") c.Check(err, ErrorMatches, `cannot process udev properties: some error`) } + +func (s *diskSuite) TestDevlinks(c *C) { + restore := disks.MockUdevPropertiesForDevice(func(typeOpt, dev string) (map[string]string, error) { + c.Assert(typeOpt, Equals, "--name") + switch dev { + case "/dev/some/main/link": + return map[string]string{ + "DEVLINKS": "/dev/some/other/link /dev/yet/another/link", + }, nil + case "/dev/no/links": + return map[string]string{}, nil + case "/dev/some/error": + return nil, fmt.Errorf("some error") + default: + c.Errorf("unexpected udev device properties requested: %s", dev) + return nil, fmt.Errorf("unexpected udev device: %s", dev) + } + }) + defer restore() + + links, err := disks.Devlinks("/dev/some/main/link") + c.Assert(err, IsNil) + c.Check(links, DeepEquals, []string{"/dev/some/other/link", "/dev/yet/another/link"}) + + _, err = disks.Devlinks("/dev/no/links") + c.Check(err, ErrorMatches, `cannot get required udev DEVLINKS property`) + + _, err = disks.Devlinks("/dev/some/error") + c.Check(err, ErrorMatches, `cannot process udev properties: some error`) +} diff --git a/secboot/export_sb_test.go b/secboot/export_sb_test.go index bf383000a9d3..f52744c0e3db 100644 --- a/secboot/export_sb_test.go +++ b/secboot/export_sb_test.go @@ -389,3 +389,19 @@ func MockSetProtectorKeys(f func(keys ...[]byte)) (restore func()) { sbSetProtectorKeys = old } } + +func MockSbGetPrimaryKeyFromKernel(f func(prefix string, devicePath string, remove bool) (sb.PrimaryKey, error)) (restore func()) { + old := sbGetPrimaryKeyFromKernel + sbGetPrimaryKeyFromKernel = f + return func() { + sbGetPrimaryKeyFromKernel = old + } +} + +func MockDisksDevlinks(f func(node string) ([]string, error)) (restore func()) { + old := disksDevlinks + disksDevlinks = f + return func() { + disksDevlinks = old + } +} diff --git a/secboot/secboot_sb.go b/secboot/secboot_sb.go index 09801c3480b7..0e2c41ddf17f 100644 --- a/secboot/secboot_sb.go +++ b/secboot/secboot_sb.go @@ -28,6 +28,7 @@ import ( "fmt" "os" "path/filepath" + "strings" sb "github.com/snapcore/secboot" sb_plainkey "github.com/snapcore/secboot/plainkey" @@ -52,6 +53,8 @@ var ( sbRenameLUKS2ContainerKey = sb.RenameLUKS2ContainerKey sbNewLUKS2KeyDataReader = sbNewLUKS2KeyDataReaderImpl sbSetProtectorKeys = sb_plainkey.SetProtectorKeys + sbGetPrimaryKeyFromKernel = sb.GetPrimaryKeyFromKernel + disksDevlinks = disks.Devlinks ) func init() { @@ -410,10 +413,41 @@ func DeleteKeys(node string, matches map[string]bool) error { return nil } -// FIXME: add tests -func GetPrimaryKeyDigest(devicePath string, alg crypto.Hash) (salt []byte, digest []byte, err error) { +func findPrimaryKey(devicePath string) ([]byte, error) { const remove = false - p, err := sb.GetPrimaryKeyFromKernel(keyringPrefix, devicePath, remove) + p, err := sbGetPrimaryKeyFromKernel(keyringPrefix, devicePath, remove) + if err == nil { + return p, nil + } + if !errors.Is(err, sb.ErrKernelKeyNotFound) { + return nil, err + } + + // Old kernels will use "by-partuuid" symlinks. So let's + // look at all the symlinks of the device. + devlinks, errDevlinks := disksDevlinks(devicePath) + if errDevlinks != nil { + return nil, err + } + var errDevlink error + for _, devlink := range devlinks { + if !strings.HasPrefix(devlink, "/dev/disk/by-partuuid/") { + continue + } + p, err = sbGetPrimaryKeyFromKernel(keyringPrefix, devlink, remove) + if errDevlink == nil { + return p, nil + } + } + return nil, err +} + +// GetPrimaryKeyDigest retrieve the primary key for a disk from the +// keyring and returns its digest. If the path given does not match +// the keyring, then it will look for symlink in /dev/disk/by-partuuid +// for that device. +func GetPrimaryKeyDigest(devicePath string, alg crypto.Hash) (salt []byte, digest []byte, err error) { + p, err := findPrimaryKey(devicePath) if err != nil { if errors.Is(err, sb.ErrKernelKeyNotFound) { return nil, nil, ErrKernelKeyNotFound @@ -431,10 +465,12 @@ func GetPrimaryKeyDigest(devicePath string, alg crypto.Hash) (salt []byte, diges return saltArray[:], h.Sum(nil), nil } -// FIXME: add tests +// VerifyPrimaryKeyDigest retrieve the primary key for a disk from the +// keyring and verifies its digest. If the path given does not match +// the keyring, then it will look for symlink in /dev/disk/by-partuuid +// for that device. func VerifyPrimaryKeyDigest(devicePath string, alg crypto.Hash, salt []byte, digest []byte) (bool, error) { - const remove = false - p, err := sb.GetPrimaryKeyFromKernel(keyringPrefix, devicePath, remove) + p, err := findPrimaryKey(devicePath) if err != nil { if errors.Is(err, sb.ErrKernelKeyNotFound) { return false, ErrKernelKeyNotFound diff --git a/secboot/secboot_sb_test.go b/secboot/secboot_sb_test.go index 58055d1e2d50..18b38517f232 100644 --- a/secboot/secboot_sb_test.go +++ b/secboot/secboot_sb_test.go @@ -22,6 +22,7 @@ package secboot_test import ( "bytes" + "crypto" "crypto/rand" "encoding/base64" "encoding/binary" @@ -3132,3 +3133,78 @@ func (s *secbootSuite) TestReadKeyFileFDEHookV1(c *C) { c.Check(keyLoader.SealedKeyObject, IsNil) c.Check(keyLoader.FDEHookKeyV1, DeepEquals, []byte(`USK$blahblah`)) } + +func (s *secbootSuite) TestGetPrimaryKeyDigest(c *C) { + defer secboot.MockDisksDevlinks(func(node string) ([]string, error) { + c.Errorf("unexpected call") + return nil, errors.New("unexpected call") + })() + defer secboot.MockSbGetPrimaryKeyFromKernel(func(prefix string, devicePath string, remove bool) (sb.PrimaryKey, error) { + c.Check(prefix, Equals, "ubuntu-fde") + c.Check(devicePath, Equals, "/dev/test/device") + c.Check(remove, Equals, false) + return []byte{0, 1, 2, 3}, nil + })() + salt, digest, err := secboot.GetPrimaryKeyDigest("/dev/test/device", crypto.SHA256) + c.Assert(err, IsNil) + defer secboot.MockSbGetPrimaryKeyFromKernel(func(prefix string, devicePath string, remove bool) (sb.PrimaryKey, error) { + c.Check(prefix, Equals, "ubuntu-fde") + c.Check(devicePath, Equals, "/dev/other/device") + c.Check(remove, Equals, false) + return []byte{0, 1, 2, 3}, nil + })() + matches, err := secboot.VerifyPrimaryKeyDigest("/dev/other/device", crypto.SHA256, salt, digest) + c.Assert(err, IsNil) + c.Check(matches, Equals, true) + defer secboot.MockSbGetPrimaryKeyFromKernel(func(prefix string, devicePath string, remove bool) (sb.PrimaryKey, error) { + c.Check(prefix, Equals, "ubuntu-fde") + c.Check(devicePath, Equals, "/dev/not/matching") + c.Check(remove, Equals, false) + return []byte{8, 8, 8, 8}, nil + })() + matches, err = secboot.VerifyPrimaryKeyDigest("/dev/not/matching", crypto.SHA256, salt, digest) + c.Assert(err, IsNil) + c.Check(matches, Equals, false) +} + +func (s *secbootSuite) TestGetPrimaryKeyDigestFallbackDevPath(c *C) { + defer secboot.MockDisksDevlinks(func(node string) ([]string, error) { + c.Check(node, Equals, "/dev/test/device") + return []string{ + "/dev/link/to/ignore", + "/dev/test/device", + "/dev/disk/by-partuuid/a9456fe6-9850-41ce-b2ad-cf9b43a34286", + }, nil + })() + defer secboot.MockSbGetPrimaryKeyFromKernel(func(prefix string, devicePath string, remove bool) (sb.PrimaryKey, error) { + c.Check(prefix, Equals, "ubuntu-fde") + c.Check(remove, Equals, false) + if devicePath == "/dev/test/device" { + return nil, sb.ErrKernelKeyNotFound + } + c.Check(devicePath, Equals, "/dev/disk/by-partuuid/a9456fe6-9850-41ce-b2ad-cf9b43a34286") + return []byte{0, 1, 2, 3}, nil + })() + salt, digest, err := secboot.GetPrimaryKeyDigest("/dev/test/device", crypto.SHA256) + c.Assert(err, IsNil) + defer secboot.MockDisksDevlinks(func(node string) ([]string, error) { + c.Check(node, Equals, "/dev/other/device") + return []string{ + "/dev/link/to/ignore", + "/dev/other/device", + "/dev/disk/by-partuuid/58c54e4e-1e86-4bda-a51c-af50ff8447ab", + }, nil + })() + defer secboot.MockSbGetPrimaryKeyFromKernel(func(prefix string, devicePath string, remove bool) (sb.PrimaryKey, error) { + c.Check(prefix, Equals, "ubuntu-fde") + c.Check(remove, Equals, false) + if devicePath == "/dev/other/device" { + return nil, sb.ErrKernelKeyNotFound + } + c.Check(devicePath, Equals, "/dev/disk/by-partuuid/58c54e4e-1e86-4bda-a51c-af50ff8447ab") + return []byte{0, 1, 2, 3}, nil + })() + matches, err := secboot.VerifyPrimaryKeyDigest("/dev/other/device", crypto.SHA256, salt, digest) + c.Assert(err, IsNil) + c.Check(matches, Equals, true) +}