Skip to content

Commit

Permalink
secboot: check legacy path for keyring when reading the primary key
Browse files Browse the repository at this point in the history
In the case we update snapd with and old kernel, then reseal, the FDE
state might be not set because we did not find the primary key from
the initrd that set it in the legacy path. So if we do not find the
key in the keyring, we should try and see if we find it in the old
path.
  • Loading branch information
valentindavid committed Jan 23, 2025
1 parent 911bf5a commit 05b58eb
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 6 deletions.
4 changes: 4 additions & 0 deletions osutil/disks/disks_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
13 changes: 13 additions & 0 deletions osutil/disks/disks_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
30 changes: 30 additions & 0 deletions osutil/disks/disks_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
}
16 changes: 16 additions & 0 deletions secboot/export_sb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
48 changes: 42 additions & 6 deletions secboot/secboot_sb.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"

sb "github.com/snapcore/secboot"
sb_plainkey "github.com/snapcore/secboot/plainkey"
Expand All @@ -52,6 +53,8 @@ var (
sbRenameLUKS2ContainerKey = sb.RenameLUKS2ContainerKey
sbNewLUKS2KeyDataReader = sbNewLUKS2KeyDataReaderImpl
sbSetProtectorKeys = sb_plainkey.SetProtectorKeys
sbGetPrimaryKeyFromKernel = sb.GetPrimaryKeyFromKernel
disksDevlinks = disks.Devlinks
)

func init() {
Expand Down Expand Up @@ -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
}

Check warning on line 424 in secboot/secboot_sb.go

View check run for this annotation

Codecov / codecov/patch

secboot/secboot_sb.go#L423-L424

Added lines #L423 - L424 were not covered by tests

// 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
}

Check warning on line 431 in secboot/secboot_sb.go

View check run for this annotation

Codecov / codecov/patch

secboot/secboot_sb.go#L430-L431

Added lines #L430 - L431 were not covered by tests
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

Check warning on line 442 in secboot/secboot_sb.go

View check run for this annotation

Codecov / codecov/patch

secboot/secboot_sb.go#L442

Added line #L442 was not covered by tests
}

// GetPrimaryKeyDigest retrieve the primary key for a disk from the
// keyring and returns it 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
Expand All @@ -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
Expand Down
76 changes: 76 additions & 0 deletions secboot/secboot_sb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package secboot_test

import (
"bytes"
"crypto"
"crypto/rand"
"encoding/base64"
"encoding/binary"
Expand Down Expand Up @@ -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)
}

0 comments on commit 05b58eb

Please sign in to comment.