Skip to content

Commit

Permalink
many: add passphrase authentication support
Browse files Browse the repository at this point in the history
This is still not exposed through the API until target
system and entropy checks are added.

Signed-off-by: Zeyad Gouda <[email protected]>
  • Loading branch information
ZeyadYasser committed Jan 10, 2025
1 parent 5802840 commit 8b6b37c
Show file tree
Hide file tree
Showing 14 changed files with 216 additions and 32 deletions.
7 changes: 6 additions & 1 deletion gadget/install/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"time"

"github.com/snapcore/snapd/gadget"
"github.com/snapcore/snapd/gadget/device"
"github.com/snapcore/snapd/gadget/quantity"
"github.com/snapcore/snapd/kernel"
)
Expand Down Expand Up @@ -83,7 +84,7 @@ func MockKernelEnsureKernelDriversTree(f func(kMntPts kernel.MountPoints, compsM
}
}

func CheckEncryptionSetupData(encryptSetup *EncryptionSetupData, labelToEncDevice map[string]string) error {
func CheckEncryptionSetupData(encryptSetup *EncryptionSetupData, labelToEncDevice map[string]string, expectedVolumesAuth *device.VolumesAuthOptions) error {
for label, part := range encryptSetup.parts {
switch part.role {
case gadget.SystemData, gadget.SystemSave:
Expand All @@ -95,6 +96,10 @@ func CheckEncryptionSetupData(encryptSetup *EncryptionSetupData, labelToEncDevic
return fmt.Errorf("encrypted device in EncryptionSetupData (%q) different to expected (%q)",
encryptSetup.parts[label].encryptedDevice, labelToEncDevice[label])
}
if part.installKey.GetAuthOptions() != expectedVolumesAuth {
return fmt.Errorf("volume authentication for device %q in EncryptionSetupData (%v) different to expected (%v)",
labelToEncDevice[label], part.installKey.GetAuthOptions(), expectedVolumesAuth)
}
}

return nil
Expand Down
8 changes: 3 additions & 5 deletions gadget/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ func Run(model gadget.Model, gadgetRoot string, kernelSnapInfo *KernelSnapInfo,
if installKeyForRole == nil {
installKeyForRole = map[string]secboot.BootstrappedContainer{}
}
installKeyForRole[vs.Role] = secboot.CreateBootstrappedContainer(encryptionKey, diskPart.Node)
installKeyForRole[vs.Role] = secboot.CreateBootstrappedContainer(encryptionKey, diskPart.Node, nil)
partsEncrypted[vs.Name] = createEncryptionParams(options.EncryptionType)
}
if options.Mount && vs.Label != "" && vs.HasFilesystem() {
Expand Down Expand Up @@ -640,8 +640,6 @@ func EncryptPartitions(
encryptionType device.EncryptionType, model *asserts.Model,
gadgetRoot, kernelRoot string, perfTimings timings.Measurer,
) (*EncryptionSetupData, error) {
// TODO: Attach passed volumes auth options to encryption setup data.

setupData := &EncryptionSetupData{
parts: make(map[string]partEncryptionData),
}
Expand Down Expand Up @@ -681,7 +679,7 @@ func EncryptPartitions(
// EncryptedDevice will be /dev/mapper/ubuntu-data, etc.
encryptedDevice: fsParams.Device,
volName: volName,
installKey: secboot.CreateBootstrappedContainer(encryptionKey, device),
installKey: secboot.CreateBootstrappedContainer(encryptionKey, device, volumesAuth),
encryptedSectorSize: fsParams.SectorSize,
encryptionParams: createEncryptionParams(encryptionType),
}
Expand Down Expand Up @@ -802,7 +800,7 @@ func FactoryReset(model gadget.Model, gadgetRoot string, kernelSnapInfo *KernelS
if installKeyForRole == nil {
installKeyForRole = map[string]secboot.BootstrappedContainer{}
}
installKeyForRole[vs.Role] = secboot.CreateBootstrappedContainer(encryptionKey, onDiskStruct.Node)
installKeyForRole[vs.Role] = secboot.CreateBootstrappedContainer(encryptionKey, onDiskStruct.Node, nil)
}
if options.Mount && vs.Label != "" && vs.HasFilesystem() {
// fs is taken from gadget, as on disk one might be displayed as
Expand Down
12 changes: 10 additions & 2 deletions gadget/install/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1082,6 +1082,7 @@ func (s *installSuite) TestInstallWriteContentDeviceNotFound(c *C) {

type encryptPartitionsOpts struct {
encryptType device.EncryptionType
volumesAuth *device.VolumesAuthOptions
}

func (s *installSuite) testEncryptPartitions(c *C, opts encryptPartitionsOpts) {
Expand Down Expand Up @@ -1122,13 +1123,13 @@ func (s *installSuite) testEncryptPartitions(c *C, opts encryptPartitionsOpts) {
return nil
})()

encryptSetup, err := install.EncryptPartitions(ginfo.Volumes, nil, opts.encryptType, model, gadgetRoot, "", timings.New(nil))
encryptSetup, err := install.EncryptPartitions(ginfo.Volumes, opts.volumesAuth, opts.encryptType, model, gadgetRoot, "", timings.New(nil))
c.Assert(err, IsNil)
c.Assert(encryptSetup, NotNil)
err = install.CheckEncryptionSetupData(encryptSetup, map[string]string{
"ubuntu-save": "/dev/mapper/ubuntu-save",
"ubuntu-data": "/dev/mapper/ubuntu-data",
})
}, opts.volumesAuth)
c.Assert(err, IsNil)
}

Expand All @@ -1138,6 +1139,13 @@ func (s *installSuite) TestInstallEncryptPartitionsLUKSHappy(c *C) {
})
}

func (s *installSuite) TestInstallEncryptPartitions(c *C) {
s.testEncryptPartitions(c, encryptPartitionsOpts{
encryptType: device.EncryptionTypeLUKS,
volumesAuth: &device.VolumesAuthOptions{Mode: device.AuthModePassphrase, Passphrase: "test"},
})
}

func (s *installSuite) TestInstallEncryptPartitionsNoDeviceSet(c *C) {
vdaSysPath := "/sys/devices/pci0000:00/0000:00:03.0/virtio1/block/vda"
restore := gadget.MockSysfsPathForBlockDevice(func(device string) (string, error) {
Expand Down
2 changes: 2 additions & 0 deletions gadget/install/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ func (esd *EncryptionSetupData) EncryptedDevices() map[string]string {
type MockEncryptedDeviceAndRole struct {
Role string
EncryptedDevice string
VolumesAuth *device.VolumesAuthOptions
}

// MockEncryptionSetupData is meant to be used for unit tests from other
Expand All @@ -92,6 +93,7 @@ func MockEncryptionSetupData(labelToEncDevice map[string]*MockEncryptedDeviceAnd
//overlord/install/install.go. Once we have removed that call,
// we can use mock object instead.
bootstrapKey := secboot.CreateMockBootstrappedContainer()
bootstrapKey.AuthOptions = encryptData.VolumesAuth
esd.parts[label] = partEncryptionData{
role: encryptData.Role,
encryptedDevice: encryptData.EncryptedDevice,
Expand Down
22 changes: 20 additions & 2 deletions overlord/devicestate/devicestate_install_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ type finishStepOpts struct {
hasPartial bool
hasSystemSeed bool
optionalContainers *seed.OptionalContainers
volumesAuth *device.VolumesAuthOptions
}

func (s *deviceMgrInstallAPISuite) mockSystemSeedWithLabel(c *C, label string, isClassic, hasSystemSeed, hasPartial bool, seedCopyFn func(string, seed.CopyOptions, timings.Measurer) error) (gadgetSnapPath, kernelSnapPath string, ginfo *gadget.Info, mountCmd *testutil.MockCmd) {
Expand Down Expand Up @@ -638,12 +639,15 @@ func (s *deviceMgrInstallAPISuite) testInstallFinishStep(c *C, opts finishStepOp
// exact cmdline depends on arch, see
// bootloader/assets/grub.go:init()
c.Check(modeenv.CurrentKernelCommandLines[0], testutil.Contains, "snapd_recovery_mode=run")
// Check that volume authentication options where propagated
c.Check(key.GetAuthOptions(), Equals, opts.volumesAuth)
c.Check(saveKey.GetAuthOptions(), Equals, opts.volumesAuth)
return nil
})
s.AddCleanup(restore)

// Insert encryption set-up data in state cache
restore = devicestate.MockEncryptionSetupDataInCache(s.state, label)
restore = devicestate.MockEncryptionSetupDataInCache(s.state, label, opts.volumesAuth)
s.AddCleanup(restore)

// Write expected boot assets needed when creating bootchain
Expand All @@ -661,7 +665,7 @@ func (s *deviceMgrInstallAPISuite) testInstallFinishStep(c *C, opts finishStepOp
c.Assert(os.MkdirAll(bootDir, 0755), IsNil)
c.Assert(os.WriteFile(filepath.Join(bootDir, "grubx64.efi"), []byte{}, 0755), IsNil)

s.AddCleanup(secboot.MockCreateBootstrappedContainer(func(key secboot.DiskUnlockKey, devicePath string) secboot.BootstrappedContainer {
s.AddCleanup(secboot.MockCreateBootstrappedContainer(func(key secboot.DiskUnlockKey, devicePath string, volumesAuth *device.VolumesAuthOptions) secboot.BootstrappedContainer {
return secboot.CreateMockBootstrappedContainer()
}))
}
Expand Down Expand Up @@ -758,6 +762,11 @@ func (s *deviceMgrInstallAPISuite) TestInstallClassicFinishEncryptionHappy(c *C)
s.testInstallFinishStep(c, finishStepOpts{encrypted: true, installClassic: true})
}

func (s *deviceMgrInstallAPISuite) TestInstallClassicFinishEncryptionWithPassphraseAuthHappy(c *C) {
volumesAuth := &device.VolumesAuthOptions{Mode: device.AuthModePassphrase, Passphrase: "test"}
s.testInstallFinishStep(c, finishStepOpts{encrypted: true, installClassic: true, volumesAuth: volumesAuth})
}

func (s *deviceMgrInstallAPISuite) TestInstallClassicFinishEncryptionAndSystemSeedHappy(c *C) {
s.testInstallFinishStep(c, finishStepOpts{
encrypted: true,
Expand All @@ -778,6 +787,11 @@ func (s *deviceMgrInstallAPISuite) TestInstallCoreFinishEncryptionHappy(c *C) {
s.testInstallFinishStep(c, finishStepOpts{encrypted: true, installClassic: false})
}

func (s *deviceMgrInstallAPISuite) TestInstallCoreFinishEncryptionWithPassphraseAuthHappy(c *C) {
volumesAuth := &device.VolumesAuthOptions{Mode: device.AuthModePassphrase, Passphrase: "test"}
s.testInstallFinishStep(c, finishStepOpts{encrypted: true, installClassic: false, volumesAuth: volumesAuth})
}

func (s *deviceMgrInstallAPISuite) TestInstallCoreFinishWithOptionalContainers(c *C) {
s.testInstallFinishStep(c, finishStepOpts{
encrypted: true,
Expand Down Expand Up @@ -922,6 +936,8 @@ func (s *deviceMgrInstallAPISuite) testInstallSetupStorageEncryption(c *C, hasTP
c.Check(ok, Equals, true)
// Check that state has been stored in the cache
c.Check(devicestate.CheckEncryptionSetupDataFromCache(s.state, label), IsNil)
// Cached auth options are cleaned
c.Check(s.state.Cached(devicestate.VolumesAuthOptionsKeyByLabel(label)), IsNil)
}

func (s *deviceMgrInstallAPISuite) TestInstallSetupStorageEncryptionHappy(c *C) {
Expand Down Expand Up @@ -1050,4 +1066,6 @@ func (s *deviceMgrInstallAPISuite) TestInstallSetupStorageEncryptionBadVolumesAu
// Checks now
c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:
- install API set-up encryption step \(internal error: wrong data type under volumesAuthOptionsKey\)`)
// Cached auth options are cleaned
c.Check(s.state.Cached(devicestate.VolumesAuthOptionsKeyByLabel(label)), IsNil)
}
2 changes: 1 addition & 1 deletion overlord/devicestate/devicestate_install_mode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1667,7 +1667,7 @@ func (s *deviceMgrInstallModeSuite) doRunFactoryResetChange(c *C, model *asserts
})()

var bootstrapContainer *secboot.MockBootstrappedContainer
defer devicestate.MockSecbootCreateBootstrappedContainer(func(key secboot.DiskUnlockKey, devicePath string) secboot.BootstrappedContainer {
defer devicestate.MockSecbootCreateBootstrappedContainer(func(key secboot.DiskUnlockKey, devicePath string, volumesAuth *device.VolumesAuthOptions) secboot.BootstrappedContainer {
if tc.encrypt {
c.Check([]byte(key), DeepEquals, chosenBootstrapKey)
c.Assert(bootstrapContainer, IsNil)
Expand Down
6 changes: 4 additions & 2 deletions overlord/devicestate/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -563,18 +563,20 @@ func MockCreateAllKnownSystemUsers(createAllUsers func(state *state.State, asser
return restore
}

func MockEncryptionSetupDataInCache(st *state.State, label string) (restore func()) {
func MockEncryptionSetupDataInCache(st *state.State, label string, volumesAuth *device.VolumesAuthOptions) (restore func()) {
st.Lock()
defer st.Unlock()
var esd *install.EncryptionSetupData
labelToEncData := map[string]*install.MockEncryptedDeviceAndRole{
"ubuntu-save": {
Role: "system-save",
EncryptedDevice: "/dev/mapper/ubuntu-save",
VolumesAuth: volumesAuth,
},
"ubuntu-data": {
Role: "system-data",
EncryptedDevice: "/dev/mapper/ubuntu-data",
VolumesAuth: volumesAuth,
},
}
esd = install.MockEncryptionSetupData(labelToEncData)
Expand Down Expand Up @@ -618,7 +620,7 @@ func MockSecbootRenameOrDeleteKeys(f func(node string, renames map[string]string
}
}

func MockSecbootCreateBootstrappedContainer(f func(key secboot.DiskUnlockKey, devicePath string) secboot.BootstrappedContainer) (restore func()) {
func MockSecbootCreateBootstrappedContainer(f func(key secboot.DiskUnlockKey, devicePath string, volumesAuth *device.VolumesAuthOptions) secboot.BootstrappedContainer) (restore func()) {
old := secbootCreateBootstrappedContainer
secbootCreateBootstrappedContainer = f
return func() {
Expand Down
3 changes: 2 additions & 1 deletion overlord/devicestate/handlers_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -1187,6 +1187,7 @@ func (m *DeviceManager) doInstallSetupStorageEncryption(t *state.Task, _ *tomb.T
if cached == nil {
return errors.New("volumes authentication is required but cannot find corresponding cached options")
}
st.Cache(volumesAuthOptionsKey{systemLabel}, nil)
var ok bool
volumesAuth, ok = cached.(*device.VolumesAuthOptions)
if !ok {
Expand Down Expand Up @@ -1290,7 +1291,7 @@ func createSaveBootstrappedContainer(saveNode string) (secboot.BootstrappedConta
return nil, err
}

return secbootCreateBootstrappedContainer(secboot.DiskUnlockKey(saveEncryptionKey), saveNode), nil
return secbootCreateBootstrappedContainer(secboot.DiskUnlockKey(saveEncryptionKey), saveNode, nil), nil
}

func deleteOldSaveKey(saveMntPnt string) error {
Expand Down
10 changes: 9 additions & 1 deletion secboot/bootstrap_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"fmt"
"io"

"github.com/snapcore/snapd/gadget/device"
"github.com/snapcore/snapd/osutil"
)

Expand All @@ -47,9 +48,11 @@ type BootstrappedContainer interface {
GetTokenWriter(slotName string) (KeyDataWriter, error)
// RemoveBootstrapKey removes the bootstrap key.
RemoveBootstrapKey() error
// GetAuthOptions returns attached authentication options.
GetAuthOptions() *device.VolumesAuthOptions
}

func createBootstrappedContainerMockImpl(key DiskUnlockKey, devicePath string) BootstrappedContainer {
func createBootstrappedContainerMockImpl(key DiskUnlockKey, devicePath string, volumesAuth *device.VolumesAuthOptions) BootstrappedContainer {
panic("trying to create a bootstrapped container in a non-secboot build")
}

Expand All @@ -59,6 +62,7 @@ type MockBootstrappedContainer struct {
BootstrapKeyRemoved bool
Slots map[string][]byte
Tokens map[string][]byte
AuthOptions *device.VolumesAuthOptions
}

func CreateMockBootstrappedContainer() *MockBootstrappedContainer {
Expand Down Expand Up @@ -103,3 +107,7 @@ func (l *MockBootstrappedContainer) RemoveBootstrapKey() error {
l.BootstrapKeyRemoved = true
return nil
}

func (l *MockBootstrappedContainer) GetAuthOptions() *device.VolumesAuthOptions {
return l.AuthOptions
}
11 changes: 9 additions & 2 deletions secboot/bootstrap_container_sb.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ import (
"fmt"

sb "github.com/snapcore/secboot"
"github.com/snapcore/snapd/gadget/device"
"github.com/snapcore/snapd/osutil"
)

type bootstrappedContainer struct {
tempContainerKeySlot string
devicePath string
key DiskUnlockKey
authOptions *device.VolumesAuthOptions
finished bool
}

Expand Down Expand Up @@ -76,11 +78,16 @@ func (bc *bootstrappedContainer) RemoveBootstrapKey() error {
return nil
}

func createBootstrappedContainerImpl(key DiskUnlockKey, devicePath string) BootstrappedContainer {
func (bc *bootstrappedContainer) GetAuthOptions() *device.VolumesAuthOptions {
return bc.authOptions
}

func createBootstrappedContainerImpl(key DiskUnlockKey, devicePath string, volumesAuth *device.VolumesAuthOptions) BootstrappedContainer {
return &bootstrappedContainer{
tempContainerKeySlot: "bootstrap-key",
devicePath: devicePath,
key: key,
authOptions: volumesAuth,
finished: false,
}
}
Expand All @@ -89,7 +96,7 @@ func init() {
CreateBootstrappedContainer = createBootstrappedContainerImpl
}

func MockCreateBootstrappedContainer(f func(key DiskUnlockKey, devicePath string) BootstrappedContainer) func() {
func MockCreateBootstrappedContainer(f func(key DiskUnlockKey, devicePath string, volumesAuth *device.VolumesAuthOptions) BootstrappedContainer) func() {
osutil.MustBeTestBinary("MockCreateBootstrappedContainer can be only called from tests")
old := CreateBootstrappedContainer
CreateBootstrappedContainer = f
Expand Down
20 changes: 15 additions & 5 deletions secboot/bootstrap_container_sb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
sb "github.com/snapcore/secboot"
. "gopkg.in/check.v1"

"github.com/snapcore/snapd/gadget/device"
"github.com/snapcore/snapd/secboot"
)

Expand All @@ -36,7 +37,7 @@ type bootstrapContainerSuite struct {
var _ = Suite(&bootstrapContainerSuite{})

func (*bootstrapContainerSuite) TestBootstrappedContainerHappy(c *C) {
container := secboot.CreateBootstrappedContainer([]byte{1, 2, 3, 4}, "/dev/foo")
container := secboot.CreateBootstrappedContainer([]byte{1, 2, 3, 4}, "/dev/foo", nil)

defer secboot.MockAddLUKS2ContainerUnlockKey(func(devicePath string, keyslotName string, existingKey sb.DiskUnlockKey, newKey sb.DiskUnlockKey) error {
c.Check(devicePath, Equals, "/dev/foo")
Expand Down Expand Up @@ -87,7 +88,7 @@ func (m *myKeyDataWriter) Commit() error {
}

func (*bootstrapContainerSuite) TestBootstrappedContainerHappyTokenWriter(c *C) {
container := secboot.CreateBootstrappedContainer([]byte{1, 2, 3, 4}, "/dev/foo")
container := secboot.CreateBootstrappedContainer([]byte{1, 2, 3, 4}, "/dev/foo", nil)

defer secboot.MockAddLUKS2ContainerUnlockKey(func(devicePath string, keyslotName string, existingKey sb.DiskUnlockKey, newKey sb.DiskUnlockKey) error {
c.Check(devicePath, Equals, "/dev/foo")
Expand Down Expand Up @@ -141,7 +142,7 @@ func (*bootstrapContainerSuite) TestBootstrappedContainerHappyTokenWriter(c *C)
}

func (*bootstrapContainerSuite) TestBootstrappedContainerErrorAddKey(c *C) {
container := secboot.CreateBootstrappedContainer([]byte{1, 2, 3, 4}, "/dev/foo")
container := secboot.CreateBootstrappedContainer([]byte{1, 2, 3, 4}, "/dev/foo", nil)

defer secboot.MockAddLUKS2ContainerUnlockKey(func(devicePath string, keyslotName string, existingKey sb.DiskUnlockKey, newKey sb.DiskUnlockKey) error {
return fmt.Errorf("boom")
Expand All @@ -152,7 +153,7 @@ func (*bootstrapContainerSuite) TestBootstrappedContainerErrorAddKey(c *C) {
}

func (*bootstrapContainerSuite) TestBootstrappedContainerErrorRemoveKey(c *C) {
container := secboot.CreateBootstrappedContainer([]byte{1, 2, 3, 4}, "/dev/foo")
container := secboot.CreateBootstrappedContainer([]byte{1, 2, 3, 4}, "/dev/foo", nil)

defer secboot.MockDeleteLUKS2ContainerKey(func(devicePath, slotName string) error {
return fmt.Errorf("boom")
Expand All @@ -163,7 +164,7 @@ func (*bootstrapContainerSuite) TestBootstrappedContainerErrorRemoveKey(c *C) {
}

func (*bootstrapContainerSuite) TestBootstrappedContainerTokenWriterFailure(c *C) {
container := secboot.CreateBootstrappedContainer([]byte{1, 2, 3, 4}, "/dev/foo")
container := secboot.CreateBootstrappedContainer([]byte{1, 2, 3, 4}, "/dev/foo", nil)

defer secboot.MockAddLUKS2ContainerUnlockKey(func(devicePath string, keyslotName string, existingKey sb.DiskUnlockKey, newKey sb.DiskUnlockKey) error {
c.Check(devicePath, Equals, "/dev/foo")
Expand Down Expand Up @@ -195,3 +196,12 @@ func (*bootstrapContainerSuite) TestBootstrappedContainerTokenWriterFailure(c *C
_, err = container.GetTokenWriter("")
c.Assert(err, ErrorMatches, `some error`)
}

func (*bootstrapContainerSuite) TestBootstrappedContainerAuthOptions(c *C) {
container := secboot.CreateBootstrappedContainer([]byte{1, 2, 3, 4}, "/dev/foo", nil)
c.Assert(container.GetAuthOptions(), IsNil)

volumesAuth := &device.VolumesAuthOptions{Mode: device.AuthModePassphrase, Passphrase: "test"}
container = secboot.CreateBootstrappedContainer([]byte{1, 2, 3, 4}, "/dev/foo", volumesAuth)
c.Assert(container.GetAuthOptions(), Equals, volumesAuth)
}
Loading

0 comments on commit 8b6b37c

Please sign in to comment.