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 authored and pedronis committed Feb 4, 2025
1 parent ca5418e commit 1c1b779
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 @@ -1035,3 +1035,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 @@ -2050,3 +2050,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 @@ -417,3 +417,19 @@ func MockTpmGetCapabilityHandles(f func(tpm *sb_tpm2.Connection, firstHandle tpm
tpmGetCapabilityHandles = 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
}

// 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, errDevlink = 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
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 @@ -3423,3 +3424,78 @@ func (s *secbootSuite) TestFindFreeHandleNoneFree(c *C) {
_, err := secboot.FindFreeHandle()
c.Assert(err, ErrorMatches, `no free handle on TPM`)
}

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 1c1b779

Please sign in to comment.