diff --git a/boot/assets.go b/boot/assets.go
index 16bd7e51327..9c604393a19 100644
--- a/boot/assets.go
+++ b/boot/assets.go
@@ -37,7 +37,7 @@ import (
"github.com/snapcore/snapd/gadget/device"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
- "github.com/snapcore/snapd/secboot/keys"
+ "github.com/snapcore/snapd/secboot"
"github.com/snapcore/snapd/strutil"
)
@@ -256,7 +256,7 @@ func isAssetHashTrackedInMap(bam bootAssetsMap, assetName, assetHash string) boo
type TrustedAssetsInstallObserver interface {
BootLoaderSupportsEfiVariables() bool
ObserveExistingTrustedRecoveryAssets(recoveryRootDir string) error
- ChosenEncryptionKeys(key, saveKey keys.EncryptionKey)
+ ChosenEncryptionKeys(resetter, saveResetter secboot.KeyResetter)
UpdateBootEntry() error
Observe(op gadget.ContentOperation, partRole, root, relativeTarget string, data *gadget.ContentChange) (gadget.ContentChangeAction, error)
}
@@ -279,9 +279,9 @@ type trustedAssetsInstallObserverImpl struct {
trustedRecoveryAssets map[string]string
trackedRecoveryAssets bootAssetsMap
- useEncryption bool
- dataEncryptionKey keys.EncryptionKey
- saveEncryptionKey keys.EncryptionKey
+ useEncryption bool
+ dataKeyResetter secboot.KeyResetter
+ saveKeyResetter secboot.KeyResetter
seedBootloader bootloader.Bootloader
}
@@ -368,10 +368,10 @@ func (o *trustedAssetsInstallObserverImpl) currentTrustedRecoveryBootAssetsMap()
return o.trackedRecoveryAssets
}
-func (o *trustedAssetsInstallObserverImpl) ChosenEncryptionKeys(key, saveKey keys.EncryptionKey) {
+func (o *trustedAssetsInstallObserverImpl) ChosenEncryptionKeys(resetter, saveResetter secboot.KeyResetter) {
o.useEncryption = true
- o.dataEncryptionKey = key
- o.saveEncryptionKey = saveKey
+ o.dataKeyResetter = resetter
+ o.saveKeyResetter = saveResetter
}
func (o *trustedAssetsInstallObserverImpl) UpdateBootEntry() error {
diff --git a/boot/assets_test.go b/boot/assets_test.go
index 9295558bdf9..f0b322dd313 100644
--- a/boot/assets_test.go
+++ b/boot/assets_test.go
@@ -37,7 +37,6 @@ import (
"github.com/snapcore/snapd/gadget"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/secboot"
- "github.com/snapcore/snapd/secboot/keys"
"github.com/snapcore/snapd/seed"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/testutil"
@@ -474,13 +473,16 @@ func (s *assetsSuite) TestInstallObserverNonTrustedBootloader(c *C) {
obs, err := boot.TrustedAssetsInstallObserverForModel(uc20Model, d, useEncryption)
c.Assert(err, IsNil)
c.Assert(obs, NotNil)
- obs.ChosenEncryptionKeys(keys.EncryptionKey{1, 2, 3, 4}, keys.EncryptionKey{5, 6, 7, 8})
+
+ dataResetter := &secboot.MockKeyResetter{}
+ saveResetter := &secboot.MockKeyResetter{}
+ obs.ChosenEncryptionKeys(dataResetter, saveResetter)
observerImpl, ok := obs.(*boot.TrustedAssetsInstallObserverImpl)
c.Assert(ok, Equals, true)
- c.Check(observerImpl.CurrentDataEncryptionKey(), DeepEquals, keys.EncryptionKey{1, 2, 3, 4})
- c.Check(observerImpl.CurrentSaveEncryptionKey(), DeepEquals, keys.EncryptionKey{5, 6, 7, 8})
+ c.Check(observerImpl.CurrentDataKeyResetter(), Equals, dataResetter)
+ c.Check(observerImpl.CurrentSaveKeyResetter(), Equals, saveResetter)
}
func (s *assetsSuite) TestInstallObserverTrustedButNoAssets(c *C) {
@@ -499,13 +501,16 @@ func (s *assetsSuite) TestInstallObserverTrustedButNoAssets(c *C) {
obs, err := boot.TrustedAssetsInstallObserverForModel(uc20Model, d, useEncryption)
c.Assert(err, IsNil)
c.Assert(obs, NotNil)
- obs.ChosenEncryptionKeys(keys.EncryptionKey{1, 2, 3, 4}, keys.EncryptionKey{5, 6, 7, 8})
+
+ dataResetter := &secboot.MockKeyResetter{}
+ saveResetter := &secboot.MockKeyResetter{}
+ obs.ChosenEncryptionKeys(dataResetter, saveResetter)
observerImpl, ok := obs.(*boot.TrustedAssetsInstallObserverImpl)
c.Assert(ok, Equals, true)
- c.Check(observerImpl.CurrentDataEncryptionKey(), DeepEquals, keys.EncryptionKey{1, 2, 3, 4})
- c.Check(observerImpl.CurrentSaveEncryptionKey(), DeepEquals, keys.EncryptionKey{5, 6, 7, 8})
+ c.Check(observerImpl.CurrentDataKeyResetter(), Equals, dataResetter)
+ c.Check(observerImpl.CurrentSaveKeyResetter(), Equals, saveResetter)
}
func (s *assetsSuite) TestInstallObserverTrustedReuseNameErr(c *C) {
diff --git a/boot/bootstate20.go b/boot/bootstate20.go
index 1474918eaa4..b601a3fe5ce 100644
--- a/boot/bootstate20.go
+++ b/boot/bootstate20.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2019-2023 Canonical Ltd
+ * Copyright (C) 2019-2024 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
@@ -69,12 +69,12 @@ func modeenvUnlock() {
modeenvMu.Unlock()
}
-func isModeeenvLocked() bool {
+func IsModeeenvLocked() bool {
return atomic.LoadInt32(&modeenvLocked) == 1
}
func loadModeenv() (*Modeenv, error) {
- if !isModeeenvLocked() {
+ if !IsModeeenvLocked() {
return nil, fmt.Errorf("internal error: cannot read modeenv without the lock")
}
modeenv, err := ReadModeenv("")
@@ -184,7 +184,7 @@ func newBootStateUpdate20(m *Modeenv) (*bootStateUpdate20, error) {
// commit will write out boot state persistently to disk.
func (u20 *bootStateUpdate20) commit() error {
- if !isModeeenvLocked() {
+ if !IsModeeenvLocked() {
return fmt.Errorf("internal error: cannot commit modeenv without the lock")
}
diff --git a/boot/export_sb_test.go b/boot/export_sb_test.go
index 22bcbbc4383..3c638067925 100644
--- a/boot/export_sb_test.go
+++ b/boot/export_sb_test.go
@@ -21,6 +21,8 @@
package boot
import (
+ "context"
+
"github.com/canonical/go-efilib"
"github.com/canonical/go-efilib/linux"
@@ -33,19 +35,19 @@ var (
SetEfiBootOrderVariable = setEfiBootOrderVariable
)
-func MockEfiListVariables(f func() ([]efi.VariableDescriptor, error)) (restore func()) {
+func MockEfiListVariables(f func(ctx context.Context) ([]efi.VariableDescriptor, error)) (restore func()) {
restore = testutil.Backup(&efiListVariables)
efiListVariables = f
return restore
}
-func MockEfiReadVariable(f func(name string, guid efi.GUID) ([]byte, efi.VariableAttributes, error)) (restore func()) {
+func MockEfiReadVariable(f func(ctx context.Context, name string, guid efi.GUID) ([]byte, efi.VariableAttributes, error)) (restore func()) {
restore = testutil.Backup(&efiReadVariable)
efiReadVariable = f
return restore
}
-func MockEfiWriteVariable(f func(name string, guid efi.GUID, attrs efi.VariableAttributes, data []byte) error) (restore func()) {
+func MockEfiWriteVariable(f func(ctx context.Context, name string, guid efi.GUID, attrs efi.VariableAttributes, data []byte) error) (restore func()) {
restore = testutil.Backup(&efiWriteVariable)
efiWriteVariable = f
return restore
diff --git a/boot/export_test.go b/boot/export_test.go
index 21882fd47b1..4d38dc287db 100644
--- a/boot/export_test.go
+++ b/boot/export_test.go
@@ -27,7 +27,6 @@ import (
"github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/kernel/fde"
"github.com/snapcore/snapd/secboot"
- "github.com/snapcore/snapd/secboot/keys"
"github.com/snapcore/snapd/seed"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/testutil"
@@ -104,12 +103,12 @@ func (o *trustedAssetsInstallObserverImpl) CurrentTrustedRecoveryBootAssetsMap()
return o.currentTrustedRecoveryBootAssetsMap()
}
-func (o *trustedAssetsInstallObserverImpl) CurrentDataEncryptionKey() keys.EncryptionKey {
- return o.dataEncryptionKey
+func (o *TrustedAssetsInstallObserverImpl) CurrentDataKeyResetter() secboot.KeyResetter {
+ return o.dataKeyResetter
}
-func (o *trustedAssetsInstallObserverImpl) CurrentSaveEncryptionKey() keys.EncryptionKey {
- return o.saveEncryptionKey
+func (o *TrustedAssetsInstallObserverImpl) CurrentSaveKeyResetter() secboot.KeyResetter {
+ return o.saveKeyResetter
}
func MockSecbootProvisionTPM(f func(mode secboot.TPMProvisionMode, lockoutAuthFile string) error) (restore func()) {
@@ -118,7 +117,7 @@ func MockSecbootProvisionTPM(f func(mode secboot.TPMProvisionMode, lockoutAuthFi
return restore
}
-func MockSecbootSealKeys(f func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error) (restore func()) {
+func MockSecbootSealKeys(f func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) ([]byte, error)) (restore func()) {
old := secbootSealKeys
secbootSealKeys = f
return func() {
@@ -253,10 +252,10 @@ func MockRunFDESetupHook(f fde.RunSetupHookFunc) (restore func()) {
}
func MockResealKeyToModeenvUsingFDESetupHook(f func(string, *Modeenv, bool) error) (restore func()) {
- old := resealKeyToModeenvUsingFDESetupHook
- resealKeyToModeenvUsingFDESetupHook = f
+ old := ResealKeyToModeenvUsingFDESetupHook
+ ResealKeyToModeenvUsingFDESetupHook = f
return func() {
- resealKeyToModeenvUsingFDESetupHook = old
+ ResealKeyToModeenvUsingFDESetupHook = old
}
}
diff --git a/boot/makebootable.go b/boot/makebootable.go
index 0ad6383c7e6..5f9242db8e5 100644
--- a/boot/makebootable.go
+++ b/boot/makebootable.go
@@ -554,7 +554,7 @@ func makeRunnableSystem(model *asserts.Model, bootWith *BootableSet, observer Tr
flags.SnapsDir = snapBlobDir
}
// seal the encryption key to the parameters specified in modeenv
- if err := sealKeyToModeenv(observerImpl.dataEncryptionKey, observerImpl.saveEncryptionKey, model, modeenv, flags); err != nil {
+ if err := sealKeyToModeenv(observerImpl.dataKeyResetter, observerImpl.saveKeyResetter, model, modeenv, flags); err != nil {
return err
}
}
diff --git a/boot/makebootable_test.go b/boot/makebootable_test.go
index 9aab2ee3fd1..0b14c985445 100644
--- a/boot/makebootable_test.go
+++ b/boot/makebootable_test.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2014-2022 Canonical Ltd
+ * Copyright (C) 2014-2022, 2024 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
@@ -40,7 +40,6 @@ import (
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/secboot"
- "github.com/snapcore/snapd/secboot/keys"
"github.com/snapcore/snapd/seed"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snapfile"
@@ -627,14 +626,10 @@ version: 5.0
err = obs.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir)
c.Assert(err, IsNil)
- // set encryption key
- myKey := keys.EncryptionKey{}
- myKey2 := keys.EncryptionKey{}
- for i := range myKey {
- myKey[i] = byte(i)
- myKey2[i] = byte(128 + i)
- }
- obs.ChosenEncryptionKeys(myKey, myKey2)
+ // set key resetter
+ dataResetter := &secboot.MockKeyResetter{}
+ saveResetter := &secboot.MockKeyResetter{}
+ obs.ChosenEncryptionKeys(dataResetter, saveResetter)
// set a mock recovery kernel
readSystemEssentialCalls := 0
@@ -699,15 +694,14 @@ version: 5.0
// set mock key sealing
sealKeysCalls := 0
- restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error {
+ restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) ([]byte, error) {
c.Assert(provisionCalls, Equals, 1, Commentf("TPM must have been provisioned before"))
sealKeysCalls++
switch sealKeysCalls {
case 1:
c.Check(keys, HasLen, 1)
- c.Check(keys[0].Key, DeepEquals, myKey)
- c.Check(keys[0].KeyFile, Equals,
- filepath.Join(s.rootdir, "/run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key"))
+ c.Check(keys[0].Resetter, Equals, dataResetter)
+ c.Check(keys[0].KeyFile, Equals, "")
if factoryReset {
c.Check(params.PCRPolicyCounterHandle, Equals, secboot.AltRunObjectPCRPolicyCounterHandle)
} else {
@@ -715,22 +709,15 @@ version: 5.0
}
case 2:
c.Check(keys, HasLen, 2)
- c.Check(keys[0].Key, DeepEquals, myKey)
- c.Check(keys[1].Key, DeepEquals, myKey2)
- c.Check(keys[0].KeyFile, Equals,
- filepath.Join(s.rootdir,
- "/run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key"))
+ c.Check(keys[0].Resetter, Equals, dataResetter)
+ c.Check(keys[0].KeyFile, Equals, "")
+ c.Check(keys[1].Resetter, Equals, saveResetter)
if factoryReset {
c.Check(params.PCRPolicyCounterHandle, Equals, secboot.AltFallbackObjectPCRPolicyCounterHandle)
- c.Check(keys[1].KeyFile, Equals,
- filepath.Join(s.rootdir,
- "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key.factory-reset"))
-
+ c.Check(keys[1].KeyFile, Equals, "")
} else {
c.Check(params.PCRPolicyCounterHandle, Equals, secboot.FallbackObjectPCRPolicyCounterHandle)
- c.Check(keys[1].KeyFile, Equals,
- filepath.Join(s.rootdir,
- "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key"))
+ c.Check(keys[1].KeyFile, Equals, "")
}
default:
c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls)
@@ -783,7 +770,7 @@ version: 5.0
c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
- return nil
+ return nil, nil
})
defer restore()
@@ -1152,14 +1139,10 @@ version: 5.0
err = obs.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir)
c.Assert(err, IsNil)
- // set encryption key
- myKey := keys.EncryptionKey{}
- myKey2 := keys.EncryptionKey{}
- for i := range myKey {
- myKey[i] = byte(i)
- myKey2[i] = byte(128 + i)
- }
- obs.ChosenEncryptionKeys(myKey, myKey2)
+ // set key resetter
+ dataResetter := &secboot.MockKeyResetter{}
+ saveResetter := &secboot.MockKeyResetter{}
+ obs.ChosenEncryptionKeys(dataResetter, saveResetter)
// set a mock recovery kernel
readSystemEssentialCalls := 0
@@ -1179,16 +1162,15 @@ version: 5.0
defer restore()
// set mock key sealing
sealKeysCalls := 0
- restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error {
+ restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) ([]byte, error) {
sealKeysCalls++
switch sealKeysCalls {
case 1:
c.Check(keys, HasLen, 1)
- c.Check(keys[0].Key, DeepEquals, myKey)
+ c.Check(keys[0].Resetter, Equals, dataResetter)
case 2:
- c.Check(keys, HasLen, 2)
- c.Check(keys[0].Key, DeepEquals, myKey)
- c.Check(keys[1].Key, DeepEquals, myKey2)
+ c.Check(keys, HasLen, 1)
+ c.Check(keys[0].Resetter, Equals, saveResetter)
default:
c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls)
}
@@ -1217,7 +1199,7 @@ version: 5.0
})
c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
- return fmt.Errorf("seal error")
+ return nil, fmt.Errorf("seal error")
})
defer restore()
@@ -1350,7 +1332,9 @@ version: 5.0
err = obs.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir)
c.Assert(err, IsNil)
- obs.ChosenEncryptionKeys(keys.EncryptionKey{}, keys.EncryptionKey{})
+ dataResetter := &secboot.MockKeyResetter{}
+ saveResetter := &secboot.MockKeyResetter{}
+ obs.ChosenEncryptionKeys(dataResetter, saveResetter)
// set a mock recovery kernel
readSystemEssentialCalls := 0
@@ -1370,7 +1354,7 @@ version: 5.0
defer restore()
// set mock key sealing
sealKeysCalls := 0
- restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error {
+ restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) ([]byte, error) {
sealKeysCalls++
switch sealKeysCalls {
case 1, 2:
@@ -1394,7 +1378,7 @@ version: 5.0
c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
- return nil
+ return nil, nil
})
defer restore()
diff --git a/boot/seal.go b/boot/seal.go
index c8911f9ff73..c4883853c90 100644
--- a/boot/seal.go
+++ b/boot/seal.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2020-2023 Canonical Ltd
+ * Copyright (C) 2020-2024 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
@@ -20,9 +20,6 @@
package boot
import (
- "crypto/ecdsa"
- "crypto/elliptic"
- "crypto/rand"
"encoding/json"
"fmt"
"os"
@@ -35,8 +32,8 @@ import (
"github.com/snapcore/snapd/kernel/fde"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/osutil/disks"
"github.com/snapcore/snapd/secboot"
- "github.com/snapcore/snapd/secboot/keys"
"github.com/snapcore/snapd/seed"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/strutil"
@@ -94,7 +91,7 @@ func MockResealKeyToModeenv(f func(rootdir string, modeenv *Modeenv, expectResea
type MockSealKeyToModeenvFlags = sealKeyToModeenvFlags
// MockSealKeyToModeenv is used for testing from other packages.
-func MockSealKeyToModeenv(f func(key, saveKey keys.EncryptionKey, model *asserts.Model, modeenv *Modeenv, flags MockSealKeyToModeenvFlags) error) (restore func()) {
+func MockSealKeyToModeenv(f func(resetter, saveResetter secboot.KeyResetter, model *asserts.Model, modeenv *Modeenv, flags MockSealKeyToModeenvFlags) error) (restore func()) {
old := sealKeyToModeenv
sealKeyToModeenv = f
return func() {
@@ -129,8 +126,8 @@ type sealKeyToModeenvFlags struct {
// sealKeyToModeenvImpl seals the supplied keys to the parameters specified
// in modeenv.
// It assumes to be invoked in install mode.
-func sealKeyToModeenvImpl(key, saveKey keys.EncryptionKey, model *asserts.Model, modeenv *Modeenv, flags sealKeyToModeenvFlags) error {
- if !isModeeenvLocked() {
+func sealKeyToModeenvImpl(resetter, saveResetter secboot.KeyResetter, model *asserts.Model, modeenv *Modeenv, flags sealKeyToModeenvFlags) error {
+ if !IsModeeenvLocked() {
return fmt.Errorf("internal error: cannot seal without the modeenv lock")
}
@@ -148,79 +145,74 @@ func sealKeyToModeenvImpl(key, saveKey keys.EncryptionKey, model *asserts.Model,
}
if flags.HasFDESetupHook {
- return sealKeyToModeenvUsingFDESetupHook(key, saveKey, model, modeenv, flags)
+ return sealKeyToModeenvUsingFDESetupHook(resetter, saveResetter, model, modeenv, flags)
}
if flags.StateUnlocker != nil {
relock := flags.StateUnlocker()
defer relock()
}
- return sealKeyToModeenvUsingSecboot(key, saveKey, model, modeenv, flags)
+ return sealKeyToModeenvUsingSecboot(resetter, saveResetter, model, modeenv, flags)
}
-func runKeySealRequests(key keys.EncryptionKey) []secboot.SealKeyRequest {
+func runKeySealRequests(resetter secboot.KeyResetter) []secboot.SealKeyRequest {
return []secboot.SealKeyRequest{
{
- Key: key,
- KeyName: "ubuntu-data",
- KeyFile: device.DataSealedKeyUnder(InitramfsBootEncryptionKeyDir),
+ KeyName: "ubuntu-data",
+ Role: "run+recover",
+ Resetter: resetter,
},
}
}
-func fallbackKeySealRequests(key, saveKey keys.EncryptionKey, factoryReset bool) []secboot.SealKeyRequest {
- saveFallbackKey := device.FallbackSaveSealedKeyUnder(InitramfsSeedEncryptionKeyDir)
-
- if factoryReset {
- // factory reset uses alternative sealed key location, such that
- // until we boot into the run mode, both sealed keys are present
- // on disk
- saveFallbackKey = device.FactoryResetFallbackSaveSealedKeyUnder(InitramfsSeedEncryptionKeyDir)
- }
+func fallbackKeySealRequests(resetter, saveResetter secboot.KeyResetter, factoryReset bool) []secboot.SealKeyRequest {
return []secboot.SealKeyRequest{
{
- Key: key,
- KeyName: "ubuntu-data",
- KeyFile: device.FallbackDataSealedKeyUnder(InitramfsSeedEncryptionKeyDir),
+ KeyName: "ubuntu-data",
+ Role: "recover",
+ SlotName: "default-fallback",
+ Resetter: resetter,
},
{
- Key: saveKey,
- KeyName: "ubuntu-save",
- KeyFile: saveFallbackKey,
+ KeyName: "ubuntu-save",
+ Role: "recover",
+ SlotName: "default-fallback",
+ Resetter: saveResetter,
},
}
}
-func sealKeyToModeenvUsingFDESetupHook(key, saveKey keys.EncryptionKey, model *asserts.Model, modeenv *Modeenv, flags sealKeyToModeenvFlags) error {
- // XXX: Move the auxKey creation to a more generic place, see
- // PR#10123 for a possible way of doing this. However given
- // that the equivalent key for the TPM case is also created in
- // sealKeyToModeenvUsingTPM more symetric to create the auxKey
- // here and when we also move TPM to use the auxKey to move
- // the creation of it.
- auxKey, err := keys.NewAuxKey()
- if err != nil {
- return fmt.Errorf("cannot create aux key: %v", err)
- }
+func sealKeyToModeenvUsingFDESetupHook(resetter, saveResetter secboot.KeyResetter, model *asserts.Model, modeenv *Modeenv, flags sealKeyToModeenvFlags) error {
params := secboot.SealKeysWithFDESetupHookParams{
Model: modeenv.ModelForSealing(),
- AuxKey: auxKey,
AuxKeyFile: filepath.Join(InstallHostFDESaveDir, "aux-key"),
}
factoryReset := flags.FactoryReset
- skrs := append(runKeySealRequests(key), fallbackKeySealRequests(key, saveKey, factoryReset)...)
+ skrs := append(runKeySealRequests(resetter), fallbackKeySealRequests(resetter, saveResetter, factoryReset)...)
if err := secbootSealKeysWithFDESetupHook(RunFDESetupHook, skrs, ¶ms); err != nil {
return err
}
- if err := device.StampSealedKeys(InstallHostWritableDir(model), "fde-setup-hook"); err != nil {
+ if err := device.StampSealedKeys(InstallHostWritableDir(model), device.SealingMethodNextGeneration); err != nil {
return err
}
+ for _, resetter := range []secboot.KeyResetter{
+ resetter,
+ saveResetter,
+ } {
+ if resetter != nil {
+ if err := resetter.RemoveInstallationKey(); err != nil {
+ // This could be a warning
+ return err
+ }
+ }
+ }
+
return nil
}
-func sealKeyToModeenvUsingSecboot(key, saveKey keys.EncryptionKey, model *asserts.Model, modeenv *Modeenv, flags sealKeyToModeenvFlags) error {
+func sealKeyToModeenvUsingSecboot(resetter, saveResetter secboot.KeyResetter, model *asserts.Model, modeenv *Modeenv, flags sealKeyToModeenvFlags) error {
// build the recovery mode boot chain
rbl, err := bootloader.Find(InitramfsUbuntuSeedDir, &bootloader.Options{
Role: bootloader.RoleRecovery,
@@ -274,12 +266,6 @@ func sealKeyToModeenvUsingSecboot(key, saveKey keys.EncryptionKey, model *assert
// the boot chains we seal the fallback object to
rpbc := toPredictableBootChains(recoveryBootChains)
- // gets written to a file by sealRunObjectKeys()
- authKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
- if err != nil {
- return fmt.Errorf("cannot generate key for signing dynamic authorization policies: %v", err)
- }
-
runObjectKeyPCRHandle := uint32(secboot.RunObjectPCRPolicyCounterHandle)
fallbackObjectKeyPCRHandle := uint32(secboot.FallbackObjectPCRPolicyCounterHandle)
if flags.FactoryReset {
@@ -324,18 +310,30 @@ func sealKeyToModeenvUsingSecboot(key, saveKey keys.EncryptionKey, model *assert
// TODO: refactor sealing functions to take a struct instead of so many
// parameters
- err = sealRunObjectKeys(key, pbc, authKey, roleToBlName, runObjectKeyPCRHandle)
+ primaryKey, err := sealRunObjectKeys(resetter, pbc, roleToBlName, runObjectKeyPCRHandle)
if err != nil {
return err
}
- err = sealFallbackObjectKeys(key, saveKey, rpbc, authKey, roleToBlName, flags.FactoryReset,
+ err = sealFallbackObjectKeys(resetter, saveResetter, rpbc, primaryKey, roleToBlName, flags.FactoryReset,
fallbackObjectKeyPCRHandle)
if err != nil {
return err
}
- if err := device.StampSealedKeys(InstallHostWritableDir(model), device.SealingMethodTPM); err != nil {
+ for _, resetter := range []secboot.KeyResetter{
+ resetter,
+ saveResetter,
+ } {
+ if resetter != nil {
+ if err := resetter.RemoveInstallationKey(); err != nil {
+ // This could be a warning
+ return err
+ }
+ }
+ }
+
+ if err := device.StampSealedKeys(InstallHostWritableDir(model), device.SealingMethodNextGeneration); err != nil {
return err
}
@@ -363,15 +361,15 @@ func usesAltPCRHandles() (bool, error) {
return handle == secboot.AltFallbackObjectPCRPolicyCounterHandle, nil
}
-func sealRunObjectKeys(key keys.EncryptionKey, pbc predictableBootChains, authKey *ecdsa.PrivateKey, roleToBlName map[bootloader.Role]string, pcrHandle uint32) error {
+func sealRunObjectKeys(resetter secboot.KeyResetter, pbc predictableBootChains, roleToBlName map[bootloader.Role]string, pcrHandle uint32) ([]byte, error) {
modelParams, err := sealKeyModelParams(pbc, roleToBlName)
if err != nil {
- return fmt.Errorf("cannot prepare for key sealing: %v", err)
+ return nil, fmt.Errorf("cannot prepare for key sealing: %v", err)
}
sealKeyParams := &secboot.SealKeysParams{
ModelParams: modelParams,
- TPMPolicyAuthKey: authKey,
+ PrimaryKey: nil,
TPMPolicyAuthKeyFile: filepath.Join(InstallHostFDESaveDir, "tpm-policy-auth-key"),
PCRPolicyCounterHandle: pcrHandle,
}
@@ -382,14 +380,15 @@ func sealRunObjectKeys(key keys.EncryptionKey, pbc predictableBootChains, authKe
// path only unseals one object because unsealing is expensive.
// Furthermore, the run object key is stored on ubuntu-boot so that we do not
// need to continually write/read keys from ubuntu-seed.
- if err := secbootSealKeys(runKeySealRequests(key), sealKeyParams); err != nil {
- return fmt.Errorf("cannot seal the encryption keys: %v", err)
+ primaryKey, err := secbootSealKeys(runKeySealRequests(resetter), sealKeyParams)
+ if err != nil {
+ return nil, fmt.Errorf("cannot seal the encryption keys: %v", err)
}
- return nil
+ return primaryKey, nil
}
-func sealFallbackObjectKeys(key, saveKey keys.EncryptionKey, pbc predictableBootChains, authKey *ecdsa.PrivateKey, roleToBlName map[bootloader.Role]string, factoryReset bool, pcrHandle uint32) error {
+func sealFallbackObjectKeys(resetter, saveResetter secboot.KeyResetter, pbc predictableBootChains, primaryKey []byte, roleToBlName map[bootloader.Role]string, factoryReset bool, pcrHandle uint32) error {
// also seal the keys to the recovery bootchains as a fallback
modelParams, err := sealKeyModelParams(pbc, roleToBlName)
if err != nil {
@@ -397,7 +396,7 @@ func sealFallbackObjectKeys(key, saveKey keys.EncryptionKey, pbc predictableBoot
}
sealKeyParams := &secboot.SealKeysParams{
ModelParams: modelParams,
- TPMPolicyAuthKey: authKey,
+ PrimaryKey: primaryKey,
PCRPolicyCounterHandle: pcrHandle,
}
logger.Debugf("sealing fallback key with PCR handle: %#x", sealKeyParams.PCRPolicyCounterHandle)
@@ -405,7 +404,7 @@ func sealFallbackObjectKeys(key, saveKey keys.EncryptionKey, pbc predictableBoot
// key files are stored on ubuntu-seed, separate from ubuntu-data so they
// can be used if ubuntu-data and ubuntu-boot are corrupted or unavailable.
- if err := secbootSealKeys(fallbackKeySealRequests(key, saveKey, factoryReset), sealKeyParams); err != nil {
+ if _, err := secbootSealKeys(fallbackKeySealRequests(resetter, saveResetter, factoryReset), sealKeyParams); err != nil {
return fmt.Errorf("cannot seal the fallback encryption keys: %v", err)
}
@@ -414,6 +413,10 @@ func sealFallbackObjectKeys(key, saveKey keys.EncryptionKey, pbc predictableBoot
var resealKeyToModeenv = resealKeyToModeenvImpl
+func ProvideResealKeyToModeenv(f func(rootdir string, modeenv *Modeenv, expectReseal bool, unlocker Unlocker) error) {
+ resealKeyToModeenv = f
+}
+
// resealKeyToModeenv reseals the existing encryption key to the
// parameters specified in modeenv.
// It is *very intentional* that resealing takes the modeenv and only
@@ -422,7 +425,7 @@ var resealKeyToModeenv = resealKeyToModeenvImpl
// transient/in-memory information with the risk that successive
// reseals during in-progress operations produce diverging outcomes.
func resealKeyToModeenvImpl(rootdir string, modeenv *Modeenv, expectReseal bool, unlocker Unlocker) error {
- if !isModeeenvLocked() {
+ if !IsModeeenvLocked() {
return fmt.Errorf("internal error: cannot reseal without the modeenv lock")
}
@@ -436,43 +439,40 @@ func resealKeyToModeenvImpl(rootdir string, modeenv *Modeenv, expectReseal bool,
}
switch method {
case device.SealingMethodFDESetupHook:
- return resealKeyToModeenvUsingFDESetupHook(rootdir, modeenv, expectReseal)
+ return ResealKeyToModeenvUsingFDESetupHook(rootdir, modeenv, expectReseal)
case device.SealingMethodTPM, device.SealingMethodLegacyTPM:
if unlocker != nil {
// unlock/relock global state
defer unlocker()()
}
- return resealKeyToModeenvSecboot(rootdir, modeenv, expectReseal)
+ return ResealKeyToModeenvSecboot(rootdir, modeenv, expectReseal)
default:
return fmt.Errorf("unknown key sealing method: %q", method)
}
}
-var resealKeyToModeenvUsingFDESetupHook = resealKeyToModeenvUsingFDESetupHookImpl
+var ResealKeyToModeenvUsingFDESetupHook = resealKeyToModeenvUsingFDESetupHookImpl
func resealKeyToModeenvUsingFDESetupHookImpl(rootdir string, modeenv *Modeenv, expectReseal bool) error {
- // TODO: we need to implement reseal at least in terms of
- // rebinding the keys to models on remodeling
-
- // TODO: If we have situations that do TPM-like full sealing then:
- // Implement reseal using the fde-setup hook. This will
- // require a helper like "FDEShouldResealUsingSetupHook"
- // that will be set by devicestate and returns (bool,
- // error). It needs to return "false" during seeding
- // because then there is no kernel available yet. It
- // can though return true as soon as there's an active
- // kernel if seeded is false
- //
- // It will also need to run HasFDESetupHook internally
- // and return an error if the hook goes missing
- // (e.g. because a kernel refresh losses the hook by
- // accident). It could also run features directly and
- // check for "reseal" in features.
- return nil
+ var models []secboot.ModelForSealing
+ models = append(models, modeenv.ModelForSealing())
+ if modeenv.TryModel != "" {
+ models = append(models, modeenv.TryModelForSealing())
+ }
+
+ keys := []string{
+ device.DataSealedKeyUnder(InitramfsBootEncryptionKeyDir),
+ device.FallbackDataSealedKeyUnder(InitramfsSeedEncryptionKeyDir),
+ device.FallbackSaveSealedKeyUnder(InitramfsSeedEncryptionKeyDir),
+ }
+
+ primaryKey := filepath.Join(InstallHostFDESaveDir, "aux-key")
+
+ return secboot.ResealKeysWithFDESetupHook(keys, primaryKey, models)
}
// TODO:UC20: allow more than one model to accommodate the remodel scenario
-func resealKeyToModeenvSecboot(rootdir string, modeenv *Modeenv, expectReseal bool) error {
+func ResealKeyToModeenvSecboot(rootdir string, modeenv *Modeenv, expectReseal bool) error {
// build the recovery mode boot chain
rbl, err := bootloader.Find(InitramfsUbuntuSeedDir, &bootloader.Options{
Role: bootloader.RoleRecovery,
@@ -593,6 +593,153 @@ func resealKeyToModeenvSecboot(rootdir string, modeenv *Modeenv, expectReseal bo
return nil
}
+func ResealKeyToModeenvNextGeneration(rootdir string, modeenv *Modeenv, expectReseal bool) error {
+ fmt.Fprintf(os.Stderr, "resealing!")
+ var devices []string
+
+ // FIXME: find out the correct way to list disks
+ for _, p := range []string{
+ "/run/mnt/data",
+ "/run/mnt/ubuntu-save",
+ } {
+ partUUID, err := disks.PartitionUUIDFromMountPoint(p, &disks.Options{
+ IsDecryptedDevice: true,
+ })
+ if err != nil {
+ return fmt.Errorf("cannot partition partition %s: %v", p, err)
+ }
+ diskPath := filepath.Join("/dev/disk/by-partuuid", partUUID)
+ devices = append(devices, diskPath)
+ }
+
+ // build the recovery mode boot chain
+ rbl, err := bootloader.Find(InitramfsUbuntuSeedDir, &bootloader.Options{
+ Role: bootloader.RoleRecovery,
+ })
+ if err != nil {
+ return fmt.Errorf("cannot find the recovery bootloader: %v", err)
+ }
+ tbl, ok := rbl.(bootloader.TrustedAssetsBootloader)
+ if !ok {
+ // TODO:UC20: later the exact kind of bootloaders we expect here might change
+ return fmt.Errorf("internal error: sealed keys but not a trusted assets bootloader")
+ }
+ // derive the allowed modes for each system mentioned in the modeenv
+ modes := modesForSystems(modeenv)
+
+ // the recovery boot chains for the run key are generated for all
+ // recovery systems, including those that are being tried; since this is
+ // a run key, the boot chains are generated for both models to
+ // accommodate the dynamics of a remodel
+ includeTryModel := true
+ recoveryBootChainsForRunKey, err := recoveryBootChainsForSystems(modeenv.CurrentRecoverySystems, modes, tbl,
+ modeenv, includeTryModel, dirs.SnapSeedDir)
+ if err != nil {
+ return fmt.Errorf("cannot compose recovery boot chains for run key: %v", err)
+ }
+
+ // the boot chains for recovery keys include only those system that were
+ // tested and are known to be good
+ testedRecoverySystems := modeenv.GoodRecoverySystems
+ if len(testedRecoverySystems) == 0 && len(modeenv.CurrentRecoverySystems) > 0 {
+ // compatibility for systems where good recovery systems list
+ // has not been populated yet
+ testedRecoverySystems = modeenv.CurrentRecoverySystems[:1]
+ logger.Noticef("no good recovery systems for reseal, fallback to known current system %v",
+ testedRecoverySystems[0])
+ }
+ // use the current model as the recovery keys are not expected to be
+ // used during a remodel
+ includeTryModel = false
+ recoveryBootChains, err := recoveryBootChainsForSystems(testedRecoverySystems, modes, tbl, modeenv, includeTryModel, dirs.SnapSeedDir)
+ if err != nil {
+ return fmt.Errorf("cannot compose recovery boot chains: %v", err)
+ }
+
+ // build the run mode boot chains
+ bl, err := bootloader.Find(InitramfsUbuntuBootDir, &bootloader.Options{
+ Role: bootloader.RoleRunMode,
+ NoSlashBoot: true,
+ })
+ if err != nil {
+ return fmt.Errorf("cannot find the bootloader: %v", err)
+ }
+ cmdlines, err := kernelCommandLinesForResealWithFallback(modeenv)
+ if err != nil {
+ return err
+ }
+ runModeBootChains, err := runModeBootChains(rbl, bl, modeenv, cmdlines, "")
+ if err != nil {
+ return fmt.Errorf("cannot compose run mode boot chains: %v", err)
+ }
+
+ roleToBlName := map[bootloader.Role]string{
+ bootloader.RoleRecovery: rbl.Name(),
+ bootloader.RoleRunMode: bl.Name(),
+ }
+
+ // reseal the run object
+ pbc := toPredictableBootChains(append(runModeBootChains, recoveryBootChainsForRunKey...))
+
+ neededRun, nextCount, err := isResealNeeded(pbc, bootChainsFileUnder(rootdir), expectReseal)
+ if err != nil {
+ return err
+ }
+ modelParams := make(map[string][]*secboot.SealKeyModelParams)
+ if neededRun {
+ pbcJSON, _ := json.Marshal(pbc)
+ logger.Debugf("resealing (%d) to boot chains: %s", nextCount, pbcJSON)
+
+ params, err := sealKeyModelParams(pbc, roleToBlName)
+ if err != nil {
+ return err
+ }
+ modelParams["run"] = params
+
+ } else {
+ logger.Debugf("reseal not necessary")
+ }
+
+ // reseal the fallback object
+ rpbc := toPredictableBootChains(recoveryBootChains)
+
+ neededRecovery, nextFallbackCount, err := isResealNeeded(rpbc, recoveryBootChainsFileUnder(rootdir), expectReseal)
+ if err != nil {
+ return err
+ }
+ if neededRecovery {
+ rpbcJSON, _ := json.Marshal(rpbc)
+ logger.Debugf("resealing (%d) to recovery boot chains: %s", nextFallbackCount, rpbcJSON)
+
+ params, err := sealKeyModelParams(rpbc, roleToBlName)
+ if err != nil {
+ return err
+ }
+ modelParams["recover"] = params
+ } else {
+ logger.Debugf("fallback reseal not necessary")
+ }
+
+ if err := secboot.ResealKeysNextGeneration(devices, modelParams); err != nil {
+ return err
+ }
+
+ if neededRun {
+ bootChainsPath := bootChainsFileUnder(rootdir)
+ if err := writeBootChains(pbc, bootChainsPath, nextCount); err != nil {
+ return err
+ }
+ }
+ if neededRecovery {
+ recoveryBootChainsPath := recoveryBootChainsFileUnder(rootdir)
+ if err := writeBootChains(rpbc, recoveryBootChainsPath, nextFallbackCount); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
func resealRunObjectKeys(pbc predictableBootChains, authKeyFile string, roleToBlName map[bootloader.Role]string) error {
// get model parameters from bootchains
modelParams, err := sealKeyModelParams(pbc, roleToBlName)
diff --git a/boot/seal_test.go b/boot/seal_test.go
index 052f228c028..1da67ebfe96 100644
--- a/boot/seal_test.go
+++ b/boot/seal_test.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2020 Canonical Ltd
+ * Copyright (C) 2020, 2024 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
@@ -40,7 +40,6 @@ import (
"github.com/snapcore/snapd/kernel/fde"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/secboot"
- "github.com/snapcore/snapd/secboot/keys"
"github.com/snapcore/snapd/seed"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snaptest"
@@ -216,13 +215,9 @@ func (s *sealSuite) TestSealKeyToModeenv(c *C) {
fmt.Sprintf("%s-run-grub-hash-1", runGrubId),
})
- // set encryption key
- myKey := keys.EncryptionKey{}
- myKey2 := keys.EncryptionKey{}
- for i := range myKey {
- myKey[i] = byte(i)
- myKey2[i] = byte(128 + i)
- }
+ // key resetters
+ dataResetter := &secboot.MockKeyResetter{}
+ saveResetter := &secboot.MockKeyResetter{}
// set a mock recovery kernel
readSystemEssentialCalls := 0
@@ -275,16 +270,14 @@ func (s *sealSuite) TestSealKeyToModeenv(c *C) {
// set mock key sealing
sealKeysCalls := 0
- restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error {
+ restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) ([]byte, error) {
c.Assert(provisionCalls, Equals, 1, Commentf("TPM must have been provisioned before"))
sealKeysCalls++
switch sealKeysCalls {
case 1:
// the run object seals only the ubuntu-data key
c.Check(params.TPMPolicyAuthKeyFile, Equals, filepath.Join(boot.InstallHostFDESaveDir, "tpm-policy-auth-key"))
-
- dataKeyFile := filepath.Join(rootdir, "/run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key")
- c.Check(keys, DeepEquals, []secboot.SealKeyRequest{{Key: myKey, KeyName: "ubuntu-data", KeyFile: dataKeyFile}})
+ c.Check(keys, DeepEquals, []secboot.SealKeyRequest{{KeyName: "ubuntu-data", Resetter: dataResetter, Role: "run+recover"}})
if tc.pcrHandleOfKey == secboot.FallbackObjectPCRPolicyCounterHandle {
c.Check(params.PCRPolicyCounterHandle, Equals, secboot.AltRunObjectPCRPolicyCounterHandle)
} else {
@@ -293,14 +286,7 @@ func (s *sealSuite) TestSealKeyToModeenv(c *C) {
case 2:
// the fallback object seals the ubuntu-data and the ubuntu-save keys
c.Check(params.TPMPolicyAuthKeyFile, Equals, "")
-
- dataKeyFile := filepath.Join(rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key")
- saveKeyFile := filepath.Join(rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key")
- if tc.factoryReset {
- // during factory reset we use a different key location
- saveKeyFile = filepath.Join(rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key.factory-reset")
- }
- c.Check(keys, DeepEquals, []secboot.SealKeyRequest{{Key: myKey, KeyName: "ubuntu-data", KeyFile: dataKeyFile}, {Key: myKey2, KeyName: "ubuntu-save", KeyFile: saveKeyFile}})
+ c.Check(keys, DeepEquals, []secboot.SealKeyRequest{{KeyName: "ubuntu-data", SlotName: "default-fallback", Resetter: dataResetter, Role: "recover"}, {KeyName: "ubuntu-save", SlotName: "default-fallback", Resetter: saveResetter, Role: "recover"}})
if tc.pcrHandleOfKey == secboot.FallbackObjectPCRPolicyCounterHandle {
c.Check(params.PCRPolicyCounterHandle, Equals, secboot.AltFallbackObjectPCRPolicyCounterHandle)
} else {
@@ -352,12 +338,12 @@ func (s *sealSuite) TestSealKeyToModeenv(c *C) {
}
c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
- return tc.sealErr
+ return nil, tc.sealErr
})
defer restore()
u := mockUnlocker{}
- err = boot.SealKeyToModeenv(myKey, myKey2, model, modeenv, boot.MockSealKeyToModeenvFlags{
+ err = boot.SealKeyToModeenv(dataResetter, saveResetter, model, modeenv, boot.MockSealKeyToModeenvFlags{
FactoryReset: tc.factoryReset,
StateUnlocker: u.unlocker,
})
@@ -465,7 +451,7 @@ func (s *sealSuite) TestSealKeyToModeenv(c *C) {
// marker
marker := filepath.Join(dirs.SnapFDEDirUnder(filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data")), "sealed-keys")
- c.Check(marker, testutil.FileEquals, "tpm")
+ c.Check(marker, testutil.FileEquals, "next-generation")
}
}
@@ -2152,17 +2138,15 @@ func (s *sealSuite) TestSealToModeenvWithFdeHookHappy(c *C) {
return key, nil
})
defer restore()
- keyToSave := make(map[string][]byte)
restore = boot.MockSecbootSealKeysWithFDESetupHook(func(runHook fde.RunSetupHookFunc, skrs []secboot.SealKeyRequest, params *secboot.SealKeysWithFDESetupHookParams) error {
c.Check(params.Model.Model(), Equals, model.Model())
c.Check(params.AuxKeyFile, Equals, filepath.Join(boot.InstallHostFDESaveDir, "aux-key"))
for _, skr := range skrs {
- out, err := runHook(&fde.SetupRequest{
- Key: skr.Key,
+ _, err := runHook(&fde.SetupRequest{
+ Key: []byte{1, 2, 3, 4},
KeyName: skr.KeyName,
})
c.Assert(err, IsNil)
- keyToSave[skr.KeyFile] = out
}
return nil
})
@@ -2175,32 +2159,23 @@ func (s *sealSuite) TestSealToModeenvWithFdeHookHappy(c *C) {
Grade: string(model.Grade()),
ModelSignKeyID: model.SignKeyID(),
}
- key := keys.EncryptionKey{1, 2, 3, 4}
- saveKey := keys.EncryptionKey{5, 6, 7, 8}
defer boot.MockModeenvLocked()()
- err := boot.SealKeyToModeenv(key, saveKey, model, modeenv, boot.MockSealKeyToModeenvFlags{HasFDESetupHook: true})
+ dataResetter := &secboot.MockKeyResetter{}
+ saveResetter := &secboot.MockKeyResetter{}
+
+ err := boot.SealKeyToModeenv(dataResetter, saveResetter, model, modeenv, boot.MockSealKeyToModeenvFlags{HasFDESetupHook: true})
c.Assert(err, IsNil)
// check that runFDESetupHook was called the expected way
c.Check(runFDESetupHookReqs, DeepEquals, []*fde.SetupRequest{
- {Key: key, KeyName: "ubuntu-data"},
- {Key: key, KeyName: "ubuntu-data"},
- {Key: saveKey, KeyName: "ubuntu-save"},
+ {Key: []byte{1, 2, 3, 4}, KeyName: "ubuntu-data"},
+ {Key: []byte{1, 2, 3, 4}, KeyName: "ubuntu-data"},
+ {Key: []byte{1, 2, 3, 4}, KeyName: "ubuntu-save"},
})
- // check that the sealed keys got written to the expected places
- for i, p := range []string{
- filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
- filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
- filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
- } {
- // Check for a valid platform handle, encrypted payload (base64)
- mockedSealedKey := []byte(fmt.Sprintf("key-%v", strconv.Itoa(i+1)))
- c.Check(keyToSave[p], DeepEquals, mockedSealedKey)
- }
marker := filepath.Join(dirs.SnapFDEDirUnder(filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data")), "sealed-keys")
- c.Check(marker, testutil.FileEquals, "fde-setup-hook")
+ c.Check(marker, testutil.FileEquals, "next-generation")
}
func (s *sealSuite) TestSealToModeenvWithFdeHookSad(c *C) {
@@ -2216,13 +2191,13 @@ func (s *sealSuite) TestSealToModeenvWithFdeHookSad(c *C) {
modeenv := &boot.Modeenv{
RecoverySystem: "20200825",
}
- key := keys.EncryptionKey{1, 2, 3, 4}
- saveKey := keys.EncryptionKey{5, 6, 7, 8}
+ dataResetter := &secboot.MockKeyResetter{}
+ saveResetter := &secboot.MockKeyResetter{}
defer boot.MockModeenvLocked()()
model := boottest.MakeMockUC20Model()
- err := boot.SealKeyToModeenv(key, saveKey, model, modeenv, boot.MockSealKeyToModeenvFlags{HasFDESetupHook: true})
+ err := boot.SealKeyToModeenv(dataResetter, saveResetter, model, modeenv, boot.MockSealKeyToModeenvFlags{HasFDESetupHook: true})
c.Assert(err, ErrorMatches, "hook failed")
marker := filepath.Join(dirs.SnapFDEDirUnder(filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data")), "sealed-keys")
c.Check(marker, testutil.FileAbsent)
diff --git a/boot/setefibootvars_sb.go b/boot/setefibootvars_sb.go
index 65157794605..bc9157d54b9 100644
--- a/boot/setefibootvars_sb.go
+++ b/boot/setefibootvars_sb.go
@@ -78,7 +78,7 @@ func constructLoadOption(description string, assetPath string, optionalData []by
// number should be written. If a different error occurs, returns that error,
// and the returned boot number should be ignored.
func findMatchingBootOption(optionData []byte) (uint16, error) {
- variables, err := efiListVariables()
+ variables, err := efiListVariables(efi.DefaultVarContext)
if err != nil {
return 0, err
}
@@ -101,7 +101,7 @@ func findMatchingBootOption(optionData []byte) (uint16, error) {
}
// Since we never overwrite an existing variable, we can ignore
// variable attributes when reading the variable
- varData, _, err := efiReadVariable(varName, varGUID)
+ varData, _, err := efiReadVariable(efi.DefaultVarContext, varName, varGUID)
if err != nil {
return 0, err
}
@@ -135,7 +135,7 @@ func setEfiBootOptionVariable(loadOptionData []byte) (uint16, error) {
return 0, err
}
varName := fmt.Sprintf("Boot%04X", bootNum)
- err = efiWriteVariable(varName, efi.GlobalVariable, defaultVarAttrs, loadOptionData)
+ err = efiWriteVariable(efi.DefaultVarContext, varName, efi.GlobalVariable, defaultVarAttrs, loadOptionData)
return bootNum, err
}
@@ -144,7 +144,7 @@ func setEfiBootOptionVariable(loadOptionData []byte) (uint16, error) {
// (and removes it from later in the list if it occurs) and writes the
// list as the new BootOrder variable.
func setEfiBootOrderVariable(newBootNum uint16) error {
- origData, attrs, err := efiReadVariable("BootOrder", efi.GlobalVariable)
+ origData, attrs, err := efiReadVariable(efi.DefaultVarContext, "BootOrder", efi.GlobalVariable)
if err == efi.ErrVarNotExist {
attrs = defaultVarAttrs
origData = make([]byte, 0)
@@ -177,7 +177,7 @@ func setEfiBootOrderVariable(newBootNum uint16) error {
copy(newData[2:], origData[:optionOffset])
copy(newData[optionOffset+2:], origData[optionOffset+2:])
}
- return efiWriteVariable("BootOrder", efi.GlobalVariable, attrs, newData)
+ return efiWriteVariable(efi.DefaultVarContext, "BootOrder", efi.GlobalVariable, attrs, newData)
}
// SetEfiBootVariables sets the Boot#### and BootOrder variables for the given
diff --git a/boot/setefibootvars_sb_test.go b/boot/setefibootvars_sb_test.go
index 6c42928277a..54927f2427a 100644
--- a/boot/setefibootvars_sb_test.go
+++ b/boot/setefibootvars_sb_test.go
@@ -22,6 +22,7 @@ package boot_test
import (
"bytes"
+ "context"
"errors"
"fmt"
"io"
@@ -230,7 +231,7 @@ func (s *setEfiBootVarsSuite) TestSetEfiBootOptionVariable(c *C) {
defaultVarAttrs,
},
}
- restore := boot.MockEfiListVariables(func() ([]efi.VariableDescriptor, error) {
+ restore := boot.MockEfiListVariables(func(ctx context.Context) ([]efi.VariableDescriptor, error) {
varDescriptorList := make([]efi.VariableDescriptor, 0, len(fakeVariableData))
for key := range fakeVariableData {
varDescriptorList = append(varDescriptorList, key)
@@ -238,7 +239,7 @@ func (s *setEfiBootVarsSuite) TestSetEfiBootOptionVariable(c *C) {
return varDescriptorList, nil
})
defer restore()
- restore = boot.MockEfiReadVariable(func(name string, guid efi.GUID) ([]byte, efi.VariableAttributes, error) {
+ restore = boot.MockEfiReadVariable(func(ctx context.Context, name string, guid efi.GUID) ([]byte, efi.VariableAttributes, error) {
descriptor := efi.VariableDescriptor{
Name: name,
GUID: guid,
@@ -250,7 +251,7 @@ func (s *setEfiBootVarsSuite) TestSetEfiBootOptionVariable(c *C) {
})
defer restore()
writeChan := make(chan []byte, 1)
- restore = boot.MockEfiWriteVariable(func(name string, guid efi.GUID, attrs efi.VariableAttributes, data []byte) error {
+ restore = boot.MockEfiWriteVariable(func(ctx context.Context, name string, guid efi.GUID, attrs efi.VariableAttributes, data []byte) error {
for varDesc := range fakeVariableData {
if varDesc.Name == name && varDesc.GUID == guid {
return errShouldNotOverwrite
@@ -359,7 +360,7 @@ func (s *setEfiBootVarsSuite) TestMismatchedGuid(c *C) {
defaultVarAttrs,
},
}
- restore := boot.MockEfiListVariables(func() ([]efi.VariableDescriptor, error) {
+ restore := boot.MockEfiListVariables(func(ctx context.Context) ([]efi.VariableDescriptor, error) {
varDescriptorList := make([]efi.VariableDescriptor, 0, len(fakeVariableData))
for key := range fakeVariableData {
varDescriptorList = append(varDescriptorList, key)
@@ -367,7 +368,7 @@ func (s *setEfiBootVarsSuite) TestMismatchedGuid(c *C) {
return varDescriptorList, nil
})
defer restore()
- restore = boot.MockEfiReadVariable(func(name string, guid efi.GUID) ([]byte, efi.VariableAttributes, error) {
+ restore = boot.MockEfiReadVariable(func(ctx context.Context, name string, guid efi.GUID) ([]byte, efi.VariableAttributes, error) {
descriptor := efi.VariableDescriptor{
Name: name,
GUID: guid,
@@ -379,7 +380,7 @@ func (s *setEfiBootVarsSuite) TestMismatchedGuid(c *C) {
})
defer restore()
writeChan := make(chan []byte, 1)
- restore = boot.MockEfiWriteVariable(func(name string, guid efi.GUID, attrs efi.VariableAttributes, data []byte) error {
+ restore = boot.MockEfiWriteVariable(func(ctx context.Context, name string, guid efi.GUID, attrs efi.VariableAttributes, data []byte) error {
for varDesc := range fakeVariableData {
if varDesc.Name == name && varDesc.GUID == guid {
return errShouldNotOverwrite
@@ -455,7 +456,7 @@ func (s *setEfiBootVarsSuite) TestSetEfiBootOptionVarAttrs(c *C) {
grubAttrs,
},
}
- restore := boot.MockEfiListVariables(func() ([]efi.VariableDescriptor, error) {
+ restore := boot.MockEfiListVariables(func(ctx context.Context) ([]efi.VariableDescriptor, error) {
varDescriptorList := make([]efi.VariableDescriptor, 0, len(fakeVariableData))
for key := range fakeVariableData {
varDescriptorList = append(varDescriptorList, key)
@@ -463,7 +464,7 @@ func (s *setEfiBootVarsSuite) TestSetEfiBootOptionVarAttrs(c *C) {
return varDescriptorList, nil
})
defer restore()
- restore = boot.MockEfiReadVariable(func(name string, guid efi.GUID) ([]byte, efi.VariableAttributes, error) {
+ restore = boot.MockEfiReadVariable(func(ctx context.Context, name string, guid efi.GUID) ([]byte, efi.VariableAttributes, error) {
descriptor := efi.VariableDescriptor{
Name: name,
GUID: guid,
@@ -474,7 +475,7 @@ func (s *setEfiBootVarsSuite) TestSetEfiBootOptionVarAttrs(c *C) {
return nil, 0, efi.ErrVarNotExist
})
defer restore()
- restore = boot.MockEfiWriteVariable(func(name string, guid efi.GUID, attrs efi.VariableAttributes, data []byte) error {
+ restore = boot.MockEfiWriteVariable(func(ctx context.Context, name string, guid efi.GUID, attrs efi.VariableAttributes, data []byte) error {
for varDesc := range fakeVariableData {
if varDesc.Name == name && varDesc.GUID == guid {
return errShouldNotOverwrite
@@ -528,7 +529,7 @@ func (s *setEfiBootVarsSuite) TestOutOfBootNumbers(c *C) {
fakeVariableData := make(map[efi.VariableDescriptor]*varDataAttrs)
- restore := boot.MockEfiListVariables(func() ([]efi.VariableDescriptor, error) {
+ restore := boot.MockEfiListVariables(func(ctx context.Context) ([]efi.VariableDescriptor, error) {
varDescriptorList := make([]efi.VariableDescriptor, 0, len(fakeVariableData))
for key := range fakeVariableData {
varDescriptorList = append(varDescriptorList, key)
@@ -536,7 +537,7 @@ func (s *setEfiBootVarsSuite) TestOutOfBootNumbers(c *C) {
return varDescriptorList, nil
})
defer restore()
- restore = boot.MockEfiReadVariable(func(name string, guid efi.GUID) ([]byte, efi.VariableAttributes, error) {
+ restore = boot.MockEfiReadVariable(func(ctx context.Context, name string, guid efi.GUID) ([]byte, efi.VariableAttributes, error) {
descriptor := efi.VariableDescriptor{
Name: name,
GUID: guid,
@@ -548,7 +549,7 @@ func (s *setEfiBootVarsSuite) TestOutOfBootNumbers(c *C) {
})
defer restore()
writeChan := make(chan []byte, 1)
- restore = boot.MockEfiWriteVariable(func(name string, guid efi.GUID, attrs efi.VariableAttributes, data []byte) error {
+ restore = boot.MockEfiWriteVariable(func(ctx context.Context, name string, guid efi.GUID, attrs efi.VariableAttributes, data []byte) error {
for varDesc := range fakeVariableData {
if varDesc.Name == name && varDesc.GUID == guid {
return errShouldNotOverwrite
@@ -682,7 +683,7 @@ func (s *setEfiBootVarsSuite) TestSetEfiBootOrderVariable(c *C) {
},
}
readChan := make(chan []byte, 1)
- restore := boot.MockEfiReadVariable(func(name string, guid efi.GUID) ([]byte, efi.VariableAttributes, error) {
+ restore := boot.MockEfiReadVariable(func(ctx context.Context, name string, guid efi.GUID) ([]byte, efi.VariableAttributes, error) {
value := <-readChan
if value == nil {
return nil, 0, efi.ErrVarNotExist
@@ -691,7 +692,7 @@ func (s *setEfiBootVarsSuite) TestSetEfiBootOrderVariable(c *C) {
})
defer restore()
writeChan := make(chan []byte, 1)
- restore = boot.MockEfiWriteVariable(func(name string, guid efi.GUID, attrs efi.VariableAttributes, data []byte) error {
+ restore = boot.MockEfiWriteVariable(func(ctx context.Context, name string, guid efi.GUID, attrs efi.VariableAttributes, data []byte) error {
c.Assert(name, Equals, "BootOrder")
c.Assert(guid, Equals, efi.GlobalVariable)
c.Assert(attrs, Equals, defaultVarAttrs)
@@ -728,12 +729,12 @@ func (s *setEfiBootVarsSuite) TestSetEfiBootOrderVarAttrs(c *C) {
defaultVarAttrs | efi.AttributeAuthenticatedWriteAccess,
}
attrReadChan := make(chan efi.VariableAttributes, 1)
- restore := boot.MockEfiReadVariable(func(name string, guid efi.GUID) ([]byte, efi.VariableAttributes, error) {
+ restore := boot.MockEfiReadVariable(func(ctx context.Context, name string, guid efi.GUID) ([]byte, efi.VariableAttributes, error) {
return initialBootOrder, <-attrReadChan, nil
})
defer restore()
attrWriteChan := make(chan efi.VariableAttributes, 1)
- restore = boot.MockEfiWriteVariable(func(name string, guid efi.GUID, attrs efi.VariableAttributes, data []byte) error {
+ restore = boot.MockEfiWriteVariable(func(ctx context.Context, name string, guid efi.GUID, attrs efi.VariableAttributes, data []byte) error {
c.Assert(name, Equals, "BootOrder")
c.Assert(guid, Equals, efi.GlobalVariable)
c.Assert(data, DeepEquals, finalBootOrder)
@@ -780,7 +781,7 @@ func (s *setEfiBootVarsSuite) TestSetEfiBootVariables(c *C) {
},
}
- defer boot.MockEfiReadVariable(func(name string, guid efi.GUID) ([]byte, efi.VariableAttributes, error) {
+ defer boot.MockEfiReadVariable(func(ctx context.Context, name string, guid efi.GUID) ([]byte, efi.VariableAttributes, error) {
descriptor := efi.VariableDescriptor{
Name: name,
GUID: guid,
@@ -791,7 +792,7 @@ func (s *setEfiBootVarsSuite) TestSetEfiBootVariables(c *C) {
return nil, 0, efi.ErrVarNotExist
})()
- defer boot.MockEfiListVariables(func() ([]efi.VariableDescriptor, error) {
+ defer boot.MockEfiListVariables(func(ctx context.Context) ([]efi.VariableDescriptor, error) {
varDescriptorList := make([]efi.VariableDescriptor, 0, len(fakeVariableData))
for key := range fakeVariableData {
varDescriptorList = append(varDescriptorList, key)
@@ -800,7 +801,7 @@ func (s *setEfiBootVarsSuite) TestSetEfiBootVariables(c *C) {
})()
written := 0
- defer boot.MockEfiWriteVariable(func(name string, guid efi.GUID, attrs efi.VariableAttributes, data []byte) error {
+ defer boot.MockEfiWriteVariable(func(ctx context.Context, name string, guid efi.GUID, attrs efi.VariableAttributes, data []byte) error {
written += 1
if name == "BootOrder" && guid == efi.GlobalVariable {
return nil
@@ -850,7 +851,7 @@ func (s *setEfiBootVarsSuite) TestSetEfiBootVariablesConstructError(c *C) {
},
}
- defer boot.MockEfiReadVariable(func(name string, guid efi.GUID) ([]byte, efi.VariableAttributes, error) {
+ defer boot.MockEfiReadVariable(func(ctx context.Context, name string, guid efi.GUID) ([]byte, efi.VariableAttributes, error) {
descriptor := efi.VariableDescriptor{
Name: name,
GUID: guid,
@@ -861,7 +862,7 @@ func (s *setEfiBootVarsSuite) TestSetEfiBootVariablesConstructError(c *C) {
return nil, 0, efi.ErrVarNotExist
})()
- defer boot.MockEfiListVariables(func() ([]efi.VariableDescriptor, error) {
+ defer boot.MockEfiListVariables(func(ctx context.Context) ([]efi.VariableDescriptor, error) {
varDescriptorList := make([]efi.VariableDescriptor, 0, len(fakeVariableData))
for key := range fakeVariableData {
varDescriptorList = append(varDescriptorList, key)
@@ -869,7 +870,7 @@ func (s *setEfiBootVarsSuite) TestSetEfiBootVariablesConstructError(c *C) {
return varDescriptorList, nil
})()
- defer boot.MockEfiWriteVariable(func(name string, guid efi.GUID, attrs efi.VariableAttributes, data []byte) error {
+ defer boot.MockEfiWriteVariable(func(ctx context.Context, name string, guid efi.GUID, attrs efi.VariableAttributes, data []byte) error {
c.Fatalf("Not execpted to write variables")
return nil
})()
@@ -911,7 +912,7 @@ func (s *setEfiBootVarsSuite) TestSetEfiBootVariablesErrorSetVariable(c *C) {
},
}
- defer boot.MockEfiReadVariable(func(name string, guid efi.GUID) ([]byte, efi.VariableAttributes, error) {
+ defer boot.MockEfiReadVariable(func(ctx context.Context, name string, guid efi.GUID) ([]byte, efi.VariableAttributes, error) {
descriptor := efi.VariableDescriptor{
Name: name,
GUID: guid,
@@ -922,7 +923,7 @@ func (s *setEfiBootVarsSuite) TestSetEfiBootVariablesErrorSetVariable(c *C) {
return nil, 0, efi.ErrVarNotExist
})()
- defer boot.MockEfiListVariables(func() ([]efi.VariableDescriptor, error) {
+ defer boot.MockEfiListVariables(func(ctx context.Context) ([]efi.VariableDescriptor, error) {
varDescriptorList := make([]efi.VariableDescriptor, 0, len(fakeVariableData))
for key := range fakeVariableData {
varDescriptorList = append(varDescriptorList, key)
@@ -931,7 +932,7 @@ func (s *setEfiBootVarsSuite) TestSetEfiBootVariablesErrorSetVariable(c *C) {
})()
written := 0
- defer boot.MockEfiWriteVariable(func(name string, guid efi.GUID, attrs efi.VariableAttributes, data []byte) error {
+ defer boot.MockEfiWriteVariable(func(ctx context.Context, name string, guid efi.GUID, attrs efi.VariableAttributes, data []byte) error {
written += 1
return fmt.Errorf(`INJECT ERROR`)
})()
diff --git a/cmd/snap-bootstrap/cmd_initramfs_mounts.go b/cmd/snap-bootstrap/cmd_initramfs_mounts.go
index 170cf93ed1f..cf92024f9db 100644
--- a/cmd/snap-bootstrap/cmd_initramfs_mounts.go
+++ b/cmd/snap-bootstrap/cmd_initramfs_mounts.go
@@ -99,7 +99,7 @@ var (
secbootMeasureSnapSystemEpochWhenPossible func() error
secbootMeasureSnapModelWhenPossible func(findModel func() (*asserts.Model, error)) error
secbootUnlockVolumeUsingSealedKeyIfEncrypted func(disk disks.Disk, name string, encryptionKeyFile string, opts *secboot.UnlockVolumeUsingSealedKeyOptions) (secboot.UnlockResult, error)
- secbootUnlockEncryptedVolumeUsingKey func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error)
+ secbootUnlockEncryptedVolumeUsingPlatformKey func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error)
secbootLockSealedKeys func() error
@@ -357,7 +357,7 @@ func doInstall(mst *initramfsMountsState, model *asserts.Model, sysSnaps map[sna
}
if useEncryption {
- if err := install.PrepareEncryptedSystemData(model, installedSystem.KeyForRole, trustedInstallObserver); err != nil {
+ if err := install.PrepareEncryptedSystemData(model, installedSystem.ResetterForRole, trustedInstallObserver); err != nil {
return err
}
}
@@ -719,6 +719,8 @@ type recoverModeStateMachine struct {
// state for tracking what happens as we progress through degraded mode of
// recovery
degradedState *recoverDegradedState
+
+ bootMode string
}
func (m *recoverModeStateMachine) whichModel() (*asserts.Model, error) {
@@ -966,7 +968,7 @@ func (m *recoverModeStateMachine) setUnlockStateWithFallbackKey(partName string,
return nil
}
-func newRecoverModeStateMachine(model *asserts.Model, disk disks.Disk, allowFallback bool) *recoverModeStateMachine {
+func newRecoverModeStateMachine(model *asserts.Model, disk disks.Disk, allowFallback bool, bootMode string) *recoverModeStateMachine {
m := &recoverModeStateMachine{
model: model,
disk: disk,
@@ -974,6 +976,7 @@ func newRecoverModeStateMachine(model *asserts.Model, disk disks.Disk, allowFall
ErrorLog: []string{},
},
noFallback: !allowFallback,
+ bootMode: bootMode,
}
// first step is to mount ubuntu-boot to check for run mode keys to unlock
// ubuntu-data
@@ -1108,6 +1111,7 @@ func (m *recoverModeStateMachine) unlockDataRunKey() (stateFunc, error) {
// recovery key after we first try the fallback object
AllowRecoveryKey: false,
WhichModel: m.whichModel,
+ BootMode: m.bootMode,
}
unlockRes, unlockErr := secbootUnlockVolumeUsingSealedKeyIfEncrypted(m.disk, "ubuntu-data", runModeKey, unlockOpts)
if err := m.setUnlockStateWithRunKey("ubuntu-data", unlockRes, unlockErr); err != nil {
@@ -1148,6 +1152,7 @@ func (m *recoverModeStateMachine) unlockDataFallbackKey() (stateFunc, error) {
// to unlock data
AllowRecoveryKey: true,
WhichModel: m.whichModel,
+ BootMode: m.bootMode,
}
// TODO: this prompts for a recovery key
// TODO: we should somehow customize the prompt to mention what key we need
@@ -1210,7 +1215,7 @@ func (m *recoverModeStateMachine) unlockEncryptedSaveRunKey() (stateFunc, error)
return m.unlockEncryptedSaveFallbackKey, nil
}
- unlockRes, unlockErr := secbootUnlockEncryptedVolumeUsingKey(m.disk, "ubuntu-save", key)
+ unlockRes, unlockErr := secbootUnlockEncryptedVolumeUsingPlatformKey(m.disk, "ubuntu-save", key)
if err := m.setUnlockStateWithRunKey("ubuntu-save", unlockRes, unlockErr); err != nil {
return nil, err
}
@@ -1282,6 +1287,7 @@ func (m *recoverModeStateMachine) unlockEncryptedSaveFallbackKey() (stateFunc, e
// to unlock save
AllowRecoveryKey: true,
WhichModel: m.whichModel,
+ BootMode: m.bootMode,
}
saveFallbackKey := device.FallbackSaveSealedKeyUnder(boot.InitramfsSeedEncryptionKeyDir)
// TODO: this prompts again for a recover key, but really this is the
@@ -1364,7 +1370,7 @@ func generateMountsModeRecover(mst *initramfsMountsState) error {
machine, err := func() (machine *recoverModeStateMachine, err error) {
// first state to execute is to unlock ubuntu-data with the run key
- machine = newRecoverModeStateMachine(model, disk, allowFallback)
+ machine = newRecoverModeStateMachine(model, disk, allowFallback, "recover")
for {
finished, err := machine.execute()
// TODO: consider whether certain errors are fatal or not
@@ -1481,7 +1487,7 @@ func generateMountsModeFactoryReset(mst *initramfsMountsState) error {
// invoked)
machine, err := func() (machine *recoverModeStateMachine, err error) {
allowFallback := true
- machine = newRecoverModeStateMachine(model, disk, allowFallback)
+ machine = newRecoverModeStateMachine(model, disk, allowFallback, "factory-reset")
// start from looking up encrypted ubuntu-save and unlocking with the fallback key
machine.current = machine.unlockMaybeEncryptedAloneSaveFallbackKey
for {
@@ -1799,7 +1805,7 @@ func maybeMountSave(disk disks.Disk, rootdir string, encrypted bool, mountOpts *
if err != nil {
return true, err
}
- unlockRes, err := secbootUnlockEncryptedVolumeUsingKey(disk, "ubuntu-save", key)
+ unlockRes, err := secbootUnlockEncryptedVolumeUsingPlatformKey(disk, "ubuntu-save", key)
if err != nil {
return true, fmt.Errorf("cannot unlock ubuntu-save volume: %v", err)
}
@@ -1980,6 +1986,7 @@ func generateMountsModeRun(mst *initramfsMountsState) error {
opts := &secboot.UnlockVolumeUsingSealedKeyOptions{
AllowRecoveryKey: true,
WhichModel: mst.UnverifiedBootModel,
+ BootMode: "run",
}
unlockRes, err := secbootUnlockVolumeUsingSealedKeyIfEncrypted(disk, "ubuntu-data", runModeKey, opts)
if err != nil {
diff --git a/cmd/snap-bootstrap/cmd_initramfs_mounts_nosecboot.go b/cmd/snap-bootstrap/cmd_initramfs_mounts_nosecboot.go
index 640063d1bb1..bbe3cc7f7f2 100644
--- a/cmd/snap-bootstrap/cmd_initramfs_mounts_nosecboot.go
+++ b/cmd/snap-bootstrap/cmd_initramfs_mounts_nosecboot.go
@@ -45,7 +45,7 @@ func init() {
secbootUnlockVolumeUsingSealedKeyIfEncrypted = func(disk disks.Disk, name string, sealedEncryptionKeyFile string, opts *secboot.UnlockVolumeUsingSealedKeyOptions) (secboot.UnlockResult, error) {
return secboot.UnlockResult{}, errNotImplemented
}
- secbootUnlockEncryptedVolumeUsingKey = func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ secbootUnlockEncryptedVolumeUsingPlatformKey = func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
return secboot.UnlockResult{}, errNotImplemented
}
diff --git a/cmd/snap-bootstrap/cmd_initramfs_mounts_secboot.go b/cmd/snap-bootstrap/cmd_initramfs_mounts_secboot.go
index 1a30006bb28..b8014db28c8 100644
--- a/cmd/snap-bootstrap/cmd_initramfs_mounts_secboot.go
+++ b/cmd/snap-bootstrap/cmd_initramfs_mounts_secboot.go
@@ -29,6 +29,6 @@ func init() {
secbootMeasureSnapSystemEpochWhenPossible = secboot.MeasureSnapSystemEpochWhenPossible
secbootMeasureSnapModelWhenPossible = secboot.MeasureSnapModelWhenPossible
secbootUnlockVolumeUsingSealedKeyIfEncrypted = secboot.UnlockVolumeUsingSealedKeyIfEncrypted
- secbootUnlockEncryptedVolumeUsingKey = secboot.UnlockEncryptedVolumeUsingKey
+ secbootUnlockEncryptedVolumeUsingPlatformKey = secboot.UnlockEncryptedVolumeUsingPlatformKey
secbootLockSealedKeys = secboot.LockSealedKeys
}
diff --git a/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go b/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go
index f2f396f8091..99dd708169a 100644
--- a/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go
+++ b/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go
@@ -46,7 +46,6 @@ import (
"github.com/snapcore/snapd/osutil/disks"
"github.com/snapcore/snapd/osutil/kcmdline"
"github.com/snapcore/snapd/secboot"
- "github.com/snapcore/snapd/secboot/keys"
"github.com/snapcore/snapd/seed"
"github.com/snapcore/snapd/seed/seedtest"
"github.com/snapcore/snapd/snap"
@@ -2369,7 +2368,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRunModeEncryptedDataHappy(c *C
s.mockUbuntuSaveMarker(c, boot.InitramfsUbuntuSaveDir, "marker")
saveActivated := false
- restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ restore = main.MockSecbootUnlockEncryptedVolumeUsingPlatformKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
c.Check(dataActivated, Equals, true, Commentf("ubuntu-data not activated yet"))
saveActivated = true
c.Assert(name, Equals, "ubuntu-save")
@@ -2587,7 +2586,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRunModeEncryptedDataUnhappyNoS
// the test does not mock ubuntu-save.key, the secboot helper for
// opening a volume using the key should not be called
- restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ restore = main.MockSecbootUnlockEncryptedVolumeUsingPlatformKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
c.Fatal("unexpected call")
return secboot.UnlockResult{}, fmt.Errorf("unexpected call")
})
@@ -2665,7 +2664,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRunModeEncryptedDataUnhappyUnl
defer restore()
s.mockUbuntuSaveKeyAndMarker(c, filepath.Join(dirs.GlobalRootDir, "/run/mnt/data/system-data"), "foo", "")
- restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ restore = main.MockSecbootUnlockEncryptedVolumeUsingPlatformKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
c.Check(dataActivated, Equals, true, Commentf("ubuntu-data not yet activated"))
return foundEncrypted("ubuntu-save"), fmt.Errorf("ubuntu-save unlock fail")
})
@@ -3693,7 +3692,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeHappyEncrypted(c *C
s.mockUbuntuSaveMarker(c, boot.InitramfsUbuntuSaveDir, "marker")
saveActivated := false
- restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ restore = main.MockSecbootUnlockEncryptedVolumeUsingPlatformKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
c.Check(dataActivated, Equals, true, Commentf("ubuntu-data not activated yet"))
encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc")
c.Assert(err, IsNil)
@@ -3852,7 +3851,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedDegradedDa
s.mockUbuntuSaveKeyAndMarker(c, filepath.Join(dirs.GlobalRootDir, "/run/mnt/host/ubuntu-data/system-data"), "foo", "marker")
s.mockUbuntuSaveMarker(c, boot.InitramfsUbuntuSaveDir, "marker")
- restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ restore = main.MockSecbootUnlockEncryptedVolumeUsingPlatformKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
c.Check(dataActivated, Equals, true, Commentf("ubuntu-data not activated yet"))
encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc")
c.Assert(err, IsNil)
@@ -4030,7 +4029,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedDegradedSa
s.mockUbuntuSaveKeyAndMarker(c, filepath.Join(dirs.GlobalRootDir, "/run/mnt/host/ubuntu-data/system-data"), "foo", "marker")
s.mockUbuntuSaveMarker(c, boot.InitramfsUbuntuSaveDir, "marker")
- restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ restore = main.MockSecbootUnlockEncryptedVolumeUsingPlatformKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
c.Check(dataActivated, Equals, true, Commentf("ubuntu-data not activated yet"))
encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc")
c.Assert(err, IsNil)
@@ -4201,7 +4200,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedDegradedAb
s.mockUbuntuSaveKeyAndMarker(c, filepath.Join(dirs.GlobalRootDir, "/run/mnt/host/ubuntu-data/system-data"), "foo", "marker")
s.mockUbuntuSaveMarker(c, boot.InitramfsUbuntuSaveDir, "marker")
- restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ restore = main.MockSecbootUnlockEncryptedVolumeUsingPlatformKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
c.Check(dataActivated, Equals, true, Commentf("ubuntu-data not activated yet"))
encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc")
c.Assert(err, IsNil)
@@ -4364,7 +4363,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedDegradedAb
s.mockUbuntuSaveKeyAndMarker(c, filepath.Join(dirs.GlobalRootDir, "/run/mnt/host/ubuntu-data/system-data"), "foo", "marker")
s.mockUbuntuSaveMarker(c, boot.InitramfsUbuntuSaveDir, "marker")
- restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ restore = main.MockSecbootUnlockEncryptedVolumeUsingPlatformKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
c.Check(dataActivated, Equals, true, Commentf("ubuntu-data not activated yet"))
encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc")
c.Assert(err, IsNil)
@@ -4536,7 +4535,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedDegradedDa
s.mockUbuntuSaveKeyAndMarker(c, filepath.Join(dirs.GlobalRootDir, "/run/mnt/host/ubuntu-data/system-data"), "foo", "")
- restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ restore = main.MockSecbootUnlockEncryptedVolumeUsingPlatformKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
// nothing can call this function in the tested scenario
c.Fatalf("unexpected call")
return secboot.UnlockResult{}, fmt.Errorf("unexpected call")
@@ -4726,7 +4725,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeDegradedAbsentDataU
s.mockUbuntuSaveKeyAndMarker(c, filepath.Join(dirs.GlobalRootDir, "/run/mnt/host/ubuntu-data/system-data"), "foo", "")
- restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ restore = main.MockSecbootUnlockEncryptedVolumeUsingPlatformKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
// nothing can call this function in the tested scenario
c.Fatalf("unexpected call")
return secboot.UnlockResult{}, fmt.Errorf("unexpected call")
@@ -4917,7 +4916,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeDegradedUnencrypted
s.mockUbuntuSaveKeyAndMarker(c, filepath.Join(dirs.GlobalRootDir, "/run/mnt/host/ubuntu-data/system-data"), "foo", "")
- restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ restore = main.MockSecbootUnlockEncryptedVolumeUsingPlatformKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
// nothing can call this function in the tested scenario
c.Fatalf("unexpected call")
return secboot.UnlockResult{}, fmt.Errorf("unexpected call")
@@ -5063,7 +5062,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeDegradedEncryptedDa
s.mockUbuntuSaveKeyAndMarker(c, filepath.Join(dirs.GlobalRootDir, "/run/mnt/host/ubuntu-data/system-data"), "foo", "")
- restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ restore = main.MockSecbootUnlockEncryptedVolumeUsingPlatformKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
// nothing can call this function in the tested scenario
c.Fatalf("unexpected call")
return secboot.UnlockResult{}, fmt.Errorf("unexpected call")
@@ -5187,7 +5186,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeUnencryptedDataUnen
s.mockUbuntuSaveKeyAndMarker(c, filepath.Join(dirs.GlobalRootDir, "/run/mnt/host/ubuntu-data/system-data"), "foo", "")
- restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ restore = main.MockSecbootUnlockEncryptedVolumeUsingPlatformKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
// nothing can call this function in the tested scenario
c.Fatalf("unexpected call")
return secboot.UnlockResult{}, fmt.Errorf("unexpected call")
@@ -5326,7 +5325,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedDegradedAb
s.mockUbuntuSaveKeyAndMarker(c, filepath.Join(dirs.GlobalRootDir, "/run/mnt/host/ubuntu-data/system-data"), "foo", "")
- restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ restore = main.MockSecbootUnlockEncryptedVolumeUsingPlatformKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
// nothing can call this function in the tested scenario
c.Fatalf("unexpected call")
return secboot.UnlockResult{}, fmt.Errorf("unexpected call")
@@ -5532,7 +5531,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedDegradedDa
s.mockUbuntuSaveKeyAndMarker(c, filepath.Join(dirs.GlobalRootDir, "/run/mnt/host/ubuntu-data/system-data"), "foo", "")
- restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ restore = main.MockSecbootUnlockEncryptedVolumeUsingPlatformKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
// nothing can call this function in the tested scenario
c.Fatalf("unexpected call")
return secboot.UnlockResult{}, fmt.Errorf("unexpected call")
@@ -5697,7 +5696,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedMismatched
s.mockUbuntuSaveMarker(c, boot.InitramfsUbuntuSaveDir, "marker")
saveActivated := false
- restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ restore = main.MockSecbootUnlockEncryptedVolumeUsingPlatformKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
c.Check(dataActivated, Equals, true, Commentf("ubuntu-data not activated yet"))
encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc")
c.Assert(err, IsNil)
@@ -5911,7 +5910,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedAttackerFS
s.mockUbuntuSaveKeyAndMarker(c, filepath.Join(dirs.GlobalRootDir, "/run/mnt/host/ubuntu-data/system-data"), "foo", "marker")
s.mockUbuntuSaveMarker(c, boot.InitramfsUbuntuSaveDir, "marker")
- restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ restore = main.MockSecbootUnlockEncryptedVolumeUsingPlatformKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc")
c.Assert(err, IsNil)
c.Assert(encDevPartUUID, Equals, "ubuntu-save-enc-partuuid")
@@ -6460,7 +6459,7 @@ func (s *initramfsMountsSuite) testInitramfsMountsTryRecoveryDegraded(c *C, expe
})
defer restore()
unlockVolumeWithKeyCalls := 0
- restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ restore = main.MockSecbootUnlockEncryptedVolumeUsingPlatformKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
unlockVolumeWithKeyCalls++
switch unlockVolumeWithKeyCalls {
case 1:
@@ -6761,7 +6760,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsFactoryResetModeHappyEncrypted
})
defer restore()
- restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ restore = main.MockSecbootUnlockEncryptedVolumeUsingPlatformKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
c.Errorf("unexpected call")
return secboot.UnlockResult{}, fmt.Errorf("unexpected call")
})
@@ -6884,7 +6883,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsFactoryResetModeHappyUnencrypt
return secboot.UnlockResult{}, fmt.Errorf("unexpected call")
})
defer restore()
- restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ restore = main.MockSecbootUnlockEncryptedVolumeUsingPlatformKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
c.Errorf("unexpected call")
return secboot.UnlockResult{}, fmt.Errorf("unexpected call")
})
@@ -6970,7 +6969,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsFactoryResetModeHappyUnencrypt
return secboot.UnlockResult{}, fmt.Errorf("unexpected call")
})
defer restore()
- restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ restore = main.MockSecbootUnlockEncryptedVolumeUsingPlatformKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
c.Errorf("unexpected call")
return secboot.UnlockResult{}, fmt.Errorf("unexpected call")
})
@@ -7057,7 +7056,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsFactoryResetModeUnhappyUnlockE
})
defer restore()
- restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ restore = main.MockSecbootUnlockEncryptedVolumeUsingPlatformKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
c.Errorf("unexpected call")
return secboot.UnlockResult{}, fmt.Errorf("unexpected call")
})
@@ -7639,7 +7638,7 @@ func (s *initramfsClassicMountsSuite) TestInitramfsMountsRunModeEncryptedDataHap
s.mockUbuntuSaveMarker(c, boot.InitramfsUbuntuSaveDir, "marker")
saveActivated := false
- restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
+ restore = main.MockSecbootUnlockEncryptedVolumeUsingPlatformKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) {
c.Check(dataActivated, Equals, true, Commentf("ubuntu-data not activated yet"))
saveActivated = true
c.Assert(name, Equals, "ubuntu-save")
@@ -8087,7 +8086,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsInstallAndRunMissingFdeSetup(c
type MockObserver struct {
BootLoaderSupportsEfiVariablesFunc func() bool
ObserveExistingTrustedRecoveryAssetsFunc func(recoveryRootDir string) error
- ChosenEncryptionKeysFunc func(key, saveKey keys.EncryptionKey)
+ ChosenEncryptionKeysFunc func(resetter, saveResetter secboot.KeyResetter)
UpdateBootEntryFunc func() error
ObserveFunc func(op gadget.ContentOperation, partRole, root, relativeTarget string, data *gadget.ContentChange) (gadget.ContentChangeAction, error)
}
@@ -8100,8 +8099,8 @@ func (m *MockObserver) ObserveExistingTrustedRecoveryAssets(recoveryRootDir stri
return m.ObserveExistingTrustedRecoveryAssetsFunc(recoveryRootDir)
}
-func (m *MockObserver) ChosenEncryptionKeys(key, saveKey keys.EncryptionKey) {
- m.ChosenEncryptionKeysFunc(key, saveKey)
+func (m *MockObserver) ChosenEncryptionKeys(resetter, saveResetter secboot.KeyResetter) {
+ m.ChosenEncryptionKeysFunc(resetter, saveResetter)
}
func (m *MockObserver) UpdateBootEntry() error {
@@ -8174,9 +8173,6 @@ echo '{"features":[]}'
writeGadget(c, "ubuntu-seed", "system-seed", "")
- dataKey := keys.EncryptionKey{'d', 'a', 't', 'a', 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
- saveKey := keys.EncryptionKey{'s', 'a', 'v', 'e', 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
-
gadgetInstallCalled := false
restoreGadgetInstall := main.MockGadgetInstallRun(func(model gadget.Model, gadgetRoot string, kernelSnapInfo *gadgetInstall.KernelSnapInfo, bootDevice string, options gadgetInstall.Options, observer gadget.ContentObserver, perfTimings timings.Measurer) (*gadgetInstall.InstalledSystemSideData, error) {
gadgetInstallCalled = true
@@ -8188,11 +8184,11 @@ echo '{"features":[]}'
c.Assert(gadgetRoot, Equals, filepath.Join(boot.InitramfsRunMntDir, "gadget"))
c.Assert(kernelSnapInfo.MountPoint, Equals, filepath.Join(boot.InitramfsRunMntDir, "kernel"))
- keyForRole := map[string]keys.EncryptionKey{
- gadget.SystemData: dataKey,
- gadget.SystemSave: saveKey,
+ resetterForRole := map[string]secboot.KeyResetter{
+ gadget.SystemData: &secboot.MockKeyResetter{},
+ gadget.SystemSave: &secboot.MockKeyResetter{},
}
- return &gadgetInstall.InstalledSystemSideData{KeyForRole: keyForRole}, nil
+ return &gadgetInstall.InstalledSystemSideData{ResetterForRole: resetterForRole}, nil
})
defer restoreGadgetInstall()
@@ -8240,7 +8236,7 @@ echo '{"features":[]}'
observeExistingTrustedRecoveryAssetsCalled += 1
return nil
},
- ChosenEncryptionKeysFunc: func(key, saveKey keys.EncryptionKey) {
+ ChosenEncryptionKeysFunc: func(resetter, saveResetter secboot.KeyResetter) {
},
UpdateBootEntryFunc: func() error {
return nil
@@ -8291,7 +8287,7 @@ echo '{"features":[]}'
c.Assert(makeRunnableCalled, Equals, true)
c.Assert(gadgetInstallCalled, Equals, true)
c.Assert(nextBooEnsured, Equals, true)
- c.Check(observeExistingTrustedRecoveryAssetsCalled, Equals, 1)
+ c.Check(observeExistingTrustedRecoveryAssetsCalled, Equals, 2)
}
func (s *initramfsMountsSuite) TestInitramfsMountsInstallAndRunFdeSetupNotPresent(c *C) {
@@ -8397,7 +8393,7 @@ func (s *initramfsMountsSuite) TestInitramfsMountsInstallAndRunFdeSetupNotPresen
observeExistingTrustedRecoveryAssetsCalled += 1
return nil
},
- ChosenEncryptionKeysFunc: func(key, saveKey keys.EncryptionKey) {
+ ChosenEncryptionKeysFunc: func(resetter, saveResetter secboot.KeyResetter) {
},
UpdateBootEntryFunc: func() error {
return nil
diff --git a/cmd/snap-bootstrap/export_test.go b/cmd/snap-bootstrap/export_test.go
index 1ff22759802..80f01f69ccf 100644
--- a/cmd/snap-bootstrap/export_test.go
+++ b/cmd/snap-bootstrap/export_test.go
@@ -129,11 +129,11 @@ func MockSecbootUnlockVolumeUsingSealedKeyIfEncrypted(f func(disk disks.Disk, na
}
}
-func MockSecbootUnlockEncryptedVolumeUsingKey(f func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error)) (restore func()) {
- old := secbootUnlockEncryptedVolumeUsingKey
- secbootUnlockEncryptedVolumeUsingKey = f
+func MockSecbootUnlockEncryptedVolumeUsingPlatformKey(f func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error)) (restore func()) {
+ old := secbootUnlockEncryptedVolumeUsingPlatformKey
+ secbootUnlockEncryptedVolumeUsingPlatformKey = f
return func() {
- secbootUnlockEncryptedVolumeUsingKey = old
+ secbootUnlockEncryptedVolumeUsingPlatformKey = old
}
}
diff --git a/data/systemd/snapd.service.in b/data/systemd/snapd.service.in
index 5e568d3c7eb..d5164bc780a 100644
--- a/data/systemd/snapd.service.in
+++ b/data/systemd/snapd.service.in
@@ -27,6 +27,8 @@ NotifyAccess=all
SuccessExitStatus=42
RestartPreventExitStatus=42
KillMode=process
+#TODO: test with "shared"
+KeyringMode=inherit
[Install]
WantedBy=multi-user.target
diff --git a/gadget/device/encrypt.go b/gadget/device/encrypt.go
index 2c399edd32d..c19b07ddf4f 100644
--- a/gadget/device/encrypt.go
+++ b/gadget/device/encrypt.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2022 Canonical Ltd
+ * Copyright (C) 2022-2024 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
@@ -108,9 +108,10 @@ var ErrNoSealedKeys = errors.New("no sealed keys")
type SealingMethod string
const (
- SealingMethodLegacyTPM = SealingMethod("")
- SealingMethodTPM = SealingMethod("tpm")
- SealingMethodFDESetupHook = SealingMethod("fde-setup-hook")
+ SealingMethodLegacyTPM = SealingMethod("")
+ SealingMethodTPM = SealingMethod("tpm")
+ SealingMethodFDESetupHook = SealingMethod("fde-setup-hook")
+ SealingMethodNextGeneration = SealingMethod("next-generation")
)
// StampSealedKeys writes what sealing method was used for key sealing
diff --git a/gadget/install/encrypt.go b/gadget/install/encrypt.go
index 14296620a34..1e2c0baaa93 100644
--- a/gadget/install/encrypt.go
+++ b/gadget/install/encrypt.go
@@ -2,7 +2,7 @@
//go:build !nosecboot
/*
- * Copyright (C) 2020 Canonical Ltd
+ * Copyright (C) 2020, 2024 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
@@ -21,13 +21,9 @@
package install
import (
- "bytes"
"fmt"
- "os/exec"
- "github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/secboot"
- "github.com/snapcore/snapd/secboot/keys"
)
var (
@@ -52,7 +48,7 @@ var _ = encryptedDevice(&encryptedDeviceLUKS{})
// newEncryptedDeviceLUKS creates an encrypted device in the existing
// partition using the specified key with the LUKS backend.
-func newEncryptedDeviceLUKS(devNode string, encType secboot.EncryptionType, key keys.EncryptionKey, label, name string) (encryptedDevice, error) {
+func newEncryptedDeviceLUKS(devNode string, encType secboot.EncryptionType, key secboot.DiskUnlockKey, label, name string) (encryptedDevice, error) {
encLabel := label + "-enc"
if err := secbootFormatEncryptedDevice(key, encType, encLabel, devNode); err != nil {
return nil, fmt.Errorf("cannot format encrypted device: %v", err)
@@ -81,18 +77,14 @@ func (dev *encryptedDeviceLUKS) Close() error {
return cryptsetupClose(dev.name)
}
-func cryptsetupOpen(key keys.EncryptionKey, node, name string) error {
- cmd := exec.Command("cryptsetup", "open", "--key-file", "-", node, name)
- cmd.Stdin = bytes.NewReader(key[:])
- if output, err := cmd.CombinedOutput(); err != nil {
- return osutil.OutputErr(output, err)
- }
- return nil
+func cryptsetupOpenImpl(key secboot.DiskUnlockKey, node, name string) error {
+ return secboot.ActivateVolumeWithKey(name, node, key, nil)
}
-func cryptsetupClose(name string) error {
- if output, err := exec.Command("cryptsetup", "close", name).CombinedOutput(); err != nil {
- return osutil.OutputErr(output, err)
- }
- return nil
+var cryptsetupOpen = cryptsetupOpenImpl
+
+func cryptsetupCloseImpl(name string) error {
+ return secboot.DeactivateVolume(name)
}
+
+var cryptsetupClose = cryptsetupCloseImpl
diff --git a/gadget/install/encrypt_test.go b/gadget/install/encrypt_test.go
index 556f542a963..60ff3b7a40f 100644
--- a/gadget/install/encrypt_test.go
+++ b/gadget/install/encrypt_test.go
@@ -2,7 +2,7 @@
//go:build !nosecboot
/*
- * Copyright (C) 2020 Canonical Ltd
+ * Copyright (C) 2020, 2024 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
@@ -79,25 +79,28 @@ func (s *encryptSuite) TestNewEncryptedDeviceLUKS(c *C) {
expectedErr: "cannot open encrypted device on /dev/node1: open error",
},
} {
- script := ""
- if tc.mockedOpenErr != "" {
- script = fmt.Sprintf("echo '%s'>&2; exit 1", tc.mockedOpenErr)
+ defer install.MockCryptsetupOpen(func(key secboot.DiskUnlockKey, node, name string) error {
+ if tc.mockedOpenErr != "" {
+ return fmt.Errorf(tc.mockedOpenErr)
+ }
+ return nil
+ })()
- }
- s.mockCryptsetup = testutil.MockCommand(c, "cryptsetup", script)
- s.AddCleanup(s.mockCryptsetup.Restore)
+ defer install.MockCryptsetupClose(func(name string) error {
+ return nil
+ })()
calls := 0
- restore := install.MockSecbootFormatEncryptedDevice(func(key keys.EncryptionKey, encType secboot.EncryptionType, label, node string) error {
+ restore := install.MockSecbootFormatEncryptedDevice(func(key []byte, encType secboot.EncryptionType, label, node string) error {
calls++
- c.Assert(key, DeepEquals, s.mockedEncryptionKey)
+ c.Assert(key, DeepEquals, []byte(s.mockedEncryptionKey))
c.Assert(label, Equals, "some-label-enc")
c.Assert(node, Equals, "/dev/node1")
return tc.mockedFormatErr
})
defer restore()
- dev, err := install.NewEncryptedDeviceLUKS("/dev/node1", secboot.EncryptionTypeLUKS, s.mockedEncryptionKey, "some-label", "some-label")
+ dev, err := install.NewEncryptedDeviceLUKS("/dev/node1", secboot.EncryptionTypeLUKS, secboot.DiskUnlockKey(s.mockedEncryptionKey), "some-label", "some-label")
c.Assert(calls, Equals, 1)
if tc.expectedErr == "" {
c.Assert(err, IsNil)
@@ -109,10 +112,5 @@ func (s *encryptSuite) TestNewEncryptedDeviceLUKS(c *C) {
err = dev.Close()
c.Assert(err, IsNil)
-
- c.Assert(s.mockCryptsetup.Calls(), DeepEquals, [][]string{
- {"cryptsetup", "open", "--key-file", "-", "/dev/node1", "some-label"},
- {"cryptsetup", "close", "some-label"},
- })
}
}
diff --git a/gadget/install/export_secboot_test.go b/gadget/install/export_secboot_test.go
index 101bc347fa5..d9464475e49 100644
--- a/gadget/install/export_secboot_test.go
+++ b/gadget/install/export_secboot_test.go
@@ -2,7 +2,7 @@
//go:build !nosecboot
/*
- * Copyright (C) 2020 Canonical Ltd
+ * Copyright (C) 2020, 2024 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
@@ -24,7 +24,6 @@ import (
"github.com/snapcore/snapd/boot"
"github.com/snapcore/snapd/kernel/fde"
"github.com/snapcore/snapd/secboot"
- "github.com/snapcore/snapd/secboot/keys"
"github.com/snapcore/snapd/testutil"
)
@@ -33,7 +32,7 @@ var (
NewEncryptedDeviceLUKS = newEncryptedDeviceLUKS
)
-func MockSecbootFormatEncryptedDevice(f func(key keys.EncryptionKey, encType secboot.EncryptionType, label, node string) error) (restore func()) {
+func MockSecbootFormatEncryptedDevice(f func(key []byte, encType secboot.EncryptionType, label, node string) error) (restore func()) {
r := testutil.Backup(&secbootFormatEncryptedDevice)
secbootFormatEncryptedDevice = f
return r
@@ -45,3 +44,19 @@ func MockBootRunFDESetupHook(f func(req *fde.SetupRequest) ([]byte, error)) (res
boot.RunFDESetupHook = f
return r
}
+
+func MockCryptsetupOpen(f func(key secboot.DiskUnlockKey, node, name string) error) func() {
+ old := cryptsetupOpen
+ cryptsetupOpen = f
+ return func() {
+ cryptsetupOpen = old
+ }
+}
+
+func MockCryptsetupClose(f func(name string) error) func() {
+ old := cryptsetupClose
+ cryptsetupClose = f
+ return func() {
+ cryptsetupClose = old
+ }
+}
diff --git a/gadget/install/export_test.go b/gadget/install/export_test.go
index aae52eb3f93..72fea390fc4 100644
--- a/gadget/install/export_test.go
+++ b/gadget/install/export_test.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2020 Canonical Ltd
+ * Copyright (C) 2020, 2024, 2024 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
diff --git a/gadget/install/install.go b/gadget/install/install.go
index 4603162ce25..4ebfc7e438c 100644
--- a/gadget/install/install.go
+++ b/gadget/install/install.go
@@ -2,7 +2,7 @@
//go:build !nosecboot
/*
- * Copyright (C) 2019-2020 Canonical Ltd
+ * Copyright (C) 2019-2020, 2024 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
@@ -99,7 +99,7 @@ func saveStorageTraits(mod gadget.Model, allVols map[string]*gadget.Volume, opts
return nil
}
-func maybeEncryptPartition(dgpair *gadget.OnDiskAndGadgetStructurePair, encryptionType secboot.EncryptionType, sectorSize quantity.Size, perfTimings timings.Measurer) (fsParams *mkfsParams, encryptionKey keys.EncryptionKey, err error) {
+func maybeEncryptPartition(dgpair *gadget.OnDiskAndGadgetStructurePair, encryptionType secboot.EncryptionType, sectorSize quantity.Size, perfTimings timings.Measurer) (fsParams *mkfsParams, diskEncryptionKey secboot.DiskUnlockKey, err error) {
diskPart := dgpair.DiskStructure
volStruct := dgpair.GadgetStructure
mustEncrypt := (encryptionType != secboot.EncryptionTypeNone)
@@ -121,10 +121,12 @@ func maybeEncryptPartition(dgpair *gadget.OnDiskAndGadgetStructurePair, encrypti
timings.Run(perfTimings, fmt.Sprintf("make-key-set[%s]", volStruct.Role),
fmt.Sprintf("Create encryption key set for %s", volStruct.Role),
func(timings.Measurer) {
- encryptionKey, err = keys.NewEncryptionKey()
- if err != nil {
- err = fmt.Errorf("cannot create encryption key: %v", err)
+ encryptionKey, errk := keys.NewEncryptionKey()
+ if errk != nil {
+ err = fmt.Errorf("cannot create encryption key: %v", errk)
+ return
}
+ diskEncryptionKey = secboot.DiskUnlockKey(encryptionKey)
})
if err != nil {
return nil, nil, err
@@ -136,7 +138,7 @@ func maybeEncryptPartition(dgpair *gadget.OnDiskAndGadgetStructurePair, encrypti
timings.Run(perfTimings, fmt.Sprintf("new-encrypted-device[%s] (%v)", volStruct.Role, encryptionType),
fmt.Sprintf("Create encryption device for %s (%s)", volStruct.Role, encryptionType),
func(timings.Measurer) {
- dataPart, err = newEncryptedDeviceLUKS(diskPart.Node, encryptionType, encryptionKey, volStruct.Label, volStruct.Name)
+ dataPart, err = newEncryptedDeviceLUKS(diskPart.Node, encryptionType, diskEncryptionKey, volStruct.Label, volStruct.Name)
// TODO close device???
})
if err != nil {
@@ -157,7 +159,7 @@ func maybeEncryptPartition(dgpair *gadget.OnDiskAndGadgetStructurePair, encrypti
fsParams.SectorSize = quantity.Size(fsSectorSizeInt)
}
- return fsParams, encryptionKey, nil
+ return fsParams, diskEncryptionKey, nil
}
// TODO probably we won't need to pass partDisp when we include storage in laidOut
@@ -192,7 +194,7 @@ func installOnePartition(dgpair *gadget.OnDiskAndGadgetStructurePair,
kernelInfo *kernel.Info, kernelSnapInfo *KernelSnapInfo, gadgetRoot string,
encryptionType secboot.EncryptionType, sectorSize quantity.Size,
observer gadget.ContentObserver, perfTimings timings.Measurer,
-) (fsDevice string, encryptionKey keys.EncryptionKey, err error) {
+) (fsDevice string, encryptionKey secboot.DiskUnlockKey, err error) {
// 1. Encrypt
diskPart := dgpair.DiskStructure
vs := dgpair.GadgetStructure
@@ -352,7 +354,7 @@ func Run(model gadget.Model, gadgetRoot string, kernelSnapInfo *KernelSnapInfo,
}
// Step 2: layout content in the created partitions
- var keyForRole map[string]keys.EncryptionKey
+ var resetterForRole map[string]secboot.KeyResetter
devicesForRoles := map[string]string{}
partsEncrypted := map[string]gadget.StructureEncryptionParameters{}
kernelInfo, err := kernel.ReadInfo(kernelRoot)
@@ -391,10 +393,10 @@ func Run(model gadget.Model, gadgetRoot string, kernelSnapInfo *KernelSnapInfo,
}
if encryptionKey != nil {
- if keyForRole == nil {
- keyForRole = map[string]keys.EncryptionKey{}
+ if resetterForRole == nil {
+ resetterForRole = map[string]secboot.KeyResetter{}
}
- keyForRole[vs.Role] = encryptionKey
+ resetterForRole[vs.Role] = secboot.CreateKeyResetter(encryptionKey, diskPart.Node)
partsEncrypted[vs.Name] = createEncryptionParams(options.EncryptionType)
}
if options.Mount && vs.Label != "" && vs.HasFilesystem() {
@@ -422,8 +424,8 @@ func Run(model gadget.Model, gadgetRoot string, kernelSnapInfo *KernelSnapInfo,
}
return &InstalledSystemSideData{
- KeyForRole: keyForRole,
- DeviceForRole: devicesForRoles,
+ ResetterForRole: resetterForRole,
+ DeviceForRole: devicesForRoles,
}, nil
}
@@ -676,12 +678,12 @@ func EncryptPartitions(onVolumes map[string]*gadget.Volume, encryptionType secbo
return setupData, nil
}
-func KeysForRole(setupData *EncryptionSetupData) map[string]keys.EncryptionKey {
- keyForRole := make(map[string]keys.EncryptionKey)
+func ResetterForRole(setupData *EncryptionSetupData) map[string]secboot.KeyResetter {
+ resetterForRole := make(map[string]secboot.KeyResetter)
for _, p := range setupData.parts {
- keyForRole[p.role] = p.encryptionKey
+ resetterForRole[p.role] = secboot.CreateKeyResetter(p.encryptionKey, p.device)
}
- return keyForRole
+ return resetterForRole
}
func FactoryReset(model gadget.Model, gadgetRoot string, kernelSnapInfo *KernelSnapInfo, bootDevice string, options Options, observer gadget.ContentObserver, perfTimings timings.Measurer) (*InstalledSystemSideData, error) {
@@ -746,7 +748,7 @@ func FactoryReset(model gadget.Model, gadgetRoot string, kernelSnapInfo *KernelS
if err != nil {
return nil, err
}
- var keyForRole map[string]keys.EncryptionKey
+ var resetterForRole map[string]secboot.KeyResetter
deviceForRole := map[string]string{}
var hasSavePartition bool
rolesToReset := []string{gadget.SystemBoot, gadget.SystemData}
@@ -771,6 +773,11 @@ func FactoryReset(model gadget.Model, gadgetRoot string, kernelSnapInfo *KernelS
// device) for each role
deviceForRole[vs.Role] = onDiskStruct.Node
+ // TODO: when save partition does not have slots, then
+ // we need to create new partition the old fashion
+ // way. Or, we should upgrade the save partition
+ // (which should be safe since the seed should not be
+ // considered safe to downgrade).
fsDevice, encryptionKey, err := installOnePartition(
&gadget.OnDiskAndGadgetStructurePair{
DiskStructure: onDiskStruct, GadgetStructure: vs},
@@ -780,10 +787,10 @@ func FactoryReset(model gadget.Model, gadgetRoot string, kernelSnapInfo *KernelS
return nil, err
}
if encryptionKey != nil {
- if keyForRole == nil {
- keyForRole = map[string]keys.EncryptionKey{}
+ if resetterForRole == nil {
+ resetterForRole = map[string]secboot.KeyResetter{}
}
- keyForRole[vs.Role] = encryptionKey
+ resetterForRole[vs.Role] = secboot.CreateKeyResetter(encryptionKey, onDiskStruct.Node)
}
if options.Mount && vs.Label != "" && vs.HasFilesystem() {
// fs is taken from gadget, as on disk one might be displayed as
@@ -809,8 +816,8 @@ func FactoryReset(model gadget.Model, gadgetRoot string, kernelSnapInfo *KernelS
}
return &InstalledSystemSideData{
- KeyForRole: keyForRole,
- DeviceForRole: deviceForRole,
+ ResetterForRole: resetterForRole,
+ DeviceForRole: deviceForRole,
}, nil
}
diff --git a/gadget/install/install_dummy.go b/gadget/install/install_dummy.go
index 2dfb2840f8a..1ca7b9d00b2 100644
--- a/gadget/install/install_dummy.go
+++ b/gadget/install/install_dummy.go
@@ -2,7 +2,7 @@
//go:build nosecboot
/*
- * Copyright (C) 2019-2020 Canonical Ltd
+ * Copyright (C) 2019-2020, 2024 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
@@ -26,7 +26,6 @@ import (
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/gadget"
"github.com/snapcore/snapd/secboot"
- "github.com/snapcore/snapd/secboot/keys"
"github.com/snapcore/snapd/timings"
)
@@ -55,7 +54,7 @@ func EncryptPartitions(onVolumes map[string]*gadget.Volume, encryptionType secbo
return nil, fmt.Errorf("build without secboot support")
}
-func KeysForRole(setupData *EncryptionSetupData) map[string]keys.EncryptionKey {
+func ResetterForRole(setupData *EncryptionSetupData) map[string]secboot.KeyResetter {
return nil
}
diff --git a/gadget/install/install_test.go b/gadget/install/install_test.go
index 5e2bd6ea3c0..80056064041 100644
--- a/gadget/install/install_test.go
+++ b/gadget/install/install_test.go
@@ -2,7 +2,7 @@
//go:build !nosecboot
/*
- * Copyright (C) 2019-2022 Canonical Ltd
+ * Copyright (C) 2019-2022, 2024 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
@@ -42,7 +42,6 @@ import (
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/osutil/disks"
"github.com/snapcore/snapd/secboot"
- "github.com/snapcore/snapd/secboot/keys"
"github.com/snapcore/snapd/testutil"
"github.com/snapcore/snapd/timings"
)
@@ -181,9 +180,6 @@ fi
defer restoreMountInfo()
}
- mockCryptsetup := testutil.MockCommand(c, "cryptsetup", "")
- defer mockCryptsetup.Restore()
-
if opts.encryption {
mockBlockdev := testutil.MockCommand(c, "blockdev", "case ${1} in --getss) echo 4096; exit 0;; esac; exit 1")
defer mockBlockdev.Restore()
@@ -334,10 +330,8 @@ fi
gadgetRoot, err := gadgettest.WriteGadgetYaml(c.MkDir(), gadgettest.RaspiSimplifiedYaml)
c.Assert(err, IsNil)
- var saveEncryptionKey, dataEncryptionKey keys.EncryptionKey
-
secbootFormatEncryptedDeviceCall := 0
- restore = install.MockSecbootFormatEncryptedDevice(func(key keys.EncryptionKey, encType secboot.EncryptionType, label, node string) error {
+ restore = install.MockSecbootFormatEncryptedDevice(func(key []byte, encType secboot.EncryptionType, label, node string) error {
if !opts.encryption {
c.Error("unexpected call to secboot.FormatEncryptedDevice when encryption is off")
return fmt.Errorf("no encryption functions should be called")
@@ -349,12 +343,10 @@ fi
c.Assert(key, HasLen, 32)
c.Assert(label, Equals, "ubuntu-save-enc")
c.Assert(node, Equals, "/dev/mmcblk0p3")
- saveEncryptionKey = key
case 2:
c.Assert(key, HasLen, 32)
c.Assert(label, Equals, "ubuntu-data-enc")
c.Assert(node, Equals, "/dev/mmcblk0p4")
- dataEncryptionKey = key
default:
c.Errorf("unexpected call to secboot.FormatEncryptedDevice (%d)", secbootFormatEncryptedDeviceCall)
return fmt.Errorf("test broken")
@@ -370,20 +362,23 @@ fi
if opts.encryption {
runOpts.EncryptionType = secboot.EncryptionTypeLUKS
}
+
+ defer install.MockCryptsetupOpen(func(key secboot.DiskUnlockKey, node, name string) error {
+ return nil
+ })()
+
+ defer install.MockCryptsetupClose(func(name string) error {
+ return nil
+ })()
+
sys, err := install.Run(uc20Mod, gadgetRoot, &install.KernelSnapInfo{}, "", runOpts, nil, timings.New(nil))
c.Assert(err, IsNil)
if opts.encryption {
c.Check(sys, Not(IsNil))
- c.Assert(sys, DeepEquals, &install.InstalledSystemSideData{
- KeyForRole: map[string]keys.EncryptionKey{
- gadget.SystemData: dataEncryptionKey,
- gadget.SystemSave: saveEncryptionKey,
- },
- DeviceForRole: map[string]string{
- "system-boot": "/dev/mmcblk0p2",
- "system-save": "/dev/mmcblk0p3",
- "system-data": "/dev/mmcblk0p4",
- },
+ c.Assert(sys.DeviceForRole, DeepEquals, map[string]string{
+ "system-boot": "/dev/mmcblk0p2",
+ "system-save": "/dev/mmcblk0p3",
+ "system-data": "/dev/mmcblk0p4",
})
} else {
c.Assert(sys, DeepEquals, &install.InstalledSystemSideData{
@@ -430,15 +425,6 @@ fi
c.Assert(mockUdevadm.Calls(), DeepEquals, udevmadmCalls)
- if opts.encryption {
- c.Assert(mockCryptsetup.Calls(), DeepEquals, [][]string{
- {"cryptsetup", "open", "--key-file", "-", "/dev/mmcblk0p3", "ubuntu-save"},
- {"cryptsetup", "open", "--key-file", "-", "/dev/mmcblk0p4", "ubuntu-data"},
- })
- } else {
- c.Assert(mockCryptsetup.Calls(), HasLen, 0)
- }
-
c.Assert(mkfsCall, Equals, 3)
c.Assert(mountCall, Equals, 3)
c.Assert(umountCall, Equals, 3)
@@ -638,9 +624,6 @@ fi
defer restoreMountInfo()
}
- mockCryptsetup := testutil.MockCommand(c, "cryptsetup", "")
- defer mockCryptsetup.Restore()
-
if opts.encryption {
mockBlockdev := testutil.MockCommand(c, "blockdev", "case ${1} in --getss) echo 4096; exit 0;; esac; exit 1")
defer mockBlockdev.Restore()
@@ -747,9 +730,8 @@ fi
gadgetRoot, err := gadgettest.WriteGadgetYaml(c.MkDir(), opts.gadgetYaml)
c.Assert(err, IsNil)
- var dataPrimaryKey keys.EncryptionKey
secbootFormatEncryptedDeviceCall := 0
- restore = install.MockSecbootFormatEncryptedDevice(func(key keys.EncryptionKey, encType secboot.EncryptionType, label, node string) error {
+ restore = install.MockSecbootFormatEncryptedDevice(func(key []byte, encType secboot.EncryptionType, label, node string) error {
if !opts.encryption {
c.Error("unexpected call to secboot.FormatEncryptedDevice")
return fmt.Errorf("unexpected call")
@@ -761,7 +743,6 @@ fi
c.Assert(key, HasLen, 32)
c.Assert(label, Equals, "ubuntu-data-enc")
c.Assert(node, Equals, "/dev/mmcblk0p4")
- dataPrimaryKey = key
default:
c.Errorf("unexpected call to secboot.FormatEncryptedDevice (%d)", secbootFormatEncryptedDeviceCall)
return fmt.Errorf("test broken")
@@ -776,6 +757,15 @@ fi
if opts.encryption {
runOpts.EncryptionType = secboot.EncryptionTypeLUKS
}
+
+ defer install.MockCryptsetupOpen(func(key secboot.DiskUnlockKey, node, name string) error {
+ return nil
+ })()
+
+ defer install.MockCryptsetupClose(func(name string) error {
+ return nil
+ })()
+
sys, err := install.FactoryReset(uc20Mod, gadgetRoot, &install.KernelSnapInfo{}, "", runOpts, nil, timings.New(nil))
if opts.err != "" {
c.Check(sys, IsNil)
@@ -799,12 +789,7 @@ fi
DeviceForRole: devsForRoles,
})
} else {
- c.Assert(sys, DeepEquals, &install.InstalledSystemSideData{
- KeyForRole: map[string]keys.EncryptionKey{
- gadget.SystemData: dataPrimaryKey,
- },
- DeviceForRole: devsForRoles,
- })
+ c.Assert(sys.DeviceForRole, DeepEquals, devsForRoles)
}
c.Assert(mockSfdisk.Calls(), HasLen, 0)
@@ -1107,9 +1092,6 @@ func (s *installSuite) testEncryptPartitions(c *C, opts encryptPartitionsOpts) {
c.Assert(err, IsNil)
defer restore()
- mockCryptsetup := testutil.MockCommand(c, "cryptsetup", "")
- defer mockCryptsetup.Restore()
-
mockBlockdev := testutil.MockCommand(c, "blockdev", "case ${1} in --getss) echo 4096; exit 0;; esac; exit 1")
defer mockBlockdev.Restore()
@@ -1122,6 +1104,19 @@ func (s *installSuite) testEncryptPartitions(c *C, opts encryptPartitionsOpts) {
ginfo.Volumes["pc"].Structure[i].Device = "/dev/vda" + strconv.Itoa(partIdx)
partIdx++
}
+
+ defer install.MockCryptsetupOpen(func(key secboot.DiskUnlockKey, node, name string) error {
+ return nil
+ })()
+
+ defer install.MockCryptsetupClose(func(name string) error {
+ return nil
+ })()
+
+ defer install.MockSecbootFormatEncryptedDevice(func(key []byte, encType secboot.EncryptionType, label, node string) error {
+ return nil
+ })()
+
encryptSetup, err := install.EncryptPartitions(ginfo.Volumes, opts.encryptType, model, gadgetRoot, "", timings.New(nil))
c.Assert(err, IsNil)
c.Assert(encryptSetup, NotNil)
@@ -1130,15 +1125,6 @@ func (s *installSuite) testEncryptPartitions(c *C, opts encryptPartitionsOpts) {
"ubuntu-data": "/dev/mapper/ubuntu-data",
})
c.Assert(err, IsNil)
-
- c.Assert(mockCryptsetup.Calls(), DeepEquals, [][]string{
- {"cryptsetup", "-q", "luksFormat", "--type", "luks2", "--key-file", "-", "--cipher", expectedCipher(), "--key-size", expectedKeysize(), "--label", "ubuntu-save-enc", "--pbkdf", "argon2i", "--pbkdf-force-iterations", "4", "--pbkdf-memory", "32", "--luks2-metadata-size", "2048k", "--luks2-keyslots-size", "2560k", "/dev/vda4"},
- {"cryptsetup", "config", "--priority", "prefer", "--key-slot", "0", "/dev/vda4"},
- {"cryptsetup", "open", "--key-file", "-", "/dev/vda4", "ubuntu-save"},
- {"cryptsetup", "-q", "luksFormat", "--type", "luks2", "--key-file", "-", "--cipher", expectedCipher(), "--key-size", expectedKeysize(), "--label", "ubuntu-data-enc", "--pbkdf", "argon2i", "--pbkdf-force-iterations", "4", "--pbkdf-memory", "32", "--luks2-metadata-size", "2048k", "--luks2-keyslots-size", "2560k", "/dev/vda5"},
- {"cryptsetup", "config", "--priority", "prefer", "--key-slot", "0", "/dev/vda5"},
- {"cryptsetup", "open", "--key-file", "-", "/dev/vda5", "ubuntu-data"},
- })
}
func (s *installSuite) TestInstallEncryptPartitionsLUKSHappy(c *C) {
diff --git a/gadget/install/params.go b/gadget/install/params.go
index 6d3c2dcc5b8..089261ed923 100644
--- a/gadget/install/params.go
+++ b/gadget/install/params.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2019-2022 Canonical Ltd
+ * Copyright (C) 2019-2022, 2024 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
@@ -23,7 +23,6 @@ import (
"github.com/snapcore/snapd/gadget"
"github.com/snapcore/snapd/gadget/quantity"
"github.com/snapcore/snapd/secboot"
- "github.com/snapcore/snapd/secboot/keys"
)
type Options struct {
@@ -37,7 +36,7 @@ type Options struct {
// to access its partitions.
type InstalledSystemSideData struct {
// KeysForRoles contains key sets for the relevant structure roles.
- KeyForRole map[string]keys.EncryptionKey
+ ResetterForRole map[string]secboot.KeyResetter
// DeviceForRole maps a roles to their corresponding device nodes. For
// structures with roles that require data to be encrypted, the device
// is the raw encrypted device node (eg. /dev/mmcblk0p1).
@@ -51,7 +50,7 @@ type partEncryptionData struct {
encryptedDevice string
volName string
- encryptionKey keys.EncryptionKey
+ encryptionKey []byte
// TODO: this is currently not used
encryptedSectorSize quantity.Size
encryptionParams gadget.StructureEncryptionParameters
@@ -89,7 +88,7 @@ func MockEncryptionSetupData(labelToEncDevice map[string]*MockEncryptedDeviceAnd
esd.parts[label] = partEncryptionData{
role: encryptData.Role,
encryptedDevice: encryptData.EncryptedDevice,
- encryptionKey: keys.EncryptionKey{1, 2, 3},
+ encryptionKey: []byte{1, 2, 3},
encryptedSectorSize: 512,
}
}
diff --git a/go.mod b/go.mod
index 937200d4387..abad8c8f707 100644
--- a/go.mod
+++ b/go.mod
@@ -7,26 +7,25 @@ replace maze.io/x/crypto => github.com/snapcore/maze.io-x-crypto v0.0.0-20190131
require (
github.com/bmatcuk/doublestar/v4 v4.6.1
- github.com/canonical/go-efilib v0.4.0
+ github.com/canonical/go-efilib v1.2.0
github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3 // indirect
- github.com/canonical/go-tpm2 v0.0.0-20210827151749-f80ff5afff61
- github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7
+ github.com/canonical/go-tpm2 v1.3.0
+ github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2
- github.com/gorilla/mux v1.7.4-0.20190701202633-d83b6ffe499a
+ github.com/gorilla/mux v1.8.0
github.com/gvalkov/golang-evdev v0.0.0-20191114124502-287e62b94bcb
github.com/jessevdk/go-flags v1.5.1-0.20210607101731-3927b71304df
github.com/juju/ratelimit v1.0.1
- github.com/mvo5/goconfigparser v0.0.0-20200803085309-72e476556adb
+ github.com/mvo5/goconfigparser v0.0.0-20231016112547-05bd887f05e1
// if below two libseccomp-golang lines are updated, one must also update packaging/ubuntu-14.04/rules
github.com/mvo5/libseccomp-golang v0.9.1-0.20180308152521-f4de83b52afb // old trusty builds only
github.com/seccomp/libseccomp-golang v0.9.2-0.20220502024300-f57e1d55ea18
github.com/snapcore/go-gettext v0.0.0-20191107141714-82bbea49e785
- github.com/snapcore/secboot v0.0.0-20240411101434-f3ad7c92552a
- golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
- golang.org/x/net v0.9.0 // indirect
- golang.org/x/sys v0.7.0
- golang.org/x/text v0.9.0
- golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f
+ github.com/snapcore/secboot v0.0.0-20240711203028-8dd835e19590
+ golang.org/x/crypto v0.13.0
+ golang.org/x/net v0.15.0 // indirect
+ golang.org/x/sys v0.19.0
+ golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
gopkg.in/macaroon.v1 v1.0.0-20150121114231-ab3940c6c165
gopkg.in/retry.v1 v1.0.3
@@ -35,14 +34,17 @@ require (
gopkg.in/yaml.v3 v3.0.1
)
-require go.etcd.io/bbolt v1.3.9
+require (
+ go.etcd.io/bbolt v1.3.9
+ golang.org/x/text v0.13.0
+)
require (
- github.com/canonical/go-sp800.108-kdf v0.0.0-20210314145419-a3359f2d21b9 // indirect
- github.com/canonical/tcglog-parser v0.0.0-20210824131805-69fa1e9f0ad2 // indirect
+ github.com/canonical/go-sp800.108-kdf v0.0.0-20210315104021-ead800bbf9a0 // indirect
+ github.com/canonical/tcglog-parser v0.0.0-20240502135731-7e805de2ca0d // indirect
github.com/kr/pretty v0.2.2-0.20200810074440-814ac30b4b18 // indirect
github.com/kr/text v0.1.0 // indirect
- go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 // indirect
- golang.org/x/term v0.7.0 // indirect
+ golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
+ golang.org/x/term v0.12.0 // indirect
maze.io/x/crypto v0.0.0-20190131090603-9b94c9afe066 // indirect
)
diff --git a/go.sum b/go.sum
index 4a8a5129223..9121b8993c1 100644
--- a/go.sum
+++ b/go.sum
@@ -1,26 +1,28 @@
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
-github.com/canonical/go-efilib v0.4.0 h1:2ee5pvhIZ+g1EO4HxFE/owBgs5Up2g7dw1+Ls9/fiSs=
-github.com/canonical/go-efilib v0.4.0/go.mod h1:9b2PNAuPcZsB76x75/uwH99D8CyH/A2y4rq1/+bvplg=
-github.com/canonical/go-sp800.108-kdf v0.0.0-20210314145419-a3359f2d21b9 h1:USzKjrfWo/ESzozv2i3OMM7XDgxrZRvaHFrKkIKRtwU=
+github.com/canonical/go-efilib v0.3.0/go.mod h1:9b2PNAuPcZsB76x75/uwH99D8CyH/A2y4rq1/+bvplg=
+github.com/canonical/go-efilib v1.2.0 h1:+fvJdkj3oVyURFtfk8gSft6pdKyVzzdzNn9GC1kMJw8=
+github.com/canonical/go-efilib v1.2.0/go.mod h1:n0Ttsy1JuHAvqaFbZBs6PAzoiiJdfkHsAmDOEbexYEQ=
github.com/canonical/go-sp800.108-kdf v0.0.0-20210314145419-a3359f2d21b9/go.mod h1:Zrs3YjJr+w51u0R/dyLh/oWt/EcBVdLPCVFYC4daW5s=
+github.com/canonical/go-sp800.108-kdf v0.0.0-20210315104021-ead800bbf9a0 h1:ZE2XMRFHcwlib3uU9is37+pKkkMloVoEPWmgQ6GK1yo=
+github.com/canonical/go-sp800.108-kdf v0.0.0-20210315104021-ead800bbf9a0/go.mod h1:Zrs3YjJr+w51u0R/dyLh/oWt/EcBVdLPCVFYC4daW5s=
github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3 h1:oe6fCvaEpkhyW3qAicT0TnGtyht/UrgvOwMcEgLb7Aw=
github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3/go.mod h1:qdP0gaj0QtgX2RUZhnlVrceJ+Qln8aSlDyJwelLLFeM=
-github.com/canonical/go-tpm2 v0.0.0-20210827151749-f80ff5afff61 h1:DsyeCtFXqOdukmhPOunohjSlyxDHTqWSW1O4rD9N3L8=
-github.com/canonical/go-tpm2 v0.0.0-20210827151749-f80ff5afff61/go.mod h1:vG41hdbBjV4+/fkubTT1ENBBqSkLwLr7mCeW9Y6kpZY=
-github.com/canonical/tcglog-parser v0.0.0-20210824131805-69fa1e9f0ad2 h1:CbwVq64ruNLx/S3XA0LO6QMsw6Vc2inK+RcS6D2c4Ns=
-github.com/canonical/tcglog-parser v0.0.0-20210824131805-69fa1e9f0ad2/go.mod h1:QoW2apR2tBl6T/4czdND/EHjL1Ia9cCmQnIj9Xe0Kt8=
-github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM=
-github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/canonical/go-tpm2 v1.3.0 h1:+xc2++IM4kaMCJruFzlgtYgQyV5Q0EReaP++z8VTqJk=
+github.com/canonical/go-tpm2 v1.3.0/go.mod h1:kLkR1//7ocrPDl6LZfijTKEoPGxRIZSbb8GuWaO1JM8=
+github.com/canonical/tcglog-parser v0.0.0-20240502135731-7e805de2ca0d h1:bLkGnvu8xqC6OTceF41KLsX/R/mOzvgMpqgJKAywgiM=
+github.com/canonical/tcglog-parser v0.0.0-20240502135731-7e805de2ca0d/go.mod h1:e2KTSzgMF5y6ThtfLnNJT0FwqfQUPmannJy5MnAZ1js=
+github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
+github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/frankban/quicktest v1.2.2 h1:xfmOhhoH5fGPgbEAlhLpJH9p0z/0Qizio9osmvn9IUY=
github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
-github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42 h1:q3pnF5JFBNRz8sRD+IRj7Y6DMyYGTNqnZ9axTbSfoNI=
github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/gorilla/mux v1.7.4-0.20190701202633-d83b6ffe499a h1:Rhv8JUcDkZJkUmzzjpysRtn5joJ/3T8Lt9QpdJZUz1c=
-github.com/gorilla/mux v1.7.4-0.20190701202633-d83b6ffe499a/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
+github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gvalkov/golang-evdev v0.0.0-20191114124502-287e62b94bcb h1:WHSAxLz3P5t4DKukfJ5wu7+aMyVkuTNSbCiAjVS92sM=
github.com/gvalkov/golang-evdev v0.0.0-20191114124502-287e62b94bcb/go.mod h1:SAzVFKCRezozJTGavF3GX8MBUruETCqzivVLYiywouA=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
@@ -35,11 +37,11 @@ github.com/kr/pretty v0.2.2-0.20200810074440-814ac30b4b18/go.mod h1:ipq/a2n7PKx3
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/mvo5/goconfigparser v0.0.0-20200803085309-72e476556adb h1:1I/JqsB+FffFssjcOeEP0popLhJ46+OwtXztJ/1DhM0=
-github.com/mvo5/goconfigparser v0.0.0-20200803085309-72e476556adb/go.mod h1:xmt4k1xLDl8Tdan+0S/jmMK2uSUBSzTc18+5GN5Vea8=
+github.com/mvo5/goconfigparser v0.0.0-20221018104758-434073381f37/go.mod h1:inxjKzuGbpMDmdoI7kogueqBVRdf6fPAG5dAsU3gu60=
+github.com/mvo5/goconfigparser v0.0.0-20231016112547-05bd887f05e1 h1:FFUTZbYYAr7FoddSzL7RnR0lgX2OO1y9m+3DiEV8BuQ=
+github.com/mvo5/goconfigparser v0.0.0-20231016112547-05bd887f05e1/go.mod h1:inxjKzuGbpMDmdoI7kogueqBVRdf6fPAG5dAsU3gu60=
github.com/mvo5/libseccomp-golang v0.9.1-0.20180308152521-f4de83b52afb h1:+u5VeqU0Lm7ESN1mS0WONqKRScw7WpPYYtr3zmqEFQ0=
github.com/mvo5/libseccomp-golang v0.9.1-0.20180308152521-f4de83b52afb/go.mod h1:RduRpSkQHOCvZTbGgT/NJUGjFBFkYlVedimxssQ64ag=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/rogpeppe/clock v0.0.0-20190514195947-2896927a307a h1:3QH7VyOaaiUHNrA9Se4YQIRkDTCw1EJls9xTUCaCeRM=
github.com/rogpeppe/clock v0.0.0-20190514195947-2896927a307a/go.mod h1:4r5QyqhjIWCcK8DO4KMclc5Iknq5qVBAlbYYzAbUScQ=
@@ -49,39 +51,41 @@ github.com/snapcore/go-gettext v0.0.0-20191107141714-82bbea49e785 h1:PaunR+BhraK
github.com/snapcore/go-gettext v0.0.0-20191107141714-82bbea49e785/go.mod h1:D3SsWAXK7wCCBZu+Vk5hc1EuKj/L3XN1puEMXTU4LrQ=
github.com/snapcore/maze.io-x-crypto v0.0.0-20190131090603-9b94c9afe066 h1:InG0EmriMOiI4YgtQNOo+6fNxzLCYioo3Q3BCVLdMCE=
github.com/snapcore/maze.io-x-crypto v0.0.0-20190131090603-9b94c9afe066/go.mod h1:VuAdaITF1MrGzxPU+8GxagM1HW2vg7QhEFEeGHbmEMU=
-github.com/snapcore/secboot v0.0.0-20240411101434-f3ad7c92552a h1:yzzVi0yUosDYkjSQqGZNVtaVi+6yNFLiF0erKHlBbdo=
-github.com/snapcore/secboot v0.0.0-20240411101434-f3ad7c92552a/go.mod h1:72paVOkm4sJugXt+v9ItmnjXgO921D8xqsbH2OekouY=
+github.com/snapcore/secboot v0.0.0-20221114180054-b4be60e68879/go.mod h1:72paVOkm4sJugXt+v9ItmnjXgO921D8xqsbH2OekouY=
+github.com/snapcore/secboot v0.0.0-20240711203028-8dd835e19590 h1:40lRi9ZhE0wp7Lm01y+uuT7UKzLLOXsUamlr7ecpxOU=
+github.com/snapcore/secboot v0.0.0-20240711203028-8dd835e19590/go.mod h1:ACfmg+xiKu18C1dtduc4eGb/83NyqBZKljrhGS6dTq8=
github.com/snapcore/snapd v0.0.0-20201005140838-501d14ac146e/go.mod h1:3xrn7QDDKymcE5VO2rgWEQ5ZAUGb9htfwlXnoel6Io8=
+github.com/snapcore/squashfuse v0.0.0-20171220165323-319f6d41a041/go.mod h1:8loYitFPSdoeCXBs/XjO0fyGcpgLAybOHLUsGwgMq90=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
-go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 h1:A/5uWzF44DlIgdm/PQFwfMkW0JX+cIcQi/SwLAmZP5M=
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
-golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
+golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c/go.mod h1:iQL9McJNjoIa5mjH6nYTCTZXUN6RP+XW3eib7Ya3XcI=
-golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
-golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
-golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
+golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
-golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
-golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
+golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
+golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
+golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
-golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0=
-golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
+golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
diff --git a/kernel/fde/fde.go b/kernel/fde/fde.go
index 6c6899e5079..a42148b1dad 100644
--- a/kernel/fde/fde.go
+++ b/kernel/fde/fde.go
@@ -84,6 +84,8 @@ type SetupRequest struct {
// encode it automatically for us
Key []byte `json:"key,omitempty"`
+ AAD []byte `json:"aad,omitempty"`
+
// Only used when called with "initial-setup"
KeyName string `json:"key-name,omitempty"`
@@ -98,6 +100,7 @@ type RunSetupHookFunc func(req *SetupRequest) ([]byte, error)
type InitialSetupParams struct {
Key []byte
KeyName string
+ AAD []byte
}
// InitalSetupResult contains the outputs of the fde-setup hook
@@ -114,6 +117,7 @@ func InitialSetup(runSetupHook RunSetupHookFunc, params *InitialSetupParams) (*I
Op: "initial-setup",
Key: params.Key,
KeyName: params.KeyName,
+ AAD: params.AAD,
}
hookOutput, err := runSetupHook(req)
if err != nil {
diff --git a/kernel/fde/reveal_key.go b/kernel/fde/reveal_key.go
index 261740a94b4..566dac73732 100644
--- a/kernel/fde/reveal_key.go
+++ b/kernel/fde/reveal_key.go
@@ -35,6 +35,7 @@ type RevealKeyRequest struct {
SealedKey []byte `json:"sealed-key,omitempty"`
Handle *json.RawMessage `json:"handle,omitempty"`
+ AAD []byte `json:"aad,omitempty"`
// deprecated for v1
KeyName string `json:"key-name,omitempty"`
@@ -85,6 +86,7 @@ type RevealParams struct {
// V2Payload is set true if SealedKey is expected to contain a v2 payload
// (disk key + aux key)
V2Payload bool
+ AAD []byte
}
type revealKeyResult struct {
@@ -106,6 +108,7 @@ func Reveal(params *RevealParams) (payload []byte, err error) {
Op: "reveal",
SealedKey: params.SealedKey,
Handle: handle,
+ AAD: params.AAD,
// deprecated but needed for v1 hooks
KeyName: "deprecated-" + randutil.RandomString(12),
}
diff --git a/overlord/devicestate/devicemgr.go b/overlord/devicestate/devicemgr.go
index 0a690546a4b..3d0dd7dc3bc 100644
--- a/overlord/devicestate/devicemgr.go
+++ b/overlord/devicestate/devicemgr.go
@@ -1712,8 +1712,8 @@ func (m *DeviceManager) ensurePostFactoryReset() error {
}
if encrypted {
- if err := rotateEncryptionKeys(); err != nil {
- return fmt.Errorf("cannot transition encryption keys: %v", err)
+ if err := deleteOldSaveKey(boot.InitramfsUbuntuSaveDir); err != nil {
+ return fmt.Errorf("cannot remove old encryption keys: %v", err)
}
}
diff --git a/overlord/devicestate/devicestate_install_api_test.go b/overlord/devicestate/devicestate_install_api_test.go
index 1ce0be9cc4a..f4477542baa 100644
--- a/overlord/devicestate/devicestate_install_api_test.go
+++ b/overlord/devicestate/devicestate_install_api_test.go
@@ -2,7 +2,7 @@
//go:build !nosecboot
/*
- * Copyright (C) 2022 Canonical Ltd
+ * Copyright (C) 2022, 2024 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
@@ -44,7 +44,6 @@ import (
installLogic "github.com/snapcore/snapd/overlord/install"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/secboot"
- "github.com/snapcore/snapd/secboot/keys"
"github.com/snapcore/snapd/seed"
"github.com/snapcore/snapd/seed/seedtest"
"github.com/snapcore/snapd/snap"
@@ -528,7 +527,7 @@ func (s *deviceMgrInstallAPISuite) testInstallFinishStep(c *C, opts finishStepOp
return nil
})
s.AddCleanup(restore)
- restore = boot.MockSealKeyToModeenv(func(key, saveKey keys.EncryptionKey, model *asserts.Model, modeenv *boot.Modeenv, flags boot.MockSealKeyToModeenvFlags) error {
+ restore = boot.MockSealKeyToModeenv(func(key, saveKey secboot.KeyResetter, model *asserts.Model, modeenv *boot.Modeenv, flags boot.MockSealKeyToModeenvFlags) error {
c.Check(model.Classic(), Equals, opts.installClassic)
// Note that we cannot compare the full structure and we check
// separately bits as the types for these are not exported.
@@ -564,6 +563,10 @@ func (s *deviceMgrInstallAPISuite) testInstallFinishStep(c *C, opts finishStepOp
bootDir := filepath.Join(dirs.RunDir, "mnt/ubuntu-boot/EFI/boot/")
c.Assert(os.MkdirAll(bootDir, 0755), IsNil)
c.Assert(os.WriteFile(filepath.Join(bootDir, "grubx64.efi"), []byte{}, 0755), IsNil)
+
+ s.AddCleanup(secboot.MockCreateKeyResetter(func(key secboot.DiskUnlockKey, devicePath string) secboot.KeyResetter {
+ return &secboot.MockKeyResetter{}
+ }))
}
s.state.Lock()
diff --git a/overlord/devicestate/devicestate_install_mode_test.go b/overlord/devicestate/devicestate_install_mode_test.go
index f10c6ad341f..69d46f4ecfa 100644
--- a/overlord/devicestate/devicestate_install_mode_test.go
+++ b/overlord/devicestate/devicestate_install_mode_test.go
@@ -41,6 +41,7 @@ import (
"github.com/snapcore/snapd/gadget/install"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/osutil/disks"
"github.com/snapcore/snapd/overlord/auth"
"github.com/snapcore/snapd/overlord/devicestate"
"github.com/snapcore/snapd/overlord/devicestate/devicestatetest"
@@ -255,15 +256,15 @@ func (s *deviceMgrInstallModeSuite) doRunChangeTestWithEncryption(c *C, grade st
brOpts = options
installSealingObserver = obs
installRunCalled++
- var keyForRole map[string]keys.EncryptionKey
+ var resetterForRole map[string]secboot.KeyResetter
if tc.encrypt {
- keyForRole = map[string]keys.EncryptionKey{
- gadget.SystemData: dataEncryptionKey,
- gadget.SystemSave: saveKey,
+ resetterForRole = map[string]secboot.KeyResetter{
+ gadget.SystemData: &secboot.MockKeyResetter{},
+ gadget.SystemSave: &secboot.MockKeyResetter{},
}
}
return &install.InstalledSystemSideData{
- KeyForRole: keyForRole,
+ ResetterForRole: resetterForRole,
}, nil
})
defer restore()
@@ -1113,7 +1114,6 @@ func (s *deviceMgrInstallModeSuite) TestInstallSecuredWithTPMAndSave(c *C) {
tpm: true, bypass: false, encrypt: true, trustedBootloader: true,
})
c.Assert(err, IsNil)
- c.Check(filepath.Join(filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/device/fde"), "ubuntu-save.key"), testutil.FileEquals, []byte(saveKey))
marker, err := os.ReadFile(filepath.Join(filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/device/fde"), "marker"))
c.Assert(err, IsNil)
c.Check(marker, HasLen, 32)
@@ -1210,7 +1210,7 @@ func (s *deviceMgrInstallModeSuite) TestInstallEncryptionValidityChecksNoSystemD
// no keys set
return &install.InstalledSystemSideData{
// empty map
- KeyForRole: map[string]keys.EncryptionKey{},
+ ResetterForRole: map[string]secboot.KeyResetter{},
}, nil
})
defer restore()
@@ -1568,11 +1568,11 @@ func (s *deviceMgrInstallModeSuite) doRunFactoryResetChange(c *C, model *asserts
brOpts = options
installSealingObserver = obs
installFactoryResetCalled++
- var keyForRole map[string]keys.EncryptionKey
+ var resetterForRole map[string]secboot.KeyResetter
if tc.encrypt {
- keyForRole = map[string]keys.EncryptionKey{
- gadget.SystemData: dataEncryptionKey,
- gadget.SystemSave: saveKey,
+ resetterForRole = map[string]secboot.KeyResetter{
+ gadget.SystemData: &secboot.MockKeyResetter{},
+ gadget.SystemSave: &secboot.MockKeyResetter{},
}
}
devForRole := map[string]string{
@@ -1583,8 +1583,8 @@ func (s *deviceMgrInstallModeSuite) doRunFactoryResetChange(c *C, model *asserts
}
c.Assert(os.MkdirAll(dirs.SnapDeviceDirUnder(filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data")), 0755), IsNil)
return &install.InstalledSystemSideData{
- KeyForRole: keyForRole,
- DeviceForRole: devForRole,
+ ResetterForRole: resetterForRole,
+ DeviceForRole: devForRole,
}, nil
})
defer restore()
@@ -1614,32 +1614,19 @@ func (s *deviceMgrInstallModeSuite) doRunFactoryResetChange(c *C, model *asserts
s.makeMockInstalledPcKernelAndGadget(c, "", "")
s.state.Unlock()
- var saveKey keys.EncryptionKey
restore = devicestate.MockSecbootTransitionEncryptionKeyChange(func(node string, key keys.EncryptionKey) error {
c.Errorf("unexpected call")
return fmt.Errorf("unexpected call")
})
defer restore()
restore = devicestate.MockSecbootStageEncryptionKeyChange(func(node string, key keys.EncryptionKey) error {
- if tc.encrypt {
- c.Check(node, Equals, "/dev/foo-save")
- saveKey = key
- return nil
- }
- c.Fail()
+ c.Errorf("unexpected call")
return fmt.Errorf("unexpected call")
})
defer restore()
- var recoveryKeyRemoved bool
+ //var recoveryKeyRemoved bool
defer devicestate.MockSecbootRemoveRecoveryKeys(func(r2k map[secboot.RecoveryKeyDevice]string) error {
- if tc.encrypt {
- recoveryKeyRemoved = true
- c.Check(r2k, DeepEquals, map[secboot.RecoveryKeyDevice]string{
- {Mountpoint: boot.InitramfsUbuntuSaveDir}: filepath.Join(filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/device/fde"), "recovery.key"),
- })
- return nil
- }
c.Errorf("unexpected call")
return fmt.Errorf("unexpected call")
})()
@@ -1749,9 +1736,8 @@ func (s *deviceMgrInstallModeSuite) doRunFactoryResetChange(c *C, model *asserts
c.Assert(bootMakeBootableCalled, Equals, 1)
c.Assert(s.restartRequests, DeepEquals, []restart.RestartType{restart.RestartSystemNow})
if tc.encrypt {
- c.Assert(saveKey, NotNil)
- c.Check(recoveryKeyRemoved, Equals, true)
- c.Check(filepath.Join(filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/device/fde"), "ubuntu-save.key"), testutil.FileEquals, []byte(saveKey))
+ // TODO: verify keys are removed
+ //c.Check(recoveryKeyRemoved, Equals, true)
c.Check(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), testutil.FileEquals, "new-data")
// sha3-384 of the mocked ubuntu-save sealed key
c.Check(filepath.Join(dirs.SnapDeviceDirUnder(filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data")), "factory-reset"),
@@ -1904,6 +1890,18 @@ echo "mock output of: $(basename "$0") $*"
logbuf, restore := logger.MockLogger()
defer restore()
+ defer devicestate.MockCreateSaveResetter(func(saveNode string) (secboot.KeyResetter, error) {
+ return &secboot.MockKeyResetter{}, nil
+ })()
+ defer devicestate.MockDeleteOldSaveKey(func(saveMntPnt string) error {
+ return nil
+ })()
+ defer disks.MockUdevPropertiesForDevice(func(string, string) (map[string]string, error) {
+ return map[string]string{
+ "ID_PART_ENTRY_UUID": "fbbb94fb-46ea-4e00-b830-afc72d202449",
+ }, nil
+ })()
+
err = s.doRunFactoryResetChange(c, model, resetTestCase{
tpm: true, encrypt: true, trustedBootloader: true,
})
@@ -1968,6 +1966,18 @@ echo "mock output of: $(basename "$0") $*"
logbuf, restore := logger.MockLogger()
defer restore()
+ defer devicestate.MockCreateSaveResetter(func(saveNode string) (secboot.KeyResetter, error) {
+ return &secboot.MockKeyResetter{}, nil
+ })()
+ defer devicestate.MockDeleteOldSaveKey(func(saveMntPnt string) error {
+ return nil
+ })()
+ defer disks.MockUdevPropertiesForDevice(func(string, string) (map[string]string, error) {
+ return map[string]string{
+ "ID_PART_ENTRY_UUID": "fbbb94fb-46ea-4e00-b830-afc72d202449",
+ }, nil
+ })()
+
err = s.doRunFactoryResetChange(c, model, resetTestCase{
tpm: true, encrypt: true, trustedBootloader: true,
})
diff --git a/overlord/devicestate/devicestate_test.go b/overlord/devicestate/devicestate_test.go
index c2b79b73ffc..ff37658a57a 100644
--- a/overlord/devicestate/devicestate_test.go
+++ b/overlord/devicestate/devicestate_test.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2016-2022 Canonical Ltd
+ * Copyright (C) 2016-2024 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
@@ -51,6 +51,7 @@ import (
"github.com/snapcore/snapd/overlord/configstate/config"
"github.com/snapcore/snapd/overlord/devicestate"
"github.com/snapcore/snapd/overlord/devicestate/devicestatetest"
+ "github.com/snapcore/snapd/overlord/fdestate"
"github.com/snapcore/snapd/overlord/hookstate"
"github.com/snapcore/snapd/overlord/ifacestate/ifacerepo"
"github.com/snapcore/snapd/overlord/restart"
@@ -88,6 +89,7 @@ type deviceMgrBaseSuite struct {
hookMgr *hookstate.HookManager
mgr *devicestate.DeviceManager
db *asserts.Database
+ fdeMgr *fdestate.FDEManager
bootloader *bootloadertest.MockBootloader
@@ -240,6 +242,9 @@ func (s *deviceMgrBaseSuite) setupBaseTest(c *C, classic bool) {
err = db.Add(s.storeSigning.StoreAccountKey(""))
c.Assert(err, IsNil)
+ fdeMgr := fdestate.Manager(s.state, s.o.TaskRunner())
+ c.Assert(err, IsNil)
+
hookMgr, err := hookstate.Manager(s.state, s.o.TaskRunner())
c.Assert(err, IsNil)
@@ -255,6 +260,7 @@ func (s *deviceMgrBaseSuite) setupBaseTest(c *C, classic bool) {
s.hookMgr = hookMgr
s.o.AddManager(s.hookMgr)
s.mgr = mgr
+ s.fdeMgr = fdeMgr
s.o.AddManager(s.mgr)
s.o.AddManager(s.o.TaskRunner())
@@ -2194,8 +2200,13 @@ func (s *deviceMgrSuite) TestDeviceManagerEnsurePostFactoryResetEncrypted(c *C)
transitionCalls := 0
restore = devicestate.MockSecbootTransitionEncryptionKeyChange(func(mountpoint string, key keys.EncryptionKey) error {
transitionCalls++
- c.Check(mountpoint, Equals, boot.InitramfsUbuntuSaveDir)
- c.Check(key, DeepEquals, keys.EncryptionKey([]byte("save-key")))
+ return nil
+ })
+ defer restore()
+ deleteOldSaveKey := 0
+ restore = devicestate.MockDeleteOldSaveKey(func(saveMntPnt string) error {
+ c.Check(saveMntPnt, Equals, boot.InitramfsUbuntuSaveDir)
+ deleteOldSaveKey++
return nil
})
defer restore()
@@ -2204,13 +2215,15 @@ func (s *deviceMgrSuite) TestDeviceManagerEnsurePostFactoryResetEncrypted(c *C)
c.Assert(err, IsNil)
c.Check(completeCalls, Equals, 1)
- c.Check(transitionCalls, Equals, 1)
+ c.Check(transitionCalls, Equals, 0)
+ c.Check(deleteOldSaveKey, Equals, 1)
// factory reset marker is gone, the key was verified successfully
c.Check(filepath.Join(dirs.SnapDeviceDir, "factory-reset"), testutil.FileAbsent)
c.Check(filepath.Join(dirs.SnapFDEDir, "marker"), testutil.FilePresent)
completeCalls = 0
transitionCalls = 0
+ deleteOldSaveKey = 0
// try again, no marker, nothing should happen
devicestate.SetPostFactoryResetRan(s.mgr, false)
err = s.mgr.Ensure()
@@ -2218,6 +2231,7 @@ func (s *deviceMgrSuite) TestDeviceManagerEnsurePostFactoryResetEncrypted(c *C)
// nothing was called
c.Check(completeCalls, Equals, 0)
c.Check(transitionCalls, Equals, 0)
+ c.Check(deleteOldSaveKey, Equals, 0)
// have the marker, but migrate the key as if boot code would do it and
// try again, in this setup the marker hash matches the migrated key
@@ -2230,7 +2244,8 @@ func (s *deviceMgrSuite) TestDeviceManagerEnsurePostFactoryResetEncrypted(c *C)
err = s.mgr.Ensure()
c.Assert(err, IsNil)
c.Check(completeCalls, Equals, 1)
- c.Check(transitionCalls, Equals, 1)
+ c.Check(transitionCalls, Equals, 0)
+ c.Check(deleteOldSaveKey, Equals, 1)
// the marker was again removed
c.Check(filepath.Join(dirs.SnapDeviceDir, "factory-reset"), testutil.FileAbsent)
}
diff --git a/overlord/devicestate/export_test.go b/overlord/devicestate/export_test.go
index d51059577fe..6061dd4384e 100644
--- a/overlord/devicestate/export_test.go
+++ b/overlord/devicestate/export_test.go
@@ -594,3 +594,19 @@ func CleanUpEncryptionSetupDataInCache(st *state.State, label string) {
}
type UniqueSnapsInRecoverySystem = uniqueSnapsInRecoverySystem
+
+func MockCreateSaveResetter(f func(saveNode string) (secboot.KeyResetter, error)) func() {
+ old := createSaveResetter
+ createSaveResetter = f
+ return func() {
+ createSaveResetter = old
+ }
+}
+
+func MockDeleteOldSaveKey(f func(saveMntPnt string) error) func() {
+ old := deleteOldSaveKey
+ deleteOldSaveKey = f
+ return func() {
+ deleteOldSaveKey = old
+ }
+}
diff --git a/overlord/devicestate/handlers_install.go b/overlord/devicestate/handlers_install.go
index 436bf4112ff..34e3ab0db5c 100644
--- a/overlord/devicestate/handlers_install.go
+++ b/overlord/devicestate/handlers_install.go
@@ -44,13 +44,13 @@ import (
"github.com/snapcore/snapd/gadget/install"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/osutil/disks"
installLogic "github.com/snapcore/snapd/overlord/install"
"github.com/snapcore/snapd/overlord/restart"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/progress"
"github.com/snapcore/snapd/secboot"
- "github.com/snapcore/snapd/secboot/keys"
"github.com/snapcore/snapd/seed"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snapfile"
@@ -299,7 +299,7 @@ func (m *DeviceManager) doSetupRunSystem(t *state.Task, _ *tomb.Tomb) error {
}
if useEncryption {
- if err := installLogic.PrepareEncryptedSystemData(model, installedSystem.KeyForRole, trustedInstallObserver); err != nil {
+ if err := installLogic.PrepareEncryptedSystemData(model, installedSystem.ResetterForRole, trustedInstallObserver); err != nil {
return err
}
}
@@ -594,34 +594,24 @@ func (m *DeviceManager) doFactoryResetRunSystem(t *state.Task, _ *tomb.Tomb) err
return fmt.Errorf("cannot cleanup obsolete key file: %v", err)
}
- // it is ok if the recovery key file on disk does not exist;
- // ubuntu-save was opened during boot, so the removal operation
- // can be authorized with a key from the keyring
- err = secbootRemoveRecoveryKeys(map[secboot.RecoveryKeyDevice]string{
- {Mountpoint: boot.InitramfsUbuntuSaveDir}: device.RecoveryKeyUnder(boot.InstallHostFDEDataDir(model)),
- })
- if err != nil {
- return fmt.Errorf("cannot remove recovery key: %v", err)
- }
-
- // new encryption key for save
- saveEncryptionKey, err := keys.NewEncryptionKey()
- if err != nil {
- return fmt.Errorf("cannot create encryption key: %v", err)
- }
-
saveNode := installedSystem.DeviceForRole[gadget.SystemSave]
if saveNode == "" {
return fmt.Errorf("internal error: no system-save device")
}
+ uuid, err := disks.PartitionUUID(saveNode)
+ if err != nil {
+ return fmt.Errorf("cannot find uuid for partition %s: %v", saveNode, err)
+ }
+ saveNode = fmt.Sprintf("/dev/disk/by-partuuid/%s", uuid)
- if err := secbootStageEncryptionKeyChange(saveNode, saveEncryptionKey); err != nil {
- return fmt.Errorf("cannot change encryption keys: %v", err)
+ saveResetter, err := createSaveResetter(saveNode)
+ if err != nil {
+ return err
}
- // keep track of the new ubuntu-save encryption key
- installedSystem.KeyForRole[gadget.SystemSave] = saveEncryptionKey
- if err := installLogic.PrepareEncryptedSystemData(model, installedSystem.KeyForRole, trustedInstallObserver); err != nil {
+ installedSystem.ResetterForRole[gadget.SystemSave] = saveResetter
+
+ if err := installLogic.PrepareEncryptedSystemData(model, installedSystem.ResetterForRole, trustedInstallObserver); err != nil {
return err
}
}
@@ -848,18 +838,6 @@ func verifyFactoryResetMarkerInRun(marker string, hasEncryption bool) error {
return nil
}
-func rotateEncryptionKeys() error {
- kd, err := os.ReadFile(filepath.Join(dirs.SnapFDEDir, "ubuntu-save.key"))
- if err != nil {
- return fmt.Errorf("cannot open encryption key file: %v", err)
- }
- // does the right thing if the key has already been transitioned
- if err := secbootTransitionEncryptionKeyChange(boot.InitramfsUbuntuSaveDir, keys.EncryptionKey(kd)); err != nil {
- return fmt.Errorf("cannot transition the encryption key: %v", err)
- }
- return nil
-}
-
type encryptionSetupDataKey struct {
systemLabel string
}
@@ -1085,7 +1063,7 @@ func (m *DeviceManager) doInstallFinish(t *state.Task, _ *tomb.Tomb) error {
if useEncryption {
if trustedInstallObserver != nil {
- if err := installLogic.PrepareEncryptedSystemData(systemAndSnaps.Model, install.KeysForRole(encryptSetupData), trustedInstallObserver); err != nil {
+ if err := installLogic.PrepareEncryptedSystemData(systemAndSnaps.Model, install.ResetterForRole(encryptSetupData), trustedInstallObserver); err != nil {
return err
}
}
diff --git a/overlord/devicestate/handlers_install_nosb.go b/overlord/devicestate/handlers_install_nosb.go
new file mode 100644
index 00000000000..e2dc973c159
--- /dev/null
+++ b/overlord/devicestate/handlers_install_nosb.go
@@ -0,0 +1,39 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build nosecboot
+
+/*
+ * Copyright (C) 2024 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 .
+ *
+ */
+
+package devicestate
+
+import (
+ "fmt"
+
+ "github.com/snapcore/snapd/secboot"
+)
+
+func createSaveResetterImpl(saveNode string) (secboot.KeyResetter, error) {
+ return nil, fmt.Errorf("not implemented")
+}
+
+var createSaveResetter = createSaveResetterImpl
+
+func deleteOldSaveKeyImpl(saveMntPt string) error {
+ return fmt.Errorf("not implemented")
+}
+
+var deleteOldSaveKey = deleteOldSaveKeyImpl
diff --git a/overlord/devicestate/handlers_install_sb.go b/overlord/devicestate/handlers_install_sb.go
new file mode 100644
index 00000000000..42620d0c1a2
--- /dev/null
+++ b/overlord/devicestate/handlers_install_sb.go
@@ -0,0 +1,77 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !nosecboot
+
+/*
+ * Copyright (C) 2024 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 .
+ *
+ */
+
+package devicestate
+
+import (
+ "fmt"
+ "path/filepath"
+
+ "github.com/snapcore/snapd/osutil/disks"
+ "github.com/snapcore/snapd/secboot"
+ "github.com/snapcore/snapd/secboot/keys"
+)
+
+func createSaveResetterImpl(saveNode string) (secboot.KeyResetter, error) {
+ // new encryption key for save
+ saveEncryptionKey, err := keys.NewEncryptionKey()
+ if err != nil {
+ return nil, fmt.Errorf("cannot create encryption key: %v", err)
+ }
+
+ if err := secboot.AddInstallationKeyOnExistingDisk(saveNode, saveEncryptionKey); err != nil {
+ return nil, err
+ }
+
+ renames := map[string]string{
+ "default": "factory-reset-old",
+ "default-fallback": "factory-reset-old-fallback",
+ "save": "factory-reset-old-save",
+ }
+ if err := secboot.RenameOrDeleteKeys(saveNode, renames); err != nil {
+ return nil, err
+ }
+
+ return secboot.CreateKeyResetter(secboot.DiskUnlockKey(saveEncryptionKey), saveNode), nil
+}
+
+var createSaveResetter = createSaveResetterImpl
+
+func deleteOldSaveKeyImpl(saveMntPnt string) error {
+ // FIXME: maybe there is better if we had a function returning the devname instead.
+ partUUID, err := disks.PartitionUUIDFromMountPoint(saveMntPnt, &disks.Options{
+ IsDecryptedDevice: true,
+ })
+ if err != nil {
+ return fmt.Errorf("cannot partition save partition: %v", err)
+ }
+
+ diskPath := filepath.Join("/dev/disk/by-partuuid", partUUID)
+
+ toDelete := map[string]bool{
+ "factory-reset-old": true,
+ "factory-reset-old-fallback": true,
+ "factory-reset-old-save": true,
+ }
+
+ return secboot.DeleteKeys(diskPath, toDelete)
+}
+
+var deleteOldSaveKey = deleteOldSaveKeyImpl
diff --git a/overlord/fdestate/fdemgr.go b/overlord/fdestate/fdemgr.go
new file mode 100644
index 00000000000..51ad9c88881
--- /dev/null
+++ b/overlord/fdestate/fdemgr.go
@@ -0,0 +1,138 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2024 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 .
+ *
+ */
+
+package fdestate
+
+import (
+ "fmt"
+
+ "github.com/snapcore/snapd/boot"
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/gadget/device"
+ "github.com/snapcore/snapd/overlord/state"
+)
+
+// ServiceManager is responsible for starting and stopping snap services.
+type FDEManager struct {
+ state *state.State
+}
+
+type fdeManagerKey struct{}
+
+func Manager(st *state.State, runner *state.TaskRunner) *FDEManager {
+ m := &FDEManager{
+ state: st,
+ }
+
+ boot.ProvideResealKeyToModeenv(func(rootdir string, modeenv *boot.Modeenv, expectReseal bool, unlocker boot.Unlocker) error {
+ return resealLocked(m.state, modeenv, expectReseal)
+ })
+
+ st.Lock()
+ st.Cache(fdeManagerKey{}, m)
+ st.Unlock()
+
+ return m
+}
+
+func (m *FDEManager) Ensure() error {
+ return nil
+}
+
+func (m *FDEManager) Stop() {
+ boot.ProvideResealKeyToModeenv(func(rootdir string, modeenv *boot.Modeenv, expectReseal bool, unlocker boot.Unlocker) error {
+ return fmt.Errorf("fde manager is disabled")
+ })
+}
+
+/*
+func getManager(st *state.State) (*FDEManager, error) {
+ c := st.Cached(fdeManagerKey{})
+ if c == nil {
+ return nil, fmt.Errorf("no FDE manager found")
+ }
+ manager := c.(*FDEManager)
+ if manager == nil {
+ return nil, fmt.Errorf("FDE manager found has wrong type")
+ }
+
+ return manager, nil
+}
+*/
+
+func resealWithHookLocked(st *state.State, modeenv *boot.Modeenv, expectReseal bool) error {
+ return boot.ResealKeyToModeenvUsingFDESetupHook(dirs.GlobalRootDir, modeenv, expectReseal)
+}
+
+func resealWithSecbootLocked(st *state.State, modeenv *boot.Modeenv, expectReseal bool) error {
+ st.Unlock()
+ defer st.Lock()
+ return boot.ResealKeyToModeenvSecboot(dirs.GlobalRootDir, modeenv, expectReseal)
+}
+
+func resealNextGenLocked(st *state.State, modeenv *boot.Modeenv, expectReseal bool) error {
+ st.Unlock()
+ defer st.Lock()
+
+ return boot.ResealKeyToModeenvNextGeneration(dirs.GlobalRootDir, modeenv, expectReseal)
+}
+
+func resealLocked(st *state.State, modeenv *boot.Modeenv, expectReseal bool) error {
+ /*
+ manager, err := getManager(st)
+ if err != nil {
+ return err
+ }
+ */
+
+ if !boot.IsModeeenvLocked() {
+ return fmt.Errorf("modeenv is not locked")
+ }
+
+ method, err := device.SealedKeysMethod(dirs.GlobalRootDir)
+ if err == device.ErrNoSealedKeys {
+ return nil
+ }
+ if err != nil {
+ return err
+ }
+ switch method {
+ case device.SealingMethodFDESetupHook:
+ return resealWithHookLocked(st, modeenv, expectReseal)
+ case device.SealingMethodTPM, device.SealingMethodLegacyTPM:
+ return resealWithSecbootLocked(st, modeenv, expectReseal)
+ case device.SealingMethodNextGeneration:
+ return resealNextGenLocked(st, modeenv, expectReseal)
+ default:
+ return fmt.Errorf("unknown key sealing method: %q", method)
+ }
+}
+
+func Reseal(st *state.State, modeenv *boot.Modeenv, expectReseal bool) error {
+ st.Lock()
+ defer st.Unlock()
+
+ return resealLocked(st, modeenv, expectReseal)
+}
+
+func init() {
+ boot.ProvideResealKeyToModeenv(func(rootdir string, modeenv *boot.Modeenv, expectReseal bool, unlocker boot.Unlocker) error {
+ return fmt.Errorf("fde manager is disabled")
+ })
+}
diff --git a/overlord/install/install.go b/overlord/install/install.go
index 9c974ed57d8..926ab7e3ae7 100644
--- a/overlord/install/install.go
+++ b/overlord/install/install.go
@@ -244,19 +244,43 @@ func BuildInstallObserver(model *asserts.Model, gadgetDir string, useEncryption
// * save keys and markers for ubuntu-data being able to safely open ubuntu-save
// It is the responsibility of the caller to call
// ObserveExistingTrustedRecoveryAssets on trustedInstallObserver.
-func PrepareEncryptedSystemData(model *asserts.Model, keyForRole map[string]keys.EncryptionKey, trustedInstallObserver boot.TrustedAssetsInstallObserver) error {
+func PrepareEncryptedSystemData(model *asserts.Model, resetterForRole map[string]secboot.KeyResetter, trustedInstallObserver boot.TrustedAssetsInstallObserver) error {
// validity check
- if len(keyForRole) == 0 || keyForRole[gadget.SystemData] == nil || keyForRole[gadget.SystemSave] == nil {
+ if len(resetterForRole) == 0 || resetterForRole[gadget.SystemData] == nil || resetterForRole[gadget.SystemSave] == nil {
return fmt.Errorf("internal error: system encryption keys are unset")
}
- dataEncryptionKey := keyForRole[gadget.SystemData]
- saveEncryptionKey := keyForRole[gadget.SystemSave]
+ dataResetter := resetterForRole[gadget.SystemData]
+ saveResetter := resetterForRole[gadget.SystemSave]
// make note of the encryption keys
- trustedInstallObserver.ChosenEncryptionKeys(dataEncryptionKey, saveEncryptionKey)
+ trustedInstallObserver.ChosenEncryptionKeys(dataResetter, saveResetter)
- if err := saveKeys(model, keyForRole); err != nil {
- return err
+ // XXX is the asset cache problematic from initramfs?
+ // keep track of recovery assets
+ if err := trustedInstallObserver.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir); err != nil {
+ return fmt.Errorf("cannot observe existing trusted recovery assets: %v", err)
+ }
+
+ if saveResetter != nil {
+ platformKey, err := keys.NewPlatformKey()
+ if err != nil {
+ return err
+ }
+ plainKey, diskKey, err := platformKey.CreateProtectedKey()
+ if err != nil {
+ return err
+ }
+ const token = true
+ tokenWriter, err := saveResetter.AddKey("default", diskKey, token)
+ if err != nil {
+ return err
+ }
+ if err := plainKey.Write(tokenWriter); err != nil {
+ return err
+ }
+ if err := saveKeys(model, platformKey); err != nil {
+ return err
+ }
}
// write markers containing a secret to pair data and save
if err := writeMarkers(model); err != nil {
@@ -284,17 +308,11 @@ func writeMarkers(model *asserts.Model) error {
return device.WriteEncryptionMarkers(boot.InstallHostFDEDataDir(model), boot.InstallHostFDESaveDir, markerSecret)
}
-func saveKeys(model *asserts.Model, keyForRole map[string]keys.EncryptionKey) error {
- saveEncryptionKey := keyForRole[gadget.SystemSave]
- if saveEncryptionKey == nil {
- // no system-save support
- return nil
- }
- // ensure directory for keys exists
+func saveKeys(model *asserts.Model, saveKey keys.PlatformKey) error {
if err := os.MkdirAll(boot.InstallHostFDEDataDir(model), 0755); err != nil {
return err
}
- if err := saveEncryptionKey.Save(device.SaveKeyUnder(boot.InstallHostFDEDataDir(model))); err != nil {
+ if err := saveKey.SaveToFile(device.SaveKeyUnder(boot.InstallHostFDEDataDir(model))); err != nil {
return fmt.Errorf("cannot store system save key: %v", err)
}
return nil
diff --git a/overlord/install/install_test.go b/overlord/install/install_test.go
index a82d3a22cfa..0bb6076dfb7 100644
--- a/overlord/install/install_test.go
+++ b/overlord/install/install_test.go
@@ -893,14 +893,13 @@ func (s *installSuite) TestPrepareEncryptedSystemData(c *C) {
err = to.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir)
c.Assert(err, IsNil)
- keyForRole := map[string]keys.EncryptionKey{
- gadget.SystemData: dataEncryptionKey,
- gadget.SystemSave: saveKey,
+ keyForRole := map[string]secboot.KeyResetter{
+ gadget.SystemData: &secboot.MockKeyResetter{},
+ gadget.SystemSave: &secboot.MockKeyResetter{},
}
err = install.PrepareEncryptedSystemData(mockModel, keyForRole, to)
c.Assert(err, IsNil)
- c.Check(filepath.Join(filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/device/fde"), "ubuntu-save.key"), testutil.FileEquals, []byte(saveKey))
marker, err := os.ReadFile(filepath.Join(filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/device/fde"), "marker"))
c.Assert(err, IsNil)
c.Check(marker, HasLen, 32)
diff --git a/overlord/overlord.go b/overlord/overlord.go
index 9a5d8e34109..b80cef19312 100644
--- a/overlord/overlord.go
+++ b/overlord/overlord.go
@@ -2,7 +2,7 @@
//go:build !nomanagers
/*
- * Copyright (C) 2016-2022 Canonical Ltd
+ * Copyright (C) 2016-2024 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
@@ -42,6 +42,7 @@ import (
"github.com/snapcore/snapd/overlord/configstate"
"github.com/snapcore/snapd/overlord/configstate/proxyconf"
"github.com/snapcore/snapd/overlord/devicestate"
+ "github.com/snapcore/snapd/overlord/fdestate"
"github.com/snapcore/snapd/overlord/healthstate"
"github.com/snapcore/snapd/overlord/hookstate"
"github.com/snapcore/snapd/overlord/ifacestate"
@@ -111,6 +112,7 @@ type Overlord struct {
deviceMgr *devicestate.DeviceManager
cmdMgr *cmdstate.CommandManager
shotMgr *snapshotstate.SnapshotManager
+ fdeMgr *fdestate.FDEManager
// proxyConf mediates the http proxy config
proxyConf func(req *http.Request) (*url.URL, error)
}
@@ -188,6 +190,8 @@ func New(restartHandler restart.Handler) (*Overlord, error) {
// the shared task runner should be added last!
o.stateEng.AddManager(o.runner)
+ o.addManager(fdestate.Manager(s, o.runner))
+
s.Lock()
defer s.Unlock()
// setting up the store
@@ -220,6 +224,8 @@ func (o *Overlord) addManager(mgr StateManager) {
o.shotMgr = x
case *restart.RestartManager:
o.restartMgr = x
+ case *fdestate.FDEManager:
+ o.fdeMgr = x
}
o.stateEng.AddManager(mgr)
}
@@ -671,6 +677,11 @@ func (o *Overlord) CommandManager() *cmdstate.CommandManager {
return o.cmdMgr
}
+// FDEManager returns the manager responsible for FDE
+func (o *Overlord) FDEManager() *fdestate.FDEManager {
+ return o.fdeMgr
+}
+
// SnapshotManager returns the manager responsible for snapshots.
func (o *Overlord) SnapshotManager() *snapshotstate.SnapshotManager {
return o.shotMgr
diff --git a/secboot/encrypt_sb.go b/secboot/encrypt_sb.go
index e04773dac93..3d3b41a3860 100644
--- a/secboot/encrypt_sb.go
+++ b/secboot/encrypt_sb.go
@@ -2,7 +2,7 @@
//go:build !nosecboot
/*
- * Copyright (C) 2022 Canonical Ltd
+ * Copyright (C) 2022-2024 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
@@ -25,6 +25,7 @@ import (
"encoding/json"
"fmt"
"io"
+ "os"
"path/filepath"
sb "github.com/snapcore/secboot"
@@ -38,8 +39,12 @@ import (
)
var (
- sbInitializeLUKS2Container = sb.InitializeLUKS2Container
- sbAddRecoveryKeyToLUKS2Container = sb.AddRecoveryKeyToLUKS2Container
+ sbInitializeLUKS2Container = sb.InitializeLUKS2Container
+ sbGetDiskUnlockKeyFromKernel = sb.GetDiskUnlockKeyFromKernel
+ sbAddLUKS2ContainerRecoveryKey = sb.AddLUKS2ContainerRecoveryKey
+ sbListLUKS2ContainerUnlockKeyNames = sb.ListLUKS2ContainerUnlockKeyNames
+ sbDeleteLUKS2ContainerKey = sb.DeleteLUKS2ContainerKey
+ sbListLUKS2ContainerRecoveryKeyNames = sb.ListLUKS2ContainerRecoveryKeyNames
)
const keyslotsAreaKiBSize = 2560 // 2.5MB
@@ -48,7 +53,7 @@ const metadataKiBSize = 2048 // 2MB
// FormatEncryptedDevice initializes an encrypted volume on the block device
// given by node, setting the specified label. The key used to unlock the volume
// is provided using the key argument.
-func FormatEncryptedDevice(key keys.EncryptionKey, encType EncryptionType, label, node string) error {
+func FormatEncryptedDevice(key []byte, encType EncryptionType, label, node string) error {
if !encType.IsLUKS() {
return fmt.Errorf("internal error: FormatEncryptedDevice for %q expects a LUKS encryption type, not %q", node, encType)
}
@@ -61,17 +66,10 @@ func FormatEncryptedDevice(key keys.EncryptionKey, encType EncryptionType, label
// enough room
MetadataKiBSize: metadataKiBSize,
KeyslotsAreaKiBSize: keyslotsAreaKiBSize,
-
- // Use fixed parameters for the KDF to avoid the
- // benchmark. This is okay because we have a high
- // entropy key and the KDF does not gain us much.
- KDFOptions: &sb.KDFOptions{
- MemoryKiB: 32,
- ForceIterations: 4,
- },
- InlineCryptoEngine: useICE,
+ InlineCryptoEngine: useICE,
+ InitialKeyslotName: "installation-key",
}
- return sbInitializeLUKS2Container(node, label, key[:], opts)
+ return sbInitializeLUKS2Container(node, label, sb.DiskUnlockKey(key), opts)
}
// AddRecoveryKey adds a fallback recovery key rkey to the existing encrypted
@@ -103,36 +101,119 @@ func runSnapFDEKeymgr(args []string, stdin io.Reader) error {
// EnsureRecoveryKey makes sure the encrypted block devices have a recovery key.
// It takes the path where to store the key and encrypted devices to operate on.
func EnsureRecoveryKey(keyFile string, rkeyDevs []RecoveryKeyDevice) (keys.RecoveryKey, error) {
- // support multiple devices with the same key
- command := []string{
- "add-recovery-key",
- "--key-file", keyFile,
+ var legacyCmdline []string
+ var newDevices []struct {
+ node string
+ keyFile string
}
for _, rkeyDev := range rkeyDevs {
dev, err := devByPartUUIDFromMount(rkeyDev.Mountpoint)
if err != nil {
return keys.RecoveryKey{}, fmt.Errorf("cannot find matching device for: %v", err)
}
- logger.Debugf("ensuring recovery key on device: %v", dev)
- authzMethod := "keyring"
- if rkeyDev.AuthorizingKeyFile != "" {
- authzMethod = "file:" + rkeyDev.AuthorizingKeyFile
+ slots, err := sbListLUKS2ContainerUnlockKeyNames(dev)
+ if err != nil {
+ return keys.RecoveryKey{}, fmt.Errorf("cannot find list keys for: %v", err)
+ }
+ if len(slots) == 0 {
+ authzMethod := "keyring"
+ if rkeyDev.AuthorizingKeyFile != "" {
+ authzMethod = "file:" + rkeyDev.AuthorizingKeyFile
+ }
+ legacyCmdline = append(legacyCmdline, []string{
+ "--devices", dev,
+ "--authorizations", authzMethod,
+ }...)
+ } else {
+ newDevices = append(newDevices, struct {
+ node string
+ keyFile string
+ }{dev, rkeyDev.AuthorizingKeyFile})
}
- command = append(command, []string{
- "--devices", dev,
- "--authorizations", authzMethod,
- }...)
}
-
- if err := runSnapFDEKeymgr(command, nil); err != nil {
- return keys.RecoveryKey{}, fmt.Errorf("cannot run keymgr tool: %v", err)
+ if len(legacyCmdline) != 0 && len(newDevices) != 0 {
+ return keys.RecoveryKey{}, fmt.Errorf("some encrypted partitions use new slots, whereas other use legacy slots")
}
+ if len(legacyCmdline) == 0 {
+ var recoveryKey keys.RecoveryKey
- rk, err := keys.RecoveryKeyFromFile(keyFile)
- if err != nil {
- return keys.RecoveryKey{}, fmt.Errorf("cannot read recovery key: %v", err)
+ f, err := os.OpenFile(keyFile, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600)
+ if err != nil {
+ if os.IsExist(err) {
+ readKey, err := keys.RecoveryKeyFromFile(keyFile)
+ if err != nil {
+ return keys.RecoveryKey{}, fmt.Errorf("cannot read recovery key file %s: %v", keyFile, err)
+ }
+ recoveryKey = *readKey
+ } else {
+ return keys.RecoveryKey{}, fmt.Errorf("cannot open recovery key file %s: %v", keyFile, err)
+ }
+ } else {
+ defer f.Close()
+ newKey, err := keys.NewRecoveryKey()
+ if err != nil {
+ return keys.RecoveryKey{}, fmt.Errorf("cannot create new recovery key: %v", err)
+ }
+ recoveryKey = newKey
+ if _, err := f.Write(recoveryKey[:]); err != nil {
+ return keys.RecoveryKey{}, fmt.Errorf("cannot create write recovery key %s: %v", keyFile, err)
+ }
+ }
+
+ for _, device := range newDevices {
+ var unlockKey []byte
+ if device.keyFile != "" {
+ key, err := os.ReadFile(device.keyFile)
+ if err != nil {
+ return keys.RecoveryKey{}, fmt.Errorf("cannot get key from '%s': %v", device.keyFile, err)
+ }
+ unlockKey = key
+ } else {
+ const defaultPrefix = "ubuntu-fde"
+ key, err := sbGetDiskUnlockKeyFromKernel(defaultPrefix, device.node, false)
+ if err != nil {
+ return keys.RecoveryKey{}, fmt.Errorf("cannot get key for unlocked disk: %v", err)
+ }
+ unlockKey = key
+ }
+
+ // FIXME: we should try to enroll the key and check the error instead of verifying the key is there
+ slots, err := sbListLUKS2ContainerRecoveryKeyNames(device.node)
+ if err != nil {
+ return keys.RecoveryKey{}, fmt.Errorf("cannot list keys on disk %s: %v", device.node, err)
+ }
+ keyExists := false
+ for _, slot := range slots {
+ if slot == "default-recovery" {
+ keyExists = true
+ break
+ }
+ }
+ if !keyExists {
+ if err := sbAddLUKS2ContainerRecoveryKey(device.node, "default-recovery", sb.DiskUnlockKey(unlockKey), sb.RecoveryKey(recoveryKey)); err != nil {
+ return keys.RecoveryKey{}, fmt.Errorf("cannot enroll new recovery key for %s: %v", device.node, err)
+ }
+ }
+ }
+
+ return recoveryKey, nil
+ } else {
+ command := []string{
+ "add-recovery-key",
+ "--key-file", keyFile,
+ }
+ command = append(command, legacyCmdline...)
+
+ if err := runSnapFDEKeymgr(command, nil); err != nil {
+ return keys.RecoveryKey{}, fmt.Errorf("cannot run keymgr tool: %v", err)
+ }
+
+ rk, err := keys.RecoveryKeyFromFile(keyFile)
+ if err != nil {
+ return keys.RecoveryKey{}, fmt.Errorf("cannot read recovery key: %v", err)
+ }
+ return *rk, nil
}
- return *rk, nil
}
func devByPartUUIDFromMount(mp string) (string, error) {
@@ -150,31 +231,65 @@ func devByPartUUIDFromMount(mp string) (string, error) {
// It takes a map from the recovery key device to where their recovery key is
// stored, mount points might share the latter.
func RemoveRecoveryKeys(rkeyDevToKey map[RecoveryKeyDevice]string) error {
- // support multiple devices and key files
- command := []string{
- "remove-recovery-key",
- }
+ var legacyCmdline []string
+ var newDevices []string
+ var keyFiles []string
+
for rkeyDev, keyFile := range rkeyDevToKey {
dev, err := devByPartUUIDFromMount(rkeyDev.Mountpoint)
if err != nil {
return fmt.Errorf("cannot find matching device for: %v", err)
}
- logger.Debugf("removing recovery key from device: %v", dev)
- authzMethod := "keyring"
- if rkeyDev.AuthorizingKeyFile != "" {
- authzMethod = "file:" + rkeyDev.AuthorizingKeyFile
+ slots, err := sbListLUKS2ContainerUnlockKeyNames(dev)
+ if err != nil {
+ return fmt.Errorf("cannot find list keys for: %v", err)
+ }
+ if len(slots) == 0 {
+ logger.Debugf("removing recovery key from device: %v", dev)
+ authzMethod := "keyring"
+ if rkeyDev.AuthorizingKeyFile != "" {
+ authzMethod = "file:" + rkeyDev.AuthorizingKeyFile
+ }
+ legacyCmdline = append(legacyCmdline, []string{
+ "--devices", dev,
+ "--authorizations", authzMethod,
+ "--key-files", keyFile,
+ }...)
+ } else {
+ newDevices = append(newDevices, dev)
+ keyFiles = append(keyFiles, keyFile)
}
- command = append(command, []string{
- "--devices", dev,
- "--authorizations", authzMethod,
- "--key-files", keyFile,
- }...)
}
- if err := runSnapFDEKeymgr(command, nil); err != nil {
- return fmt.Errorf("cannot run keymgr tool: %v", err)
+ if len(legacyCmdline) != 0 && len(newDevices) != 0 {
+ return fmt.Errorf("some encrypted partitions use new slots, whereas other use legacy slots")
+ }
+ if len(legacyCmdline) == 0 {
+ for _, device := range newDevices {
+ if err := sbDeleteLUKS2ContainerKey(device, "default-recovery"); err != nil {
+ return fmt.Errorf("cannot remove recovery key: %v", err)
+ }
+ }
+ for _, keyFile := range keyFiles {
+ if err := os.Remove(keyFile); err != nil && !os.IsNotExist(err) {
+ return fmt.Errorf("cannot remove key file %s: %v", keyFile, err)
+ }
+ }
+
+ return nil
+
+ } else {
+ // support multiple devices and key files
+ command := []string{
+ "remove-recovery-key",
+ }
+ command = append(command, legacyCmdline...)
+
+ if err := runSnapFDEKeymgr(command, nil); err != nil {
+ return fmt.Errorf("cannot run keymgr tool: %v", err)
+ }
+ return nil
}
- return nil
}
// StageEncryptionKeyChange stages a new encryption key for a given encrypted
diff --git a/secboot/encrypt_sb_test.go b/secboot/encrypt_sb_test.go
index 9a7797b2a3d..f75ebeb3c75 100644
--- a/secboot/encrypt_sb_test.go
+++ b/secboot/encrypt_sb_test.go
@@ -2,7 +2,7 @@
//go:build !nosecboot
/*
- * Copyright (C) 2022 Canonical Ltd
+ * Copyright (C) 2022-2024 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
@@ -25,6 +25,7 @@ import (
"encoding/json"
"errors"
"fmt"
+ "os"
"path/filepath"
sb "github.com/snapcore/secboot"
@@ -54,19 +55,16 @@ func (s *encryptSuite) TestFormatEncryptedDevice(c *C) {
}
calls := 0
- restore := secboot.MockSbInitializeLUKS2Container(func(devicePath, label string, key []byte,
+ restore := secboot.MockSbInitializeLUKS2Container(func(devicePath, label string, key sb.DiskUnlockKey,
opts *sb.InitializeLUKS2ContainerOptions) error {
calls++
c.Assert(devicePath, Equals, "/dev/node")
c.Assert(label, Equals, "my label")
- c.Assert(key, DeepEquals, []byte(myKey))
+ c.Assert(key, DeepEquals, sb.DiskUnlockKey(myKey))
c.Assert(opts, DeepEquals, &sb.InitializeLUKS2ContainerOptions{
MetadataKiBSize: 2048,
KeyslotsAreaKiBSize: 2560,
- KDFOptions: &sb.KDFOptions{
- MemoryKiB: 32,
- ForceIterations: 4,
- },
+ InitialKeyslotName: "installation-key",
})
return tc.initErr
})
@@ -347,6 +345,50 @@ done
func (s *keymgrSuite) TestEnsureRecoveryKey(c *C) {
udevadmCmd := s.mocksForDeviceMounts(c)
+ defer secboot.MockListLUKS2ContainerUnlockKeyNames(func(devicePath string) ([]string, error) {
+ return []string{"default"}, nil
+ })()
+ defer secboot.MockListLUKS2ContainerRecoveryKeyNames(func(devicePath string) ([]string, error) {
+ return []string{}, nil
+ })()
+ defer secboot.MockGetDiskUnlockKeyFromKernel(func(prefix string, devicePath string, remove bool) (sb.DiskUnlockKey, error) {
+ return []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, nil
+ })()
+ defer secboot.MockAddLUKS2ContainerRecoveryKey(func(devicePath string, keyslotName string, existingKey sb.DiskUnlockKey, recoveryKey sb.RecoveryKey) error {
+ return nil
+ })()
+
+ keyFilePath := filepath.Join(c.MkDir(), "key.file")
+ err := os.WriteFile(keyFilePath, []byte{}, 0644)
+ c.Assert(err, IsNil)
+ _, err = secboot.EnsureRecoveryKey(filepath.Join(s.d, "recovery.key"), []secboot.RecoveryKeyDevice{
+ {Mountpoint: "/foo"},
+ {Mountpoint: "/bar", AuthorizingKeyFile: keyFilePath},
+ })
+ c.Assert(err, IsNil)
+ c.Check(udevadmCmd.Calls(), DeepEquals, [][]string{
+ {"udevadm", "info", "--query", "property", "--name", "/dev/mapper/foo"},
+ {"udevadm", "info", "--query", "property", "--name", "/dev/disk/by-uuid/5a522809-c87e-4dfa-81a8-8dc5667d1304"},
+ {"udevadm", "info", "--query", "property", "--name", "/dev/mapper/bar"},
+ {"udevadm", "info", "--query", "property", "--name", "/dev/disk/by-uuid/5a522809-c87e-4dfa-81a8-8dc5667d1305"},
+ })
+
+}
+
+func (s *keymgrSuite) TestEnsureRecoveryKeyLegacy(c *C) {
+ udevadmCmd := s.mocksForDeviceMounts(c)
+
+ defer secboot.MockListLUKS2ContainerUnlockKeyNames(func(devicePath string) ([]string, error) {
+ return []string{}, nil
+ })()
+ defer secboot.MockGetDiskUnlockKeyFromKernel(func(prefix string, devicePath string, remove bool) (sb.DiskUnlockKey, error) {
+ c.Errorf("unexpected call")
+ return sb.DiskUnlockKey{}, nil
+ })()
+ defer secboot.MockAddLUKS2ContainerRecoveryKey(func(devicePath string, keyslotName string, existingKey sb.DiskUnlockKey, recoveryKey sb.RecoveryKey) error {
+ c.Errorf("unexpected call")
+ return nil
+ })()
rkey, err := secboot.EnsureRecoveryKey(filepath.Join(s.d, "recovery.key"), []secboot.RecoveryKeyDevice{
{Mountpoint: "/foo"},
{Mountpoint: "/bar", AuthorizingKeyFile: "/authz/key.file"},
@@ -385,6 +427,49 @@ func (s *keymgrSuite) TestEnsureRecoveryKey(c *C) {
func (s *keymgrSuite) TestRemoveRecoveryKey(c *C) {
udevadmCmd := s.mocksForDeviceMounts(c)
+ defer secboot.MockListLUKS2ContainerUnlockKeyNames(func(devicePath string) ([]string, error) {
+ return []string{"default", "default-recovery"}, nil
+ })()
+ defer secboot.MockRemoveLUKS2ContainerKey(func(devicePath string, keyslotName string) error {
+ c.Assert(keyslotName, Equals, "default-recovery")
+ return nil
+ })()
+
+ snaptest.PopulateDir(s.d, [][]string{
+ {"recovery.key", "foobar"},
+ })
+ // only one of the key files exists
+ err := secboot.RemoveRecoveryKeys(map[secboot.RecoveryKeyDevice]string{
+ {Mountpoint: "/foo"}: filepath.Join(s.d, "recovery.key"),
+ {Mountpoint: "/bar", AuthorizingKeyFile: "/authz/key.file"}: filepath.Join(s.d, "missing-recovery.key"),
+ })
+ c.Assert(err, IsNil)
+
+ expectedUdevCalls := [][]string{
+ // order can change depending on map iteration
+ {"udevadm", "info", "--query", "property", "--name", "/dev/mapper/foo"},
+ {"udevadm", "info", "--query", "property", "--name", "/dev/disk/by-uuid/5a522809-c87e-4dfa-81a8-8dc5667d1304"},
+ {"udevadm", "info", "--query", "property", "--name", "/dev/mapper/bar"},
+ {"udevadm", "info", "--query", "property", "--name", "/dev/disk/by-uuid/5a522809-c87e-4dfa-81a8-8dc5667d1305"},
+ }
+
+ udevCalls := udevadmCmd.Calls()
+ c.Assert(udevCalls, HasLen, len(expectedUdevCalls))
+ // iteration order can be different though
+ c.Assert(udevCalls[0], HasLen, 6)
+}
+
+func (s *keymgrSuite) TestRemoveRecoveryKeyLegacy(c *C) {
+ udevadmCmd := s.mocksForDeviceMounts(c)
+
+ defer secboot.MockListLUKS2ContainerUnlockKeyNames(func(devicePath string) ([]string, error) {
+ return []string{}, nil
+ })()
+ defer secboot.MockRemoveLUKS2ContainerKey(func(devicePath string, keyslotName string) error {
+ c.Errorf("unexpected call")
+ return nil
+ })()
+
snaptest.PopulateDir(s.d, [][]string{
{"recovery.key", "foobar"},
})
diff --git a/secboot/export_sb_test.go b/secboot/export_sb_test.go
index dd75ae0df6a..ec86d87148c 100644
--- a/secboot/export_sb_test.go
+++ b/secboot/export_sb_test.go
@@ -2,7 +2,7 @@
//go:build !nosecboot
/*
- * Copyright (C) 2021 Canonical Ltd
+ * Copyright (C) 2021, 2024 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
@@ -21,11 +21,10 @@
package secboot
import (
- "io"
-
"github.com/canonical/go-tpm2"
sb "github.com/snapcore/secboot"
sb_efi "github.com/snapcore/secboot/efi"
+ sb_hooks "github.com/snapcore/secboot/hooks"
sb_tpm2 "github.com/snapcore/secboot/tpm2"
"github.com/snapcore/snapd/testutil"
@@ -62,23 +61,15 @@ func MockTPMReleaseResources(f func(tpm *sb_tpm2.Connection, handle tpm2.Handle)
return restore
}
-func MockSbEfiAddSecureBootPolicyProfile(f func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.SecureBootPolicyProfileParams) error) (restore func()) {
- old := sbefiAddSecureBootPolicyProfile
- sbefiAddSecureBootPolicyProfile = f
+func MockSbEfiAddPCRProfile(f func(pcrAlg tpm2.HashAlgorithmId, branch *sb_tpm2.PCRProtectionProfileBranch, loadSequences *sb_efi.ImageLoadSequences, options ...sb_efi.PCRProfileOption) error) (restore func()) {
+ old := sbefiAddPCRProfile
+ sbefiAddPCRProfile = f
return func() {
- sbefiAddSecureBootPolicyProfile = old
+ sbefiAddPCRProfile = old
}
}
-func MockSbEfiAddBootManagerProfile(f func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.BootManagerProfileParams) error) (restore func()) {
- old := sbefiAddBootManagerProfile
- sbefiAddBootManagerProfile = f
- return func() {
- sbefiAddBootManagerProfile = old
- }
-}
-
-func MockSbEfiAddSystemdStubProfile(f func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.SystemdStubProfileParams) error) (restore func()) {
+func MockSbEfiAddSystemdStubProfile(f func(profile *sb_tpm2.PCRProtectionProfileBranch, params *sb_efi.SystemdStubProfileParams) error) (restore func()) {
old := sbefiAddSystemdStubProfile
sbefiAddSystemdStubProfile = f
return func() {
@@ -86,7 +77,7 @@ func MockSbEfiAddSystemdStubProfile(f func(profile *sb_tpm2.PCRProtectionProfile
}
}
-func MockSbAddSnapModelProfile(f func(profile *sb_tpm2.PCRProtectionProfile, params *sb_tpm2.SnapModelProfileParams) error) (restore func()) {
+func MockSbAddSnapModelProfile(f func(profile *sb_tpm2.PCRProtectionProfileBranch, params *sb_tpm2.SnapModelProfileParams) error) (restore func()) {
old := sbAddSnapModelProfile
sbAddSnapModelProfile = f
return func() {
@@ -94,15 +85,7 @@ func MockSbAddSnapModelProfile(f func(profile *sb_tpm2.PCRProtectionProfile, par
}
}
-func MockSbSealKeyToTPMMultiple(f func(tpm *sb_tpm2.Connection, keys []*sb_tpm2.SealKeyRequest, params *sb_tpm2.KeyCreationParams) (sb_tpm2.PolicyAuthKey, error)) (restore func()) {
- old := sbSealKeyToTPMMultiple
- sbSealKeyToTPMMultiple = f
- return func() {
- sbSealKeyToTPMMultiple = old
- }
-}
-
-func MockSbUpdateKeyPCRProtectionPolicyMultiple(f func(tpm *sb_tpm2.Connection, keys []*sb_tpm2.SealedKeyObject, authKey sb_tpm2.PolicyAuthKey, pcrProfile *sb_tpm2.PCRProtectionProfile) error) (restore func()) {
+func MockSbUpdateKeyPCRProtectionPolicyMultiple(f func(tpm *sb_tpm2.Connection, keys []*sb_tpm2.SealedKeyObject, authKey sb.PrimaryKey, pcrProfile *sb_tpm2.PCRProtectionProfile) error) (restore func()) {
old := sbUpdateKeyPCRProtectionPolicyMultiple
sbUpdateKeyPCRProtectionPolicyMultiple = f
return func() {
@@ -110,7 +93,7 @@ func MockSbUpdateKeyPCRProtectionPolicyMultiple(f func(tpm *sb_tpm2.Connection,
}
}
-func MockSbSealedKeyObjectRevokeOldPCRProtectionPolicies(f func(sko *sb_tpm2.SealedKeyObject, tpm *sb_tpm2.Connection, authKey sb_tpm2.PolicyAuthKey) error) (restore func()) {
+func MockSbSealedKeyObjectRevokeOldPCRProtectionPolicies(f func(sko *sb_tpm2.SealedKeyObject, tpm *sb_tpm2.Connection, authKey sb.PrimaryKey) error) (restore func()) {
old := sbSealedKeyObjectRevokeOldPCRProtectionPolicies
sbSealedKeyObjectRevokeOldPCRProtectionPolicies = f
return func() {
@@ -127,7 +110,7 @@ func MockSbBlockPCRProtectionPolicies(f func(tpm *sb_tpm2.Connection, pcrs []int
}
func MockSbActivateVolumeWithRecoveryKey(f func(volumeName, sourceDevicePath string,
- keyReader io.Reader, options *sb.ActivateVolumeOptions) error) (restore func()) {
+ authRequester sb.AuthRequestor, options *sb.ActivateVolumeOptions) error) (restore func()) {
old := sbActivateVolumeWithRecoveryKey
sbActivateVolumeWithRecoveryKey = f
return func() {
@@ -144,7 +127,7 @@ func MockSbActivateVolumeWithKey(f func(volumeName, sourceDevicePath string, key
}
}
-func MockSbActivateVolumeWithKeyData(f func(volumeName, sourceDevicePath string, key *sb.KeyData, options *sb.ActivateVolumeOptions) (sb.SnapModelChecker, error)) (restore func()) {
+func MockSbActivateVolumeWithKeyData(f func(volumeName, sourceDevicePath string, authRequestor sb.AuthRequestor, options *sb.ActivateVolumeOptions, keys ...*sb.KeyData) error) (restore func()) {
oldSbActivateVolumeWithKeyData := sbActivateVolumeWithKeyData
sbActivateVolumeWithKeyData = f
return func() {
@@ -176,7 +159,7 @@ func MockRandomKernelUUID(f func() (string, error)) (restore func()) {
}
}
-func MockSbInitializeLUKS2Container(f func(devicePath, label string, key []byte,
+func MockSbInitializeLUKS2Container(f func(devicePath, label string, key sb.DiskUnlockKey,
opts *sb.InitializeLUKS2ContainerOptions) error) (restore func()) {
old := sbInitializeLUKS2Container
sbInitializeLUKS2Container = f
@@ -185,14 +168,6 @@ func MockSbInitializeLUKS2Container(f func(devicePath, label string, key []byte,
}
}
-func MockSbAddRecoveryKeyToLUKS2Container(f func(devicePath string, key []byte, recoveryKey sb.RecoveryKey, opts *sb.KDFOptions) error) (restore func()) {
- old := sbAddRecoveryKeyToLUKS2Container
- sbAddRecoveryKeyToLUKS2Container = f
- return func() {
- sbAddRecoveryKeyToLUKS2Container = old
- }
-}
-
func MockIsTPMEnabled(f func(tpm *sb_tpm2.Connection) bool) (restore func()) {
old := isTPMEnabled
isTPMEnabled = f
@@ -236,3 +211,83 @@ func MockSbLockoutAuthSet(f func(tpm *sb_tpm2.Connection) bool) (restore func())
lockoutAuthSet = f
return restore
}
+
+func MockSbNewTPMProtectedKey(f func(tpm *sb_tpm2.Connection, params *sb_tpm2.ProtectKeyParams) (protectedKey *sb.KeyData, primaryKey sb.PrimaryKey, unlockKey sb.DiskUnlockKey, err error)) (restore func()) {
+ old := sbNewTPMProtectedKey
+ sbNewTPMProtectedKey = f
+ return func() {
+ sbNewTPMProtectedKey = old
+ }
+}
+
+func MockSbSetModel(f func(model sb.SnapModel)) (restore func()) {
+ old := sbSetModel
+ sbSetModel = f
+ return func() {
+ sbSetModel = old
+ }
+}
+
+func MockSbSetBootMode(f func(mode string)) (restore func()) {
+ old := sbSetBootMode
+ sbSetBootMode = f
+ return func() {
+ sbSetBootMode = old
+ }
+}
+
+func MockSbSetKeyRevealer(f func(kr sb_hooks.KeyRevealer)) (restore func()) {
+ old := sbSetKeyRevealer
+ sbSetKeyRevealer = f
+ return func() {
+ sbSetKeyRevealer = old
+ }
+}
+
+func MockReadKeyFile(f func(keyfile string) (*sb.KeyData, *sb_tpm2.SealedKeyObject, error)) (restore func()) {
+ old := readKeyFile
+ readKeyFile = f
+ return func() {
+ readKeyFile = old
+ }
+}
+
+func MockListLUKS2ContainerUnlockKeyNames(f func(devicePath string) ([]string, error)) (restore func()) {
+ old := sbListLUKS2ContainerUnlockKeyNames
+ sbListLUKS2ContainerUnlockKeyNames = f
+ return func() {
+ sbListLUKS2ContainerUnlockKeyNames = old
+ }
+}
+
+func MockListLUKS2ContainerRecoveryKeyNames(f func(devicePath string) ([]string, error)) (restore func()) {
+ old := sbListLUKS2ContainerRecoveryKeyNames
+ sbListLUKS2ContainerRecoveryKeyNames = f
+ return func() {
+ sbListLUKS2ContainerRecoveryKeyNames = old
+ }
+}
+
+func MockGetDiskUnlockKeyFromKernel(f func(prefix string, devicePath string, remove bool) (sb.DiskUnlockKey, error)) (restore func()) {
+ old := sbGetDiskUnlockKeyFromKernel
+ sbGetDiskUnlockKeyFromKernel = f
+ return func() {
+ sbGetDiskUnlockKeyFromKernel = old
+ }
+}
+
+func MockAddLUKS2ContainerRecoveryKey(f func(devicePath string, keyslotName string, existingKey sb.DiskUnlockKey, recoveryKey sb.RecoveryKey) error) (restore func()) {
+ old := sbAddLUKS2ContainerRecoveryKey
+ sbAddLUKS2ContainerRecoveryKey = f
+ return func() {
+ sbAddLUKS2ContainerRecoveryKey = old
+ }
+}
+
+func MockRemoveLUKS2ContainerKey(f func(devicePath string, keyslotName string) error) (restore func()) {
+ old := sbDeleteLUKS2ContainerKey
+ sbDeleteLUKS2ContainerKey = f
+ return func() {
+ sbDeleteLUKS2ContainerKey = old
+ }
+}
diff --git a/secboot/key_resetter.go b/secboot/key_resetter.go
new file mode 100644
index 00000000000..700770d16ff
--- /dev/null
+++ b/secboot/key_resetter.go
@@ -0,0 +1,56 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2024 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 .
+ *
+ */
+
+package secboot
+
+import (
+ "fmt"
+)
+
+type MockKeyResetter struct {
+ finished bool
+}
+
+type MockKeyDataWriter struct {
+}
+
+func (kdw *MockKeyDataWriter) Write(p []byte) (n int, err error) {
+ return len(p), nil
+}
+
+func (kdw *MockKeyDataWriter) Commit() error {
+ return nil
+}
+
+func (kr *MockKeyResetter) AddKey(slotName string, newKey []byte, token bool) (KeyDataWriter, error) {
+ if kr.finished {
+ return nil, fmt.Errorf("internal error: key resetter was a already finished")
+ }
+
+ if token {
+ return &MockKeyDataWriter{}, nil
+ } else {
+ return nil, nil
+ }
+}
+
+func (kr *MockKeyResetter) RemoveInstallationKey() error {
+ kr.finished = true
+ return nil
+}
diff --git a/secboot/key_resetter_sb.go b/secboot/key_resetter_sb.go
new file mode 100644
index 00000000000..9f9eff67389
--- /dev/null
+++ b/secboot/key_resetter_sb.go
@@ -0,0 +1,80 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !nosecboot
+
+/*
+ * Copyright (C) 2024 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 .
+ *
+ */
+
+package secboot
+
+import (
+ "fmt"
+
+ sb "github.com/snapcore/secboot"
+)
+
+type sbKeyResetter struct {
+ devicePath string
+ oldKey sb.DiskUnlockKey
+ oldContainerKeySlot string
+ finished bool
+}
+
+func createKeyResetterImpl(key DiskUnlockKey, devicePath string) KeyResetter {
+ return &sbKeyResetter{
+ devicePath: devicePath,
+ oldKey: sb.DiskUnlockKey(key),
+ oldContainerKeySlot: "installation-key",
+ }
+}
+
+var CreateKeyResetter = createKeyResetterImpl
+
+func (kr *sbKeyResetter) AddKey(slotName string, newKey []byte, token bool) (KeyDataWriter, error) {
+ if kr.finished {
+ return nil, fmt.Errorf("internal error: key resetter was a already finished")
+ }
+ if slotName == "" {
+ slotName = "default"
+ }
+ if err := sb.AddLUKS2ContainerUnlockKey(kr.devicePath, slotName, kr.oldKey, sb.DiskUnlockKey(newKey)); err != nil {
+ return nil, err
+ }
+ if !token {
+ return nil, nil
+ }
+ writer, err := sb.NewLUKS2KeyDataWriter(kr.devicePath, slotName)
+ if err != nil {
+ return nil, err
+ }
+ return writer, nil
+}
+
+func (kr *sbKeyResetter) RemoveInstallationKey() error {
+ if kr.finished {
+ return nil
+ }
+ kr.finished = true
+ return sb.DeleteLUKS2ContainerKey(kr.devicePath, kr.oldContainerKeySlot)
+}
+
+func MockCreateKeyResetter(f func(key DiskUnlockKey, devicePath string) KeyResetter) func() {
+ old := CreateKeyResetter
+ CreateKeyResetter = f
+ return func() {
+ CreateKeyResetter = old
+ }
+}
diff --git a/secboot/keymgr/export_test.go b/secboot/keymgr/export_test.go
index 95802e732b2..7e4e6828746 100644
--- a/secboot/keymgr/export_test.go
+++ b/secboot/keymgr/export_test.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2022 Canonical Ltd
+ * Copyright (C) 2022, 2024 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
diff --git a/secboot/keys/plainkey.go b/secboot/keys/plainkey.go
new file mode 100644
index 00000000000..ed1b7573151
--- /dev/null
+++ b/secboot/keys/plainkey.go
@@ -0,0 +1,52 @@
+//go:build !nosecboot
+
+package keys
+
+import (
+ "crypto/rand"
+ "io"
+ "os"
+ "path/filepath"
+
+ sb "github.com/snapcore/secboot"
+ sb_plainkey "github.com/snapcore/secboot/plainkey"
+
+ "github.com/snapcore/snapd/osutil"
+)
+
+const (
+ PlatformKeySize = 32
+)
+
+type PlatformKey []byte
+
+func NewPlatformKey() (PlatformKey, error) {
+ key := make(PlatformKey, PlatformKeySize)
+ _, err := rand.Read(key[:])
+ return key, err
+}
+
+func (key PlatformKey) SaveToFile(path string) error {
+ if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
+ return err
+ }
+ return osutil.AtomicWriteFile(path, key[:], 0600, 0)
+}
+
+type PlainKey struct {
+ keyData *sb.KeyData
+}
+
+func (key PlatformKey) CreateProtectedKey() (*PlainKey, []byte, error) {
+ protectedKey /*primaryKey*/, _, unlockKey, err := sb_plainkey.NewProtectedKey(rand.Reader, key[:], nil)
+ return &PlainKey{protectedKey}, unlockKey, err
+}
+
+type KeyDataWriter interface {
+ io.Writer
+ Commit() error
+}
+
+func (key *PlainKey) Write(writer KeyDataWriter) error {
+ return key.keyData.WriteAtomic(writer)
+}
diff --git a/secboot/keys/plainkey_nosb.go b/secboot/keys/plainkey_nosb.go
new file mode 100644
index 00000000000..3201af8a717
--- /dev/null
+++ b/secboot/keys/plainkey_nosb.go
@@ -0,0 +1,40 @@
+//go:build nosecboot
+
+package keys
+
+import (
+ "crypto/rand"
+ "io"
+)
+
+const (
+ PlatformKeySize = 32
+)
+
+type PlatformKey []byte
+
+func NewPlatformKey() (PlatformKey, error) {
+ key := make(PlatformKey, PlatformKeySize)
+ _, err := rand.Read(key[:])
+ return key, err
+}
+
+type PlainKey struct {
+}
+
+func (key PlatformKey) SaveToFile(path string) error {
+ return nil
+}
+
+func (key PlatformKey) CreateProtectedKey() (*PlainKey, []byte, error) {
+ return &PlainKey{}, []byte{}, nil
+}
+
+type KeyDataWriter interface {
+ io.Writer
+ Commit() error
+}
+
+func (key *PlainKey) Write(writer KeyDataWriter) error {
+ return nil
+}
diff --git a/secboot/secboot.go b/secboot/secboot.go
index 48616fb9fe5..36614836304 100644
--- a/secboot/secboot.go
+++ b/secboot/secboot.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2021-2022 Canonical Ltd
+ * Copyright (C) 2021-2022, 2024 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
@@ -25,13 +25,12 @@ package secboot
// Debian does run "go list" without any support for passing -tags.
import (
- "crypto/ecdsa"
+ "io"
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/gadget/device"
- "github.com/snapcore/snapd/secboot/keys"
)
const (
@@ -65,14 +64,29 @@ func NewLoadChain(bf bootloader.BootFile, next ...*LoadChain) *LoadChain {
}
}
+type KeyDataWriter interface {
+ io.Writer
+ Commit() error
+}
+
+type KeyResetter interface {
+ AddKey(slotName string, newKey []byte, token bool) (KeyDataWriter, error)
+ RemoveInstallationKey() error
+}
+
type SealKeyRequest struct {
- // The key to seal
- Key keys.EncryptionKey
// The key name; identical keys should have identical names
KeyName string
+
+ SlotName string
+
// The path to store the sealed key file. The same Key/KeyName
// can be stored under multiple KeyFile names for safety.
KeyFile string
+
+ Role string
+
+ Resetter KeyResetter
}
// ModelForSealing provides information about the model for use in the context
@@ -116,20 +130,18 @@ const (
type SealKeysParams struct {
// The parameters we're sealing the key to
ModelParams []*SealKeyModelParams
- // The authorization policy update key file (only relevant for TPM)
- TPMPolicyAuthKey *ecdsa.PrivateKey
+ // The primary key to use, nil if needs to be generated
+ PrimaryKey []byte
+ // The handle at which to create a NV index for dynamic authorization policy revocation support
+ PCRPolicyCounterHandle uint32
// The path to the authorization policy update key file (only relevant for TPM,
// if empty the key will not be saved)
TPMPolicyAuthKeyFile string
- // The handle at which to create a NV index for dynamic authorization policy revocation support
- PCRPolicyCounterHandle uint32
}
type SealKeysWithFDESetupHookParams struct {
// Initial model to bind sealed keys to.
Model ModelForSealing
- // AuxKey is the auxiliary key used to bind models.
- AuxKey keys.AuxKey
// The path to the aux key file (if empty the key will not be
// saved)
AuxKeyFile string
@@ -153,6 +165,8 @@ type UnlockVolumeUsingSealedKeyOptions struct {
// WhichModel if invoked should return the device model
// assertion for which the disk is being unlocked.
WhichModel func() (*asserts.Model, error)
+
+ BootMode string
}
// UnlockMethod is the method that was used to unlock a volume.
diff --git a/secboot/secboot_dummy.go b/secboot/secboot_dummy.go
index 6817fa63304..419194de266 100644
--- a/secboot/secboot_dummy.go
+++ b/secboot/secboot_dummy.go
@@ -2,7 +2,7 @@
//go:build nosecboot
/*
- * Copyright (C) 2021 Canonical Ltd
+ * Copyright (C) 2021, 2024 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
@@ -24,16 +24,19 @@ import (
"errors"
"github.com/snapcore/snapd/kernel/fde"
+ "github.com/snapcore/snapd/secboot/keys"
)
var errBuildWithoutSecboot = errors.New("build without secboot support")
+type DiskUnlockKey []byte
+
func CheckTPMKeySealingSupported(mode TPMProvisionMode) error {
return errBuildWithoutSecboot
}
-func SealKeys(keys []SealKeyRequest, params *SealKeysParams) error {
- return errBuildWithoutSecboot
+func SealKeys(keys []SealKeyRequest, params *SealKeysParams) ([]byte, error) {
+ return nil, errBuildWithoutSecboot
}
func SealKeysWithFDESetupHook(runHook fde.RunSetupHookFunc, keys []SealKeyRequest, params *SealKeysWithFDESetupHookParams) error {
@@ -59,3 +62,34 @@ func ReleasePCRResourceHandles(handles ...uint32) error {
func resetLockoutCounter(lockoutAuthFile string) error {
return errBuildWithoutSecboot
}
+
+func ResealKeysWithFDESetupHook(keyFiles []string, primaryKeyFile string, models []ModelForSealing) error {
+ return errBuildWithoutSecboot
+}
+
+type ActivateVolumeOptions struct {
+}
+
+func ActivateVolumeWithKey(volumeName, sourceDevicePath string, key []byte, options *ActivateVolumeOptions) error {
+ return errBuildWithoutSecboot
+}
+
+func DeactivateVolume(volumeName string) error {
+ return errBuildWithoutSecboot
+}
+
+func AddInstallationKeyOnExistingDisk(node string, newKey keys.EncryptionKey) error {
+ return errBuildWithoutSecboot
+}
+
+func RenameOrDeleteKeys(node string, renames map[string]string) error {
+ return errBuildWithoutSecboot
+}
+
+func DeleteKeys(node string, matches map[string]bool) error {
+ return errBuildWithoutSecboot
+}
+
+func ResealKeysNextGeneration(devices []string, modelParams map[string][]*SealKeyModelParams) error {
+ return errBuildWithoutSecboot
+}
diff --git a/secboot/secboot_hooks.go b/secboot/secboot_hooks.go
index 6e7d25cb88d..27d7de4695c 100644
--- a/secboot/secboot_hooks.go
+++ b/secboot/secboot_hooks.go
@@ -2,7 +2,7 @@
//go:build !nosecboot
/*
- * Copyright (C) 2021 Canonical Ltd
+ * Copyright (C) 2021, 2024 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
@@ -22,13 +22,15 @@ package secboot
import (
"bytes"
- "crypto"
+ "crypto/rand"
"encoding/json"
"fmt"
"io"
"os"
sb "github.com/snapcore/secboot"
+ sb_scope "github.com/snapcore/secboot/bootscope"
+ sb_hooks "github.com/snapcore/secboot/hooks"
"golang.org/x/xerrors"
"github.com/snapcore/snapd/kernel/fde"
@@ -37,69 +39,122 @@ import (
)
var fdeHasRevealKey = fde.HasRevealKey
+var sbSetModel = sb_scope.SetModel
+var sbSetBootMode = sb_scope.SetBootMode
+var sbSetKeyRevealer = sb_hooks.SetKeyRevealer
-const fdeHooksPlatformName = "fde-hook-v2"
+const legacyFdeHooksPlatformName = "fde-hook-v2"
func init() {
handler := &fdeHookV2DataHandler{}
- sb.RegisterPlatformKeyDataHandler(fdeHooksPlatformName, handler)
+ sb.RegisterPlatformKeyDataHandler(legacyFdeHooksPlatformName, handler)
+}
+
+type hookKeyProtector struct {
+ runHook fde.RunSetupHookFunc
+ keyName string
+}
+
+func (h *hookKeyProtector) ProtectKey(rand io.Reader, cleartext, aad []byte) (ciphertext []byte, handle []byte, err error) {
+ keyParams := &fde.InitialSetupParams{
+ Key: cleartext,
+ KeyName: h.keyName,
+ AAD: aad,
+ }
+ res, err := fde.InitialSetup(h.runHook, keyParams)
+ if err != nil {
+ return nil, nil, err
+ }
+ if res.Handle == nil {
+ return res.EncryptedKey, nil, nil
+ } else {
+ return res.EncryptedKey, *res.Handle, nil
+ }
}
-// SealKeysWithFDESetupHook protects the given keys through using the
-// fde-setup hook and saves each protected key to the KeyFile
-// indicated in the key SealKeyRequest.
func SealKeysWithFDESetupHook(runHook fde.RunSetupHookFunc, keys []SealKeyRequest, params *SealKeysWithFDESetupHookParams) error {
- auxKey := params.AuxKey[:]
+ var primaryKey sb.PrimaryKey
+
for _, skr := range keys {
- payload := sb.MarshalKeys([]byte(skr.Key), auxKey)
- keyParams := &fde.InitialSetupParams{
- Key: payload,
- KeyName: skr.KeyName,
+ protector := &hookKeyProtector{
+ runHook: runHook,
+ keyName: skr.KeyName,
+ }
+ flags := sb_hooks.KeyProtectorNoAEAD
+ sb_hooks.SetKeyProtector(protector, flags)
+ defer sb_hooks.SetKeyProtector(nil, 0)
+
+ params := &sb_hooks.KeyParams{
+ PrimaryKey: primaryKey,
+ Role: skr.Role,
+ AuthorizedSnapModels: []sb.SnapModel{
+ params.Model,
+ },
+ AuthorizedBootModes: []string{
+ "run",
+ "recover",
+ "factory-reset",
+ },
+ }
+ protectedKey, primaryKeyOut, unlockKey, err := sb_hooks.NewProtectedKey(rand.Reader, params)
+ if err != nil {
+ return err
+ }
+ if primaryKey == nil {
+ primaryKey = primaryKeyOut
}
- res, err := fde.InitialSetup(runHook, keyParams)
+ token := skr.KeyFile == ""
+ tokenWriter, err := skr.Resetter.AddKey(skr.SlotName, unlockKey, token)
if err != nil {
return err
}
- if err := writeKeyData(skr.KeyFile, res, auxKey, params.Model); err != nil {
- return fmt.Errorf("cannot store key: %v", err)
+ var keyDataWriter sb.KeyDataWriter
+ if token {
+ keyDataWriter = tokenWriter
+ } else {
+ keyDataWriter = sb.NewFileKeyDataWriter(skr.KeyFile)
+ }
+ if err := protectedKey.WriteAtomic(keyDataWriter); err != nil {
+ return err
}
}
- if params.AuxKeyFile != "" {
- if err := osutil.AtomicWriteFile(params.AuxKeyFile, auxKey, 0600, 0); err != nil {
- return fmt.Errorf("cannot write the aux key file: %v", err)
+ if primaryKey != nil && params.AuxKeyFile != "" {
+ if err := osutil.AtomicWriteFile(params.AuxKeyFile, primaryKey, 0600, 0); err != nil {
+ return fmt.Errorf("cannot write the policy auth key file: %v", err)
}
}
return nil
}
-func writeKeyData(path string, keySetup *fde.InitialSetupResult, auxKey []byte, model sb.SnapModel) error {
- var handle []byte
- if keySetup.Handle == nil {
- // this will reach fde-reveal-key as null but should be ok
- handle = []byte("null")
- } else {
- handle = *keySetup.Handle
- }
- kd, err := sb.NewKeyData(&sb.KeyCreationData{
- PlatformKeyData: sb.PlatformKeyData{
- EncryptedPayload: keySetup.EncryptedKey,
- Handle: handle,
- },
- PlatformName: fdeHooksPlatformName,
- AuxiliaryKey: auxKey,
- SnapModelAuthHash: crypto.SHA256,
- })
+func ResealKeysWithFDESetupHook(keyFiles []string, primaryKeyFile string, models []ModelForSealing) error {
+ primaryKeyBuf, err := os.ReadFile(primaryKeyFile)
if err != nil {
- return fmt.Errorf("cannot create key data: %v", err)
+ return fmt.Errorf("cannot read primary key file: %v", err)
}
- if err := kd.SetAuthorizedSnapModels(auxKey, model); err != nil {
- return fmt.Errorf("cannot set model %s/%s as authorized: %v", model.BrandID(), model.Model(), err)
+ primaryKey := sb.PrimaryKey(primaryKeyBuf)
+
+ var sbModels []sb.SnapModel
+ for _, model := range models {
+ sbModels = append(sbModels, model)
}
- f := sb.NewFileKeyDataWriter(path)
- if err := kd.WriteAtomic(f); err != nil {
- return fmt.Errorf("cannot write key data: %v", err)
+ for _, keyFile := range keyFiles {
+ reader, err := sbNewFileKeyDataReader(keyFile)
+ if err != nil {
+ return fmt.Errorf("cannot open key data: %v", err)
+ }
+ keyData, err := sbReadKeyData(reader)
+ if err != nil {
+ return fmt.Errorf("cannot read key data: %v", err)
+ }
+ keyData.SetAuthorizedSnapModels(primaryKey, sbModels...)
+
+ writer := sb.NewFileKeyDataWriter(keyFile)
+ if err := keyData.WriteAtomic(writer); err != nil {
+ return err
+ }
}
+
return nil
}
@@ -172,9 +227,26 @@ func unlockVolumeUsingSealedKeyFDERevealKeyV2(sealedEncryptionKeyFile, sourceDev
return res, xerrors.Errorf(fmt, err)
}
+ model, err := opts.WhichModel()
+ if err != nil {
+ return res, fmt.Errorf("cannot retrieve which model to unlock for: %v", err)
+ }
+
// the output of fde-reveal-key is the unsealed key
options := activateVolOpts(opts.AllowRecoveryKey)
- modChecker, err := sbActivateVolumeWithKeyData(mapperName, sourceDevice, keyData, options)
+ options.Model = model
+
+ sbSetModel(model)
+ sbSetBootMode(opts.BootMode)
+ sbSetKeyRevealer(&keyRevealerV3{})
+ defer sbSetKeyRevealer(nil)
+
+ authRequestor, err := newAuthRequestor()
+ if err != nil {
+ return res, fmt.Errorf("cannot build an auth requestor: %v", err)
+ }
+
+ err = sbActivateVolumeWithKeyData(mapperName, sourceDevice, authRequestor, options, keyData)
if err == sb.ErrRecoveryKeyUsed {
logger.Noticef("successfully activated encrypted device %q using a fallback activation method", sourceDevice)
res.FsDevice = targetDevice
@@ -192,18 +264,6 @@ func unlockVolumeUsingSealedKeyFDERevealKeyV2(sealedEncryptionKeyFile, sourceDev
}
}
}()
- // ensure that the model is authorized to open the volume
- model, err := opts.WhichModel()
- if err != nil {
- return res, fmt.Errorf("cannot retrieve which model to unlock for: %v", err)
- }
- ok, err := modChecker.IsModelAuthorized(model)
- if err != nil {
- return res, fmt.Errorf("cannot check if model is authorized to unlock disk: %v", err)
- }
- if !ok {
- return res, fmt.Errorf("cannot unlock volume: model %s/%s not authorized", model.BrandID(), model.Model())
- }
logger.Noticef("successfully activated encrypted device %q using FDE kernel hooks", sourceDevice)
res.FsDevice = targetDevice
@@ -213,15 +273,42 @@ func unlockVolumeUsingSealedKeyFDERevealKeyV2(sealedEncryptionKeyFile, sourceDev
type fdeHookV2DataHandler struct{}
-func (fh *fdeHookV2DataHandler) RecoverKeys(data *sb.PlatformKeyData) (sb.KeyPayload, error) {
+func (fh *fdeHookV2DataHandler) RecoverKeys(data *sb.PlatformKeyData, encryptedPayload []byte) ([]byte, error) {
+ var handle *json.RawMessage
+ if len(data.EncodedHandle) != 0 {
+ rawHandle := json.RawMessage(data.EncodedHandle)
+ handle = &rawHandle
+ }
+ p := fde.RevealParams{
+ SealedKey: encryptedPayload,
+ Handle: handle,
+ V2Payload: true,
+ }
+ return fde.Reveal(&p)
+}
+
+func (fh *fdeHookV2DataHandler) ChangeAuthKey(data *sb.PlatformKeyData, old, new []byte) ([]byte, error) {
+ return nil, fmt.Errorf("cannot change auth key yet")
+}
+
+func (fh *fdeHookV2DataHandler) RecoverKeysWithAuthKey(data *sb.PlatformKeyData, encryptedPayload, key []byte) ([]byte, error) {
+ return nil, fmt.Errorf("cannot recover keys with auth keys yet")
+}
+
+type keyRevealerV3 struct {
+}
+
+func (kr *keyRevealerV3) RevealKey(data, ciphertext, aad []byte) (plaintext []byte, err error) {
+ logger.Noticef("Called reveal key")
var handle *json.RawMessage
- if len(data.Handle) != 0 {
- rawHandle := json.RawMessage(data.Handle)
+ if len(data) != 0 {
+ rawHandle := json.RawMessage(data)
handle = &rawHandle
}
p := fde.RevealParams{
- SealedKey: data.EncryptedPayload,
+ SealedKey: ciphertext,
Handle: handle,
+ AAD: aad,
V2Payload: true,
}
return fde.Reveal(&p)
diff --git a/secboot/secboot_sb.go b/secboot/secboot_sb.go
index ed38670586e..b51892f4b12 100644
--- a/secboot/secboot_sb.go
+++ b/secboot/secboot_sb.go
@@ -2,7 +2,7 @@
//go:build !nosecboot
/*
- * Copyright (C) 2021 Canonical Ltd
+ * Copyright (C) 2021-2024 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
@@ -21,15 +21,22 @@
package secboot
import (
+ "crypto/rand"
+ "errors"
"fmt"
"path/filepath"
sb "github.com/snapcore/secboot"
+ sb_hooks "github.com/snapcore/secboot/hooks"
+ sb_plainkey "github.com/snapcore/secboot/plainkey"
+ sb_tpm2 "github.com/snapcore/secboot/tpm2"
"golang.org/x/xerrors"
"github.com/snapcore/snapd/kernel/fde"
"github.com/snapcore/snapd/logger"
+ "github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/osutil/disks"
+ "github.com/snapcore/snapd/secboot/keys"
)
var (
@@ -37,12 +44,16 @@ var (
sbActivateVolumeWithKeyData = sb.ActivateVolumeWithKeyData
sbActivateVolumeWithRecoveryKey = sb.ActivateVolumeWithRecoveryKey
sbDeactivateVolume = sb.DeactivateVolume
+ sbAddLUKS2ContainerUnlockKey = sb.AddLUKS2ContainerUnlockKey
)
func init() {
WithSecbootSupport = true
}
+type DiskUnlockKey sb.DiskUnlockKey
+type ActivateVolumeOptions sb.ActivateVolumeOptions
+
// LockSealedKeys manually locks access to the sealed keys. Meant to be
// called in place of passing lockKeysOnFinish as true to
// UnlockVolumeUsingSealedKeyIfEncrypted for cases where we don't know if a
@@ -113,15 +124,19 @@ func UnlockVolumeUsingSealedKeyIfEncrypted(disk disks.Disk, name string, sealedE
sourceDevice := partDevice
targetDevice := filepath.Join("/dev/mapper", mapperName)
- if fdeHasRevealKey() {
- return unlockVolumeUsingSealedKeyFDERevealKey(sealedEncryptionKeyFile, sourceDevice, targetDevice, mapperName, opts)
+ if osutil.FileExists(sealedEncryptionKeyFile) {
+ if fdeHasRevealKey() {
+ return unlockVolumeUsingSealedKeyFDERevealKey(sealedEncryptionKeyFile, sourceDevice, targetDevice, mapperName, opts)
+ } else {
+ return unlockVolumeUsingSealedKeyTPM(name, sealedEncryptionKeyFile, sourceDevice, targetDevice, mapperName, opts)
+ }
} else {
- return unlockVolumeUsingSealedKeyTPM(name, sealedEncryptionKeyFile, sourceDevice, targetDevice, mapperName, opts)
+ return unlockVolumeUsingSealedKeyGeneric(name, sourceDevice, targetDevice, mapperName, opts)
}
}
// UnlockEncryptedVolumeUsingKey unlocks an existing volume using the provided key.
-func UnlockEncryptedVolumeUsingKey(disk disks.Disk, name string, key []byte) (UnlockResult, error) {
+func UnlockEncryptedVolumeUsingPlatformKey(disk disks.Disk, name string, key []byte) (UnlockResult, error) {
unlockRes := UnlockResult{
UnlockMethod: NotUnlocked,
}
@@ -148,9 +163,31 @@ func UnlockEncryptedVolumeUsingKey(disk disks.Disk, name string, key []byte) (Un
// make up a new name for the mapped device
mapperName := name + "-" + uuid
- if err := unlockEncryptedPartitionWithKey(mapperName, encdev, key); err != nil {
+
+ slots, err := sbListLUKS2ContainerUnlockKeyNames(encdev)
+ if err != nil {
return unlockRes, err
}
+ keyExists := false
+ for _, slot := range slots {
+ if slot == "default-fallback" {
+ keyExists = true
+ break
+ }
+ }
+
+ if keyExists {
+ sb_plainkey.SetPlatformKeys(key)
+
+ options := sb.ActivateVolumeOptions{}
+ if err := sbActivateVolumeWithKeyData(mapperName, encdev, nil, &options); err != nil {
+ return unlockRes, err
+ }
+ } else {
+ if err := unlockEncryptedPartitionWithKey(mapperName, encdev, key); err != nil {
+ return unlockRes, err
+ }
+ }
unlockRes.FsDevice = filepath.Join("/dev/mapper/", mapperName)
unlockRes.UnlockMethod = UnlockedWithKey
@@ -177,9 +214,242 @@ func UnlockEncryptedVolumeWithRecoveryKey(name, device string) error {
KeyringPrefix: keyringPrefix,
}
- if err := sbActivateVolumeWithRecoveryKey(name, device, nil, &options); err != nil {
+ authRequestor, err := newAuthRequestor()
+ if err != nil {
+ return fmt.Errorf("cannot build an auth requestor: %v", err)
+ }
+
+ if err := sbActivateVolumeWithRecoveryKey(name, device, authRequestor, &options); err != nil {
return fmt.Errorf("cannot unlock encrypted device %q: %v", device, err)
}
return nil
}
+
+func ActivateVolumeWithKey(volumeName, sourceDevicePath string, key []byte, options *ActivateVolumeOptions) error {
+ return sb.ActivateVolumeWithKey(volumeName, sourceDevicePath, key, (*sb.ActivateVolumeOptions)(options))
+}
+
+func DeactivateVolume(volumeName string) error {
+ return sb.DeactivateVolume(volumeName)
+}
+
+func AddInstallationKeyOnExistingDisk(node string, newKey keys.EncryptionKey) error {
+ const defaultPrefix = "ubuntu-fde"
+ unlockKey, err := sbGetDiskUnlockKeyFromKernel(defaultPrefix, node, false)
+ if err != nil {
+ return fmt.Errorf("cannot get key for unlocked disk %s: %v", node, err)
+ }
+
+ if err := sbAddLUKS2ContainerUnlockKey(node, "installation-key", sb.DiskUnlockKey(unlockKey), sb.DiskUnlockKey(newKey)); err != nil {
+ return fmt.Errorf("cannot enroll new installation key: %v", err)
+ }
+
+ return nil
+}
+
+func RenameOrDeleteKeys(node string, renames map[string]string) error {
+ // FIXME: listing keys, then modifying could be a TOCTOU issue.
+ // we expect here nothing else is messing with the key slots.
+ slots, err := sb.ListLUKS2ContainerUnlockKeyNames(node)
+ if err != nil {
+ return fmt.Errorf("cannot list slots in partition save partition: %v", err)
+ }
+ for _, slot := range slots {
+ renameTo, found := renames[slot]
+ if found {
+ if err := sb.RenameLUKS2ContainerKey(node, slot, renameTo); err != nil {
+ if errors.Is(err, sb.ErrMissingCryptsetupFeature) {
+ if err := sb.DeleteLUKS2ContainerKey(node, slot); err != nil {
+ return fmt.Errorf("cannot remove old container key: %v", err)
+ }
+ } else {
+ return fmt.Errorf("cannot rename container key: %v", err)
+ }
+ }
+ }
+ }
+
+ return nil
+}
+
+func DeleteKeys(node string, matches map[string]bool) error {
+ slots, err := sb.ListLUKS2ContainerUnlockKeyNames(node)
+ if err != nil {
+ return fmt.Errorf("cannot list slots in partition save partition: %v", err)
+ }
+
+ for _, slot := range slots {
+ if matches[slot] {
+ if err := sb.DeleteLUKS2ContainerKey(node, slot); err != nil {
+ return fmt.Errorf("cannot remove old container key: %v", err)
+ }
+ }
+ }
+
+ return nil
+}
+
+func ResealKeysNextGeneration(devices []string, modelParams map[string][]*SealKeyModelParams) error {
+ type keyDataAndSlot struct {
+ KeyData *sb.KeyData
+ SlotName string
+ Device string
+ }
+ byPlatform := make(map[string][]*keyDataAndSlot)
+ for _, device := range devices {
+ slots, err := sbListLUKS2ContainerUnlockKeyNames(device)
+ if err != nil {
+ return err
+ }
+ for _, slotName := range slots {
+ reader, err := sb.NewLUKS2KeyDataReader(device, slotName)
+ if err != nil {
+ return err
+ }
+ keyData, err := sb.ReadKeyData(reader)
+ if err != nil {
+ return err
+ }
+ if keyData.PlatformName() == legacyFdeHooksPlatformName || keyData.Generation() == 1 {
+ return fmt.Errorf("Wrong resealing")
+ }
+ switch keyData.PlatformName() {
+ case "fde-hooks-v3":
+ case "tpm2":
+ byPlatform[keyData.PlatformName()] = append(byPlatform[keyData.PlatformName()], &keyDataAndSlot{keyData, slotName, device})
+ default:
+ }
+ }
+ }
+
+ const defaultPrefix = "ubuntu-fde"
+ const remove = false
+ var primaryKey sb.PrimaryKey
+ var errors []error
+ foundPrimaryKey := false
+ for _, device := range devices {
+ var err error
+ primaryKey, err = sb.GetPrimaryKeyFromKernel(defaultPrefix, device, remove)
+ if err == nil {
+ foundPrimaryKey = true
+ break
+ }
+ errors = append(errors, err)
+ }
+ if !foundPrimaryKey {
+ return fmt.Errorf("no primary key found")
+ }
+
+ hooksKS := make(map[string][]*sb.KeyData)
+ for _, ks := range byPlatform["fde-hooks-v3"] {
+ hooksKS[ks.KeyData.Role()] = append(hooksKS[ks.KeyData.Role()], ks.KeyData)
+ }
+
+ for role, keyDatas := range hooksKS {
+ var sbModels []sb.SnapModel
+ for _, p := range modelParams[role] {
+ sbModels = append(sbModels, p.Model)
+ }
+
+ for _, kd := range keyDatas {
+ hooksKeyData, err := sb_hooks.NewKeyData(kd)
+ if err != nil {
+ return fmt.Errorf("cannot read key data as hook key data: %v", err)
+ }
+ hooksKeyData.SetAuthorizedSnapModels(rand.Reader, primaryKey, sbModels...)
+ }
+ }
+
+ tpmKS := make(map[string][]*sb.KeyData)
+ for _, ks := range byPlatform["tpm2"] {
+ tpmKS[ks.KeyData.Role()] = append(tpmKS[ks.KeyData.Role()], ks.KeyData)
+ }
+
+ tpm, err := sbConnectToDefaultTPM()
+ if err != nil {
+ return fmt.Errorf("cannot connect to TPM: %v", err)
+ }
+ defer tpm.Close()
+ if !isTPMEnabled(tpm) {
+ return fmt.Errorf("TPM device is not enabled")
+ }
+
+ for role, keyDatas := range tpmKS {
+ mp, ok := modelParams[role]
+ if !ok {
+ continue
+ }
+
+ pcrProfile, err := buildPCRProtectionProfile(mp)
+ if err != nil {
+ return fmt.Errorf("cannot build new PCR protection profile: %w", err)
+ }
+
+ // TODO: find out which context when revocation should happen
+ if err := sbUpdateKeyDataPCRProtectionPolicy(tpm, primaryKey, pcrProfile, sb_tpm2.NoNewPCRPolicyVersion, keyDatas...); err != nil {
+ return fmt.Errorf("cannot update PCR protection policy: %w", err)
+ }
+ }
+
+ for _, p := range byPlatform {
+ for _, ks := range p {
+ writer, err := sb.NewLUKS2KeyDataWriter(ks.Device, ks.SlotName)
+ if err != nil {
+ return err
+ }
+ if err := ks.KeyData.WriteAtomic(writer); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}
+
+func unlockVolumeUsingSealedKeyGeneric(name, sourceDevice, targetDevice, mapperName string, opts *UnlockVolumeUsingSealedKeyOptions) (UnlockResult, error) {
+ // TODO:UC20: use sb.SecureConnectToDefaultTPM() if we decide there's benefit in doing that or
+ // we have a hard requirement for a valid EK cert chain for every boot (ie, panic
+ // if there isn't one). But we can't do that as long as we need to download
+ // intermediate certs from the manufacturer.
+
+ res := UnlockResult{IsEncrypted: true, PartDevice: sourceDevice}
+
+ if fdeHasRevealKey() {
+ sbSetKeyRevealer(&keyRevealerV3{})
+ }
+ model, err := opts.WhichModel()
+ if err != nil {
+ return res, fmt.Errorf("cannot retrieve which model to unlock for: %v", err)
+ }
+ sbSetModel(model)
+ sbSetBootMode(opts.BootMode)
+
+ method, err := unlockEncryptedPartitionNoKeyFile(mapperName, sourceDevice, opts.AllowRecoveryKey)
+ res.UnlockMethod = method
+ if err == nil {
+ res.FsDevice = targetDevice
+ }
+ return res, err
+}
+
+func unlockEncryptedPartitionNoKeyFile(mapperName, sourceDevice string, allowRecovery bool) (UnlockMethod, error) {
+ options := activateVolOpts(allowRecovery)
+ options.Model = sb.SkipSnapModelCheck
+ // ignoring model checker as it doesn't work with tpm "legacy" platform key data
+ authRequestor, err := newAuthRequestor()
+ if err != nil {
+ return NotUnlocked, fmt.Errorf("cannot build an auth requestor: %v", err)
+ }
+
+ err = sbActivateVolumeWithKeyData(mapperName, sourceDevice, authRequestor, options)
+ if err == sb.ErrRecoveryKeyUsed {
+ logger.Noticef("successfully activated encrypted device %q using a fallback activation method", sourceDevice)
+ return UnlockedWithRecoveryKey, nil
+ }
+ if err != nil {
+ return NotUnlocked, fmt.Errorf("cannot activate encrypted device %q: %v", sourceDevice, err)
+ }
+ logger.Noticef("successfully activated encrypted device %q with TPM", sourceDevice)
+ return UnlockedWithSealedKey, nil
+}
diff --git a/secboot/secboot_sb_test.go b/secboot/secboot_sb_test.go
index 465795969dd..5ab5e92b384 100644
--- a/secboot/secboot_sb_test.go
+++ b/secboot/secboot_sb_test.go
@@ -2,7 +2,7 @@
//go:build !nosecboot
/*
- * Copyright (C) 2021 Canonical Ltd
+ * Copyright (C) 2021, 2024 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
@@ -22,12 +22,11 @@ package secboot_test
import (
"bytes"
- "crypto/ecdsa"
"encoding/base64"
+ "encoding/binary"
"encoding/json"
"errors"
"fmt"
- "io"
"os"
"path/filepath"
"reflect"
@@ -37,6 +36,7 @@ import (
"github.com/canonical/go-tpm2/mu"
sb "github.com/snapcore/secboot"
sb_efi "github.com/snapcore/secboot/efi"
+ sb_hooks "github.com/snapcore/secboot/hooks"
sb_tpm2 "github.com/snapcore/secboot/tpm2"
. "gopkg.in/check.v1"
@@ -69,6 +69,13 @@ func (s *secbootSuite) SetUpTest(c *C) {
c.Assert(err, IsNil)
dirs.SetRootDir(rootDir)
s.AddCleanup(func() { dirs.SetRootDir("/") })
+
+ s.AddCleanup(secboot.MockSbSetModel(func(model sb.SnapModel) {
+ }))
+ s.AddCleanup(secboot.MockSbSetBootMode(func(mode string) {
+ }))
+ s.AddCleanup(secboot.MockSbSetKeyRevealer(func(kr sb_hooks.KeyRevealer) {
+ }))
}
func (s *secbootSuite) TestCheckTPMKeySealingSupported(c *C) {
@@ -583,23 +590,19 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncrypted(c *C) {
devicePath := filepath.Join("/dev/disk/by-partuuid", partuuid)
keyPath := filepath.Join("test-data", "keyfile")
- kd, err := sb_tpm2.NewKeyDataFromSealedKeyObjectFile(keyPath)
- c.Assert(err, IsNil)
- expectedID, err := kd.UniqueID()
- c.Assert(err, IsNil)
- restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, keyData *sb.KeyData, options *sb.ActivateVolumeOptions) (sb.SnapModelChecker, error) {
+ restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, authRequestor sb.AuthRequestor, options *sb.ActivateVolumeOptions, keys ...*sb.KeyData) error {
+
c.Assert(volumeName, Equals, "name-"+randomUUID)
c.Assert(sourceDevicePath, Equals, devicePath)
- c.Assert(keyData, NotNil)
- uID, err := keyData.UniqueID()
- c.Assert(err, IsNil)
- c.Check(uID, DeepEquals, expectedID)
+ c.Assert(keys, HasLen, 1)
+ c.Assert(keys[0], NotNil)
if tc.rkAllow {
c.Assert(*options, DeepEquals, sb.ActivateVolumeOptions{
PassphraseTries: 1,
RecoveryKeyTries: 3,
KeyringPrefix: "ubuntu-fde",
+ Model: sb.SkipSnapModelCheck,
})
} else {
c.Assert(*options, DeepEquals, sb.ActivateVolumeOptions{
@@ -607,16 +610,17 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncrypted(c *C) {
// activation with recovery key was disabled
RecoveryKeyTries: 0,
KeyringPrefix: "ubuntu-fde",
+ Model: sb.SkipSnapModelCheck,
})
}
if !tc.activated && tc.activateErr == nil {
- return nil, errors.New("activation error")
+ return errors.New("activation error")
}
- return nil, tc.activateErr
+ return tc.activateErr
})
defer restore()
- restore = secboot.MockSbActivateVolumeWithRecoveryKey(func(name, device string, keyReader io.Reader,
+ restore = secboot.MockSbActivateVolumeWithRecoveryKey(func(name, device string, authReq sb.AuthRequestor,
options *sb.ActivateVolumeOptions) error {
if !tc.rkAllow {
c.Fatalf("unexpected attempt to activate with recovery key")
@@ -628,6 +632,9 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncrypted(c *C) {
opts := &secboot.UnlockVolumeUsingSealedKeyOptions{
AllowRecoveryKey: tc.rkAllow,
+ WhichModel: func() (*asserts.Model, error) {
+ return fakeModel, nil
+ },
}
unlockRes, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(tc.disk, defaultDevice, keyPath, opts)
if tc.err == "" {
@@ -679,7 +686,7 @@ func (s *secbootSuite) TestEFIImageFromBootFile(c *C) {
{
// happy case for EFI image
bootFile: bootloader.NewBootFile("", existingFile, bootloader.RoleRecovery),
- efiImage: sb_efi.FileImage(existingFile),
+ efiImage: sb_efi.NewFileImage(existingFile),
},
{
// missing EFI image
@@ -689,7 +696,7 @@ func (s *secbootSuite) TestEFIImageFromBootFile(c *C) {
{
// happy case for snap file
bootFile: bootloader.NewBootFile(snapFile, "rel", bootloader.RoleRecovery),
- efiImage: sb_efi.SnapFileImage{Container: snapf, FileName: "rel"},
+ efiImage: sb_efi.NewSnapFileImage(snapf, "rel"),
},
{
// invalid snap file
@@ -789,8 +796,7 @@ func (s *secbootSuite) TestSealKey(c *C) {
tpmEnabled bool
missingFile bool
badSnapFile bool
- addEFISbPolicyErr error
- addEFIBootManagerErr error
+ addPCRProfileErr error
addSystemdEFIStubErr error
addSnapModelErr error
provisioningErr error
@@ -801,13 +807,12 @@ func (s *secbootSuite) TestSealKey(c *C) {
{tpmErr: mockErr, expectedErr: "cannot connect to TPM: some error"},
{tpmEnabled: false, expectedErr: "TPM device is not enabled"},
{tpmEnabled: true, missingFile: true, expectedErr: "cannot build EFI image load sequences: file /does/not/exist does not exist"},
- {tpmEnabled: true, badSnapFile: true, expectedErr: `cannot build EFI image load sequences: cannot process snap or snapdir: cannot read ".*/kernel.snap": EOF`},
- {tpmEnabled: true, addEFISbPolicyErr: mockErr, expectedErr: "cannot add EFI secure boot policy profile: some error"},
- {tpmEnabled: true, addEFIBootManagerErr: mockErr, expectedErr: "cannot add EFI boot manager profile: some error"},
+ {tpmEnabled: true, badSnapFile: true, expectedErr: `cannot build EFI image load sequences: cannot process snap or snapdir: cannot read .*\/kernel.snap": EOF`},
+ {tpmEnabled: true, addPCRProfileErr: mockErr, expectedErr: "cannot add EFI secure boot and boot manager policy profiles: some error"},
{tpmEnabled: true, addSystemdEFIStubErr: mockErr, expectedErr: "cannot add systemd EFI stub profile: some error"},
{tpmEnabled: true, addSnapModelErr: mockErr, expectedErr: "cannot add snap model profile: some error"},
{tpmEnabled: true, sealErr: mockErr, sealCalls: 1, expectedErr: "some error"},
- {tpmEnabled: true, sealCalls: 1, expectedErr: ""},
+ {tpmEnabled: true, sealCalls: 2, expectedErr: ""},
} {
c.Logf("tc: %v", idx)
tmpDir := c.MkDir()
@@ -836,8 +841,6 @@ func (s *secbootSuite) TestSealKey(c *C) {
mockBF = append(mockBF, bootloader.NewBootFile(snapPath, "kernel.efi", bootloader.RoleRecovery))
- myAuthKey := &ecdsa.PrivateKey{}
-
myParams := secboot.SealKeysParams{
ModelParams: []*secboot.SealKeyModelParams{
{
@@ -865,8 +868,8 @@ func (s *secbootSuite) TestSealKey(c *C) {
Model: &asserts.Model{},
},
},
- TPMPolicyAuthKey: myAuthKey,
- TPMPolicyAuthKeyFile: filepath.Join(tmpDir, "policy-auth-key-file"),
+ TPMPolicyAuthKeyFile: filepath.Join(tmpDir, "policy-auth-key-file"),
+
PCRPolicyCounterHandle: 42,
}
@@ -877,128 +880,93 @@ func (s *secbootSuite) TestSealKey(c *C) {
myKey2[i] = byte(128 + i)
}
+ keyFiles := c.MkDir()
+
myKeys := []secboot.SealKeyRequest{
{
- Key: myKey,
- KeyFile: "keyfile",
+ KeyFile: filepath.Join(keyFiles, "keyfile"),
+ Resetter: &secboot.MockKeyResetter{},
},
{
- Key: myKey2,
- KeyFile: "keyfile2",
+ KeyFile: filepath.Join(keyFiles, "keyfile2"),
+ Resetter: &secboot.MockKeyResetter{},
},
}
// events for
// a -> kernel
- sequences1 := []*sb_efi.ImageLoadEvent{
- {
- Source: sb_efi.Firmware,
- Image: sb_efi.FileImage(mockBF[0].Path),
- Next: []*sb_efi.ImageLoadEvent{
- {
- Source: sb_efi.Shim,
- Image: sb_efi.SnapFileImage{
- Container: kernelSnap,
- FileName: "kernel.efi",
- },
- },
- },
- },
- }
+ sequences1 := sb_efi.NewImageLoadSequences().Append(
+ sb_efi.NewImageLoadActivity(
+ sb_efi.NewFileImage(mockBF[0].Path),
+ ).Loads(sb_efi.NewImageLoadActivity(
+ sb_efi.NewSnapFileImage(
+ kernelSnap,
+ "kernel.efi",
+ ),
+ )),
+ )
// "cdk" events for
// c -> kernel OR
// d -> kernel
- cdk := []*sb_efi.ImageLoadEvent{
- {
- Source: sb_efi.Shim,
- Image: sb_efi.FileImage(mockBF[2].Path),
- Next: []*sb_efi.ImageLoadEvent{
- {
- Source: sb_efi.Shim,
- Image: sb_efi.SnapFileImage{
- Container: kernelSnap,
- FileName: "kernel.efi",
- },
- },
- },
- },
- {
- Source: sb_efi.Shim,
- Image: sb_efi.FileImage(mockBF[3].Path),
- Next: []*sb_efi.ImageLoadEvent{
- {
- Source: sb_efi.Shim,
- Image: sb_efi.SnapFileImage{
- Container: kernelSnap,
- FileName: "kernel.efi",
- },
- },
- },
- },
+ cdk := []sb_efi.ImageLoadActivity{
+ sb_efi.NewImageLoadActivity(
+ sb_efi.NewFileImage(mockBF[2].Path),
+ ).Loads(sb_efi.NewImageLoadActivity(
+ sb_efi.NewSnapFileImage(
+ kernelSnap,
+ "kernel.efi",
+ ),
+ )),
+ sb_efi.NewImageLoadActivity(
+ sb_efi.NewFileImage(mockBF[3].Path),
+ ).Loads(sb_efi.NewImageLoadActivity(
+ sb_efi.NewSnapFileImage(
+ kernelSnap,
+ "kernel.efi",
+ ),
+ )),
}
// events for
// a -> "cdk"
// b -> "cdk"
- sequences2 := []*sb_efi.ImageLoadEvent{
- {
- Source: sb_efi.Firmware,
- Image: sb_efi.FileImage(mockBF[0].Path),
- Next: cdk,
- },
- {
- Source: sb_efi.Firmware,
- Image: sb_efi.FileImage(mockBF[1].Path),
- Next: cdk,
- },
- }
+ sequences2 := sb_efi.NewImageLoadSequences().Append(
+ sb_efi.NewImageLoadActivity(
+ sb_efi.NewFileImage(mockBF[0].Path),
+ ).Loads(cdk...),
+ sb_efi.NewImageLoadActivity(
+ sb_efi.NewFileImage(mockBF[1].Path),
+ ).Loads(cdk...),
+ )
tpm, restore := mockSbTPMConnection(c, tc.tpmErr)
defer restore()
// mock adding EFI secure boot policy profile
- var pcrProfile *sb_tpm2.PCRProtectionProfile
- addEFISbPolicyCalls := 0
- restore = secboot.MockSbEfiAddSecureBootPolicyProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.SecureBootPolicyProfileParams) error {
- addEFISbPolicyCalls++
- pcrProfile = profile
- c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
- switch addEFISbPolicyCalls {
- case 1:
- c.Assert(params.LoadSequences, DeepEquals, sequences1)
- case 2:
- c.Assert(params.LoadSequences, DeepEquals, sequences2)
- default:
- c.Error("AddSecureBootPolicyProfile shouldn't be called a third time")
- }
- return tc.addEFISbPolicyErr
- })
- defer restore()
- // mock adding EFI boot manager profile
- addEFIBootManagerCalls := 0
- restore = secboot.MockSbEfiAddBootManagerProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.BootManagerProfileParams) error {
- addEFIBootManagerCalls++
- c.Assert(profile, Equals, pcrProfile)
- c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
- switch addEFISbPolicyCalls {
+ addPCRProfileCalls := 0
+ restore = secboot.MockSbEfiAddPCRProfile(func(pcrAlg tpm2.HashAlgorithmId, branch *sb_tpm2.PCRProtectionProfileBranch, loadSequences *sb_efi.ImageLoadSequences, options ...sb_efi.PCRProfileOption) error {
+ addPCRProfileCalls++
+ c.Assert(pcrAlg, Equals, tpm2.HashAlgorithmSHA256)
+ switch addPCRProfileCalls {
case 1:
- c.Assert(params.LoadSequences, DeepEquals, sequences1)
+ c.Assert(loadSequences, DeepEquals, sequences1)
case 2:
- c.Assert(params.LoadSequences, DeepEquals, sequences2)
+ c.Assert(loadSequences, DeepEquals, sequences2)
default:
- c.Error("AddBootManagerProfile shouldn't be called a third time")
+ c.Error("AddPCRProfile shouldn't be called a third time")
}
- return tc.addEFIBootManagerErr
+ return tc.addPCRProfileErr
})
defer restore()
// mock adding systemd EFI stub profile
addSystemdEfiStubCalls := 0
- restore = secboot.MockSbEfiAddSystemdStubProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.SystemdStubProfileParams) error {
+ restore = secboot.MockSbEfiAddSystemdStubProfile(func(profile *sb_tpm2.PCRProtectionProfileBranch, params *sb_efi.SystemdStubProfileParams) error {
addSystemdEfiStubCalls++
- c.Assert(profile, Equals, pcrProfile)
+ // XXX: not sure what we are testing with this:
+ //c.Assert(profile, Equals, pcrProfile)
c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
c.Assert(params.PCRIndex, Equals, 12)
switch addSystemdEfiStubCalls {
@@ -1015,9 +983,10 @@ func (s *secbootSuite) TestSealKey(c *C) {
// mock adding snap model profile
addSnapModelCalls := 0
- restore = secboot.MockSbAddSnapModelProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_tpm2.SnapModelProfileParams) error {
+ restore = secboot.MockSbAddSnapModelProfile(func(profile *sb_tpm2.PCRProtectionProfileBranch, params *sb_tpm2.SnapModelProfileParams) error {
addSnapModelCalls++
- c.Assert(profile, Equals, pcrProfile)
+ // XXX: not sure what we are testing with this:
+ //c.Assert(profile, Equals, pcrProfile)
c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
c.Assert(params.PCRIndex, Equals, 12)
switch addSnapModelCalls {
@@ -1034,13 +1003,11 @@ func (s *secbootSuite) TestSealKey(c *C) {
// mock sealing
sealCalls := 0
- restore = secboot.MockSbSealKeyToTPMMultiple(func(t *sb_tpm2.Connection, kr []*sb_tpm2.SealKeyRequest, params *sb_tpm2.KeyCreationParams) (sb_tpm2.PolicyAuthKey, error) {
+ restore = secboot.MockSbNewTPMProtectedKey(func(t *sb_tpm2.Connection, params *sb_tpm2.ProtectKeyParams) (protectedKey *sb.KeyData, primaryKey sb.PrimaryKey, unlockKey sb.DiskUnlockKey, err error) {
sealCalls++
c.Assert(t, Equals, tpm)
- c.Assert(kr, DeepEquals, []*sb_tpm2.SealKeyRequest{{Key: myKey, Path: "keyfile"}, {Key: myKey2, Path: "keyfile2"}})
- c.Assert(params.AuthKey, Equals, myAuthKey)
c.Assert(params.PCRPolicyCounterHandle, Equals, tpm2.Handle(42))
- return sb_tpm2.PolicyAuthKey{}, tc.sealErr
+ return &sb.KeyData{}, sb.PrimaryKey{}, sb.DiskUnlockKey{}, tc.sealErr
})
defer restore()
@@ -1050,11 +1017,10 @@ func (s *secbootSuite) TestSealKey(c *C) {
})
defer restore()
- err := secboot.SealKeys(myKeys, &myParams)
+ _, err := secboot.SealKeys(myKeys, &myParams)
if tc.expectedErr == "" {
c.Assert(err, IsNil)
- c.Assert(addEFISbPolicyCalls, Equals, 2)
- c.Assert(addSystemdEfiStubCalls, Equals, 2)
+ c.Assert(addPCRProfileCalls, Equals, 2)
c.Assert(addSnapModelCalls, Equals, 2)
c.Assert(osutil.FileExists(myParams.TPMPolicyAuthKeyFile), Equals, true)
} else {
@@ -1071,8 +1037,7 @@ func (s *secbootSuite) TestResealKey(c *C) {
tpmErr error
tpmEnabled bool
missingFile bool
- addEFISbPolicyErr error
- addEFIBootManagerErr error
+ addPCRProfileErr error
addSystemdEFIStubErr error
addSnapModelErr error
readSealedKeyObjectErr error
@@ -1089,14 +1054,13 @@ func (s *secbootSuite) TestResealKey(c *C) {
// unhappy cases
{tpmErr: mockErr, expectedErr: "cannot connect to TPM: some error"},
{tpmEnabled: false, expectedErr: "TPM device is not enabled"},
- {tpmEnabled: true, missingFile: true, expectedErr: "cannot build EFI image load sequences: file .*/file.efi does not exist"},
- {tpmEnabled: true, addEFISbPolicyErr: mockErr, expectedErr: "cannot add EFI secure boot policy profile: some error"},
- {tpmEnabled: true, addEFIBootManagerErr: mockErr, expectedErr: "cannot add EFI boot manager profile: some error"},
- {tpmEnabled: true, addSystemdEFIStubErr: mockErr, expectedErr: "cannot add systemd EFI stub profile: some error"},
- {tpmEnabled: true, addSnapModelErr: mockErr, expectedErr: "cannot add snap model profile: some error"},
- {tpmEnabled: true, readSealedKeyObjectErr: mockErr, expectedErr: "some error"},
- {tpmEnabled: true, resealErr: mockErr, resealCalls: 1, expectedErr: "some error"},
- {tpmEnabled: true, revokeErr: errors.New("revoke error"), resealCalls: 1, revokeCalls: 1, expectedErr: "revoke error"},
+ {tpmEnabled: true, missingFile: true, expectedErr: "cannot build new PCR protection profile: cannot build EFI image load sequences: file .*\\/file.efi does not exist"},
+ {tpmEnabled: true, addPCRProfileErr: mockErr, expectedErr: "cannot build new PCR protection profile: cannot add EFI secure boot and boot manager policy profiles: some error"},
+ {tpmEnabled: true, addSystemdEFIStubErr: mockErr, expectedErr: "cannot build new PCR protection profile: cannot add systemd EFI stub profile: some error"},
+ {tpmEnabled: true, addSnapModelErr: mockErr, expectedErr: "cannot build new PCR protection profile: cannot add snap model profile: some error"},
+ {tpmEnabled: true, readSealedKeyObjectErr: mockErr, expectedErr: "cannot read key file .*: some error"},
+ {tpmEnabled: true, resealErr: mockErr, resealCalls: 1, expectedErr: "cannot update legacy PCR protection policy: some error"},
+ {tpmEnabled: true, revokeErr: errors.New("revoke error"), resealCalls: 1, revokeCalls: 1, expectedErr: "cannot revoke old PCR protection policies: revoke error"},
} {
mockTPMPolicyAuthKey := []byte{1, 3, 3, 7}
mockTPMPolicyAuthKeyFile := filepath.Join(c.MkDir(), "policy-auth-key-file")
@@ -1138,12 +1102,11 @@ func (s *secbootSuite) TestResealKey(c *C) {
mockSealedKeyObjects = append(mockSealedKeyObjects, mockSealedKeyObject)
}
- sequences := []*sb_efi.ImageLoadEvent{
- {
- Source: sb_efi.Firmware,
- Image: sb_efi.FileImage(mockEFI.Path),
- },
- }
+ sequences := sb_efi.NewImageLoadSequences().Append(
+ sb_efi.NewImageLoadActivity(
+ sb_efi.NewFileImage(mockEFI.Path),
+ ),
+ )
// mock TPM connection
tpm, restore := mockSbTPMConnection(c, tc.tpmErr)
@@ -1155,34 +1118,20 @@ func (s *secbootSuite) TestResealKey(c *C) {
})
defer restore()
- // mock adding EFI secure boot policy profile
- var pcrProfile *sb_tpm2.PCRProtectionProfile
- addEFISbPolicyCalls := 0
- restore = secboot.MockSbEfiAddSecureBootPolicyProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.SecureBootPolicyProfileParams) error {
- addEFISbPolicyCalls++
- pcrProfile = profile
- c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
- c.Assert(params.LoadSequences, DeepEquals, sequences)
- return tc.addEFISbPolicyErr
- })
- defer restore()
-
- // mock adding EFI boot manager profile
- addEFIBootManagerCalls := 0
- restore = secboot.MockSbEfiAddBootManagerProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.BootManagerProfileParams) error {
- addEFIBootManagerCalls++
- c.Assert(profile, Equals, pcrProfile)
- c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
- c.Assert(params.LoadSequences, DeepEquals, sequences)
- return tc.addEFIBootManagerErr
+ addPCRProfileCalls := 0
+ restore = secboot.MockSbEfiAddPCRProfile(func(pcrAlg tpm2.HashAlgorithmId, branch *sb_tpm2.PCRProtectionProfileBranch, loadSequences *sb_efi.ImageLoadSequences, options ...sb_efi.PCRProfileOption) error {
+ addPCRProfileCalls++
+ c.Assert(pcrAlg, Equals, tpm2.HashAlgorithmSHA256)
+ c.Assert(loadSequences, DeepEquals, sequences)
+ return tc.addPCRProfileErr
})
defer restore()
// mock adding systemd EFI stub profile
addSystemdEfiStubCalls := 0
- restore = secboot.MockSbEfiAddSystemdStubProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.SystemdStubProfileParams) error {
+ restore = secboot.MockSbEfiAddSystemdStubProfile(func(profile *sb_tpm2.PCRProtectionProfileBranch, params *sb_efi.SystemdStubProfileParams) error {
addSystemdEfiStubCalls++
- c.Assert(profile, Equals, pcrProfile)
+ //c.Assert(profile, Equals, pcrProfile)
c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
c.Assert(params.PCRIndex, Equals, 12)
c.Assert(params.KernelCmdlines, DeepEquals, myParams.ModelParams[0].KernelCmdlines)
@@ -1192,9 +1141,9 @@ func (s *secbootSuite) TestResealKey(c *C) {
// mock adding snap model profile
addSnapModelCalls := 0
- restore = secboot.MockSbAddSnapModelProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_tpm2.SnapModelProfileParams) error {
+ restore = secboot.MockSbAddSnapModelProfile(func(profile *sb_tpm2.PCRProtectionProfileBranch, params *sb_tpm2.SnapModelProfileParams) error {
addSnapModelCalls++
- c.Assert(profile, Equals, pcrProfile)
+ //c.Assert(profile, Equals, pcrProfile)
c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
c.Assert(params.PCRIndex, Equals, 12)
c.Assert(params.Models[0], DeepEquals, myParams.ModelParams[0].Model)
@@ -1204,31 +1153,31 @@ func (s *secbootSuite) TestResealKey(c *C) {
// mock ReadSealedKeyObject
readSealedKeyObjectCalls := 0
- restore = secboot.MockSbReadSealedKeyObjectFromFile(func(keyfile string) (*sb_tpm2.SealedKeyObject, error) {
+ restore = secboot.MockReadKeyFile(func(keyfile string) (*sb.KeyData, *sb_tpm2.SealedKeyObject, error) {
readSealedKeyObjectCalls++
c.Assert(keyfile, Equals, myParams.KeyFiles[readSealedKeyObjectCalls-1])
- return mockSealedKeyObjects[readSealedKeyObjectCalls-1], tc.readSealedKeyObjectErr
+ return nil, mockSealedKeyObjects[readSealedKeyObjectCalls-1], tc.readSealedKeyObjectErr
})
defer restore()
// mock PCR protection policy update
resealCalls := 0
- restore = secboot.MockSbUpdateKeyPCRProtectionPolicyMultiple(func(t *sb_tpm2.Connection, keys []*sb_tpm2.SealedKeyObject, authKey sb_tpm2.PolicyAuthKey, profile *sb_tpm2.PCRProtectionProfile) error {
+ restore = secboot.MockSbUpdateKeyPCRProtectionPolicyMultiple(func(t *sb_tpm2.Connection, keys []*sb_tpm2.SealedKeyObject, authKey sb.PrimaryKey, profile *sb_tpm2.PCRProtectionProfile) error {
resealCalls++
c.Assert(t, Equals, tpm)
c.Assert(keys, DeepEquals, mockSealedKeyObjects)
- c.Assert(authKey, DeepEquals, sb_tpm2.PolicyAuthKey(mockTPMPolicyAuthKey))
- c.Assert(profile, Equals, pcrProfile)
+ c.Assert(authKey, DeepEquals, sb.PrimaryKey(mockTPMPolicyAuthKey))
+ //c.Assert(profile, Equals, pcrProfile)
return tc.resealErr
})
defer restore()
// mock PCR protection policy revoke
revokeCalls := 0
- restore = secboot.MockSbSealedKeyObjectRevokeOldPCRProtectionPolicies(func(sko *sb_tpm2.SealedKeyObject, t *sb_tpm2.Connection, authKey sb_tpm2.PolicyAuthKey) error {
+ restore = secboot.MockSbSealedKeyObjectRevokeOldPCRProtectionPolicies(func(sko *sb_tpm2.SealedKeyObject, t *sb_tpm2.Connection, authKey sb.PrimaryKey) error {
revokeCalls++
c.Assert(sko, Equals, mockSealedKeyObjects[0])
c.Assert(t, Equals, tpm)
- c.Assert(authKey, DeepEquals, sb_tpm2.PolicyAuthKey(mockTPMPolicyAuthKey))
+ c.Assert(authKey, DeepEquals, sb.PrimaryKey(mockTPMPolicyAuthKey))
return tc.revokeErr
})
defer restore()
@@ -1236,7 +1185,7 @@ func (s *secbootSuite) TestResealKey(c *C) {
err = secboot.ResealKeys(myParams)
if tc.expectedErr == "" {
c.Assert(err, IsNil)
- c.Assert(addEFISbPolicyCalls, Equals, 1)
+ c.Assert(addPCRProfileCalls, Equals, 1)
c.Assert(addSystemdEfiStubCalls, Equals, 1)
c.Assert(addSnapModelCalls, Equals, 1)
c.Assert(keyFile, testutil.FilePresent)
@@ -1257,15 +1206,15 @@ func (s *secbootSuite) TestResealKey(c *C) {
func (s *secbootSuite) TestSealKeyNoModelParams(c *C) {
myKeys := []secboot.SealKeyRequest{
{
- Key: keys.EncryptionKey{},
- KeyFile: "keyfile",
+ KeyFile: "keyfile",
+ Resetter: &secboot.MockKeyResetter{},
},
}
myParams := secboot.SealKeysParams{
TPMPolicyAuthKeyFile: "policy-auth-key-file",
}
- err := secboot.SealKeys(myKeys, &myParams)
+ _, err := secboot.SealKeys(myKeys, &myParams)
c.Assert(err, ErrorMatches, "at least one set of model-specific parameters is required")
}
@@ -1301,7 +1250,7 @@ func mockSbTPMConnection(c *C, tpmErr error) (*sb_tpm2.Connection, func()) {
func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyBadDisk(c *C) {
disk := &disks.MockDiskMapping{}
- unlockRes, err := secboot.UnlockEncryptedVolumeUsingKey(disk, "ubuntu-save", []byte("fooo"))
+ unlockRes, err := secboot.UnlockEncryptedVolumeUsingPlatformKey(disk, "ubuntu-save", []byte("fooo"))
c.Assert(err, ErrorMatches, `filesystem label "ubuntu-save-enc" not found`)
c.Check(unlockRes, DeepEquals, secboot.UnlockResult{})
}
@@ -1320,7 +1269,7 @@ func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyUUIDError(c *C) {
})
defer restore()
- unlockRes, err := secboot.UnlockEncryptedVolumeUsingKey(disk, "ubuntu-save", []byte("fooo"))
+ unlockRes, err := secboot.UnlockEncryptedVolumeUsingPlatformKey(disk, "ubuntu-save", []byte("fooo"))
c.Assert(err, ErrorMatches, "mocked uuid error")
c.Check(unlockRes, DeepEquals, secboot.UnlockResult{
PartDevice: "/dev/disk/by-partuuid/123-123-123",
@@ -1341,6 +1290,9 @@ func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyHappy(c *C) {
return "random-uuid-123-123", nil
})
defer restore()
+ defer secboot.MockListLUKS2ContainerUnlockKeyNames(func(devicePath string) ([]string, error) {
+ return []string{}, nil
+ })()
restore = secboot.MockSbActivateVolumeWithKey(func(volumeName, sourceDevicePath string, key []byte,
options *sb.ActivateVolumeOptions) error {
c.Check(options, DeepEquals, &sb.ActivateVolumeOptions{})
@@ -1350,7 +1302,7 @@ func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyHappy(c *C) {
return nil
})
defer restore()
- unlockRes, err := secboot.UnlockEncryptedVolumeUsingKey(disk, "ubuntu-save", []byte("fooo"))
+ unlockRes, err := secboot.UnlockEncryptedVolumeUsingPlatformKey(disk, "ubuntu-save", []byte("fooo"))
c.Assert(err, IsNil)
c.Check(unlockRes, DeepEquals, secboot.UnlockResult{
PartDevice: "/dev/disk/by-partuuid/123-123-123",
@@ -1373,12 +1325,15 @@ func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyErr(c *C) {
return "random-uuid-123-123", nil
})
defer restore()
+ defer secboot.MockListLUKS2ContainerUnlockKeyNames(func(devicePath string) ([]string, error) {
+ return []string{}, nil
+ })()
restore = secboot.MockSbActivateVolumeWithKey(func(volumeName, sourceDevicePath string, key []byte,
options *sb.ActivateVolumeOptions) error {
return fmt.Errorf("failed")
})
defer restore()
- unlockRes, err := secboot.UnlockEncryptedVolumeUsingKey(disk, "ubuntu-save", []byte("fooo"))
+ unlockRes, err := secboot.UnlockEncryptedVolumeUsingPlatformKey(disk, "ubuntu-save", []byte("fooo"))
c.Assert(err, ErrorMatches, "failed")
// we would have at least identified that the device is a decrypted one
c.Check(unlockRes, DeepEquals, secboot.UnlockResult{
@@ -1410,21 +1365,27 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyErr(
defaultDevice := "name"
mockSealedKeyFile := makeMockSealedKeyFile(c, nil)
- restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, keyData *sb.KeyData, options *sb.ActivateVolumeOptions) (sb.SnapModelChecker, error) {
+ restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, authRequestor sb.AuthRequestor, options *sb.ActivateVolumeOptions, keys ...*sb.KeyData) error {
// XXX: this is what the real
// MockSbActivateVolumeWithKeyData will do
+ c.Assert(keys, HasLen, 1)
+ keyData := keys[0]
_, _, err := keyData.RecoverKeys()
if err != nil {
- return nil, err
+ return err
}
c.Fatal("should not get this far")
- return nil, nil
+ return nil
})
defer restore()
- opts := &secboot.UnlockVolumeUsingSealedKeyOptions{}
+ opts := &secboot.UnlockVolumeUsingSealedKeyOptions{
+ WhichModel: func() (*asserts.Model, error) {
+ return fakeModel, nil
+ },
+ }
_, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts)
- c.Assert(err, ErrorMatches, `cannot unlock encrypted partition: cannot recover keys because of an unexpected error: cannot run \["fde-reveal-key"\]: helper error`)
+ c.Assert(err, ErrorMatches, `cannot unlock encrypted partition: cannot perform action because of an unexpected error: cannot run \["fde-reveal-key"\]: helper error`)
}
// this test that v1 hooks and raw binary v1 created sealedKey files still work
@@ -1508,8 +1469,6 @@ func (s *secbootSuite) TestLockSealedKeysCallsFdeReveal(c *C) {
}
func (s *secbootSuite) TestSealKeysWithFDESetupHookHappy(c *C) {
- tmpdir := c.MkDir()
-
n := 0
sealedPrefix := []byte("SEALED:")
rawHandle1 := json.RawMessage(`{"handle-for":"key1"}`)
@@ -1529,65 +1488,26 @@ func (s *secbootSuite) TestSealKeysWithFDESetupHookHappy(c *C) {
return json.Marshal(res)
}
- key1 := keys.EncryptionKey{1, 2, 3, 4}
- key2 := keys.EncryptionKey{5, 6, 7, 8}
- auxKey := keys.AuxKey{9, 10, 11, 12}
+ tmpdir := c.MkDir()
key1Fn := filepath.Join(tmpdir, "key1.key")
key2Fn := filepath.Join(tmpdir, "key2.key")
auxKeyFn := filepath.Join(tmpdir, "aux-key")
params := secboot.SealKeysWithFDESetupHookParams{
Model: fakeModel,
- AuxKey: auxKey,
AuxKeyFile: auxKeyFn,
}
err := secboot.SealKeysWithFDESetupHook(runFDESetupHook,
[]secboot.SealKeyRequest{
- {Key: key1, KeyName: "key1", KeyFile: key1Fn},
- {Key: key2, KeyName: "key2", KeyFile: key2Fn},
+ {KeyName: "key1", KeyFile: key1Fn, Resetter: &secboot.MockKeyResetter{}},
+ {KeyName: "key2", KeyFile: key2Fn, Resetter: &secboot.MockKeyResetter{}},
}, ¶ms)
c.Assert(err, IsNil)
// check that runFDESetupHook was called the expected way
- key1Payload := sb.MarshalKeys([]byte(key1), auxKey[:])
- key2Payload := sb.MarshalKeys([]byte(key2), auxKey[:])
- c.Check(runFDESetupHookReqs, DeepEquals, []*fde.SetupRequest{
- {Op: "initial-setup", Key: key1Payload, KeyName: "key1"},
- {Op: "initial-setup", Key: key2Payload, KeyName: "key2"},
- })
- // check that the sealed keys got written to the expected places
- for _, p := range []string{key1Fn, key2Fn} {
- c.Check(p, testutil.FilePresent)
- }
- c.Check(auxKeyFn, testutil.FileEquals, auxKey[:])
-
- // roundtrip to check what was written
- s.checkV2Key(c, key1Fn, sealedPrefix, key1, auxKey[:], fakeModel, &rawHandle1)
- nullHandle := json.RawMessage("null")
- s.checkV2Key(c, key2Fn, sealedPrefix, key2, auxKey[:], fakeModel, &nullHandle)
-}
-
-func (s *secbootSuite) TestSealKeysWithFDESetupHookSad(c *C) {
- tmpdir := c.MkDir()
-
- runFDESetupHook := func(req *fde.SetupRequest) ([]byte, error) {
- return nil, fmt.Errorf("hook failed")
- }
-
- key := keys.EncryptionKey{1, 2, 3, 4}
- auxKey := keys.AuxKey{5, 6, 7, 8}
- keyFn := filepath.Join(tmpdir, "key.key")
- auxKeyFn := filepath.Join(tmpdir, "aux-key")
- params := secboot.SealKeysWithFDESetupHookParams{
- Model: fakeModel,
- AuxKey: auxKey,
- AuxKeyFile: auxKeyFn,
- }
- err := secboot.SealKeysWithFDESetupHook(runFDESetupHook,
- []secboot.SealKeyRequest{
- {Key: key, KeyName: "key1", KeyFile: keyFn},
- }, ¶ms)
- c.Assert(err, ErrorMatches, "hook failed")
- c.Check(keyFn, testutil.FileAbsent)
- c.Check(auxKeyFn, testutil.FileAbsent)
+ c.Check(runFDESetupHookReqs, HasLen, 2)
+ c.Check(runFDESetupHookReqs[0].Op, Equals, "initial-setup")
+ c.Check(runFDESetupHookReqs[1].Op, Equals, "initial-setup")
+ c.Check(runFDESetupHookReqs[0].KeyName, Equals, "key1")
+ c.Check(runFDESetupHookReqs[1].KeyName, Equals, "key2")
}
func makeMockDiskKey() keys.EncryptionKey {
@@ -1601,7 +1521,12 @@ func makeMockAuxKey() keys.AuxKey {
func makeMockUnencryptedPayload() []byte {
diskKey := makeMockDiskKey()
auxKey := makeMockAuxKey()
- return sb.MarshalKeys([]byte(diskKey), auxKey[:])
+ payload := new(bytes.Buffer)
+ binary.Write(payload, binary.BigEndian, uint16(len(diskKey)))
+ payload.Write(diskKey)
+ binary.Write(payload, binary.BigEndian, uint16(len(auxKey[:])))
+ payload.Write(auxKey[:])
+ return payload.Bytes()
}
func makeMockEncryptedPayload() []byte {
@@ -1653,21 +1578,6 @@ var fakeModel = assertstest.FakeAssertion(map[string]interface{}{
}},
}).(*asserts.Model)
-type mockSnapModelChecker struct {
- mockIsAuthorized bool
- mockError error
-}
-
-func (c *mockSnapModelChecker) IsModelAuthorized(model sb.SnapModel) (bool, error) {
- if model.BrandID() != "my-brand" || model.Model() != "my-model" {
- return false, fmt.Errorf("not the test model")
- }
- return c.mockIsAuthorized, c.mockError
-}
-func (c *mockSnapModelChecker) VolumeName() string {
- return "volume-name"
-}
-
func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2(c *C) {
var reqs []*fde.RevealKeyRequest
restore := fde.MockRunFDERevealKey(func(req *fde.RevealKeyRequest) ([]byte, error) {
@@ -1698,7 +1608,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2(c
expectedKey := makeMockDiskKey()
expectedAuxKey := makeMockAuxKey()
activated := 0
- restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, keyData *sb.KeyData, options *sb.ActivateVolumeOptions) (sb.SnapModelChecker, error) {
+ restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, authRequestor sb.AuthRequestor, options *sb.ActivateVolumeOptions, keys ...*sb.KeyData) error {
+ fmt.Printf("B\n")
+ c.Assert(keys, HasLen, 1)
+ keyData := keys[0]
+
activated++
c.Check(options.RecoveryKeyTries, Equals, 0)
// XXX: this is what the real
@@ -1707,8 +1621,7 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2(c
c.Assert(err, IsNil)
c.Check([]byte(key), DeepEquals, []byte(expectedKey))
c.Check([]byte(auxKey), DeepEquals, expectedAuxKey[:])
- modChecker := &mockSnapModelChecker{mockIsAuthorized: true}
- return modChecker, nil
+ return nil
})
defer restore()
@@ -1757,18 +1670,14 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2Mo
}
activated := 0
- restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, keyData *sb.KeyData, options *sb.ActivateVolumeOptions) (sb.SnapModelChecker, error) {
+ restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, authRequestor sb.AuthRequestor, options *sb.ActivateVolumeOptions, keys ...*sb.KeyData) error {
+ c.Check(options.Model, Equals, fakeModel)
activated++
- modChecker := &mockSnapModelChecker{mockIsAuthorized: false}
- return modChecker, nil
- })
- defer restore()
-
- deactivated := 0
- restore = secboot.MockSbDeactivateVolume(func(volumeName string) error {
- deactivated++
- c.Check(volumeName, Equals, "device-name-random-uuid-for-test")
- return nil
+ // XXX: remove entire test as it now only tests mocks? The
+ // new secboot code checks for the model internally in
+ // ActivateVolumeWIthKeyData (before it expecting a
+ // model-checker that we provided)
+ return fmt.Errorf("cannot unlock volume: model %s/%s not authorized", fakeModel.AuthorityID(), fakeModel.DisplayName())
})
defer restore()
@@ -1782,13 +1691,12 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2Mo
},
}
res, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts)
- c.Assert(err, ErrorMatches, `cannot unlock volume: model my-brand/my-model not authorized`)
+ c.Assert(err, ErrorMatches, `cannot unlock encrypted partition: cannot unlock volume: model my-brand/my-model not authorized`)
c.Check(res, DeepEquals, secboot.UnlockResult{
IsEncrypted: true,
PartDevice: "/dev/disk/by-partuuid/enc-dev-partuuid",
})
c.Check(activated, Equals, 1)
- c.Check(deactivated, Equals, 1)
}
func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2ModelCheckerError(c *C) {
@@ -1811,11 +1719,13 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2Mo
},
}
+ // XXX: remove this test as the "modelchecker" code was
+ // removed from secboot and is now done internally as part of
+ // ActivateVolumeWithKeyData?
activated := 0
- restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, keyData *sb.KeyData, options *sb.ActivateVolumeOptions) (sb.SnapModelChecker, error) {
+ restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, authRequestor sb.AuthRequestor, options *sb.ActivateVolumeOptions, keys ...*sb.KeyData) error {
activated++
- modChecker := &mockSnapModelChecker{mockError: errors.New("model checker error")}
- return modChecker, nil
+ return errors.New("model checker error")
})
defer restore()
@@ -1829,7 +1739,7 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2Mo
},
}
res, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts)
- c.Assert(err, ErrorMatches, `cannot check if model is authorized to unlock disk: model checker error`)
+ c.Assert(err, ErrorMatches, `cannot unlock encrypted partition: model checker error`)
c.Check(res, DeepEquals, secboot.UnlockResult{
IsEncrypted: true,
PartDevice: "/dev/disk/by-partuuid/enc-dev-partuuid",
@@ -1865,14 +1775,17 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2Al
}
activated := 0
- restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, keyData *sb.KeyData, options *sb.ActivateVolumeOptions) (sb.SnapModelChecker, error) {
+ restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, authRequestor sb.AuthRequestor, options *sb.ActivateVolumeOptions, keys ...*sb.KeyData) error {
+ c.Assert(keys, HasLen, 1)
+ keyData := keys[0]
+
activated++
c.Check(options.RecoveryKeyTries, Equals, 3)
// XXX: this is what the real
// MockSbActivateVolumeWithKeyData will do
_, _, err := keyData.RecoverKeys()
c.Assert(err, NotNil)
- return nil, sb.ErrRecoveryKeyUsed
+ return sb.ErrRecoveryKeyUsed
})
defer restore()
@@ -1880,7 +1793,12 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2Al
handle := json.RawMessage(`{"a": "handle"}`)
mockSealedKeyFile := makeMockSealedKeyFile(c, handle)
- opts := &secboot.UnlockVolumeUsingSealedKeyOptions{AllowRecoveryKey: true}
+ opts := &secboot.UnlockVolumeUsingSealedKeyOptions{
+ AllowRecoveryKey: true,
+ WhichModel: func() (*asserts.Model, error) {
+ return fakeModel, nil
+ },
+ }
res, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts)
c.Assert(err, IsNil)
c.Check(res, DeepEquals, secboot.UnlockResult{
@@ -1898,7 +1816,7 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2Al
func (s *secbootSuite) checkV2Key(c *C, keyFn string, prefixToDrop, expectedKey, expectedAuxKey []byte, authModel *asserts.Model, handle *json.RawMessage) {
restore := fde.MockRunFDERevealKey(func(req *fde.RevealKeyRequest) ([]byte, error) {
- c.Check(req.Handle, DeepEquals, handle)
+ c.Check(string(*req.Handle), DeepEquals, string(*handle))
c.Check(bytes.HasPrefix(req.SealedKey, prefixToDrop), Equals, true)
payload := req.SealedKey[len(prefixToDrop):]
return []byte(fmt.Sprintf(`{"key": "%s"}`, base64.StdEncoding.EncodeToString(payload))), nil
@@ -1925,7 +1843,10 @@ func (s *secbootSuite) checkV2Key(c *C, keyFn string, prefixToDrop, expectedKey,
}
activated := 0
- restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, keyData *sb.KeyData, options *sb.ActivateVolumeOptions) (sb.SnapModelChecker, error) {
+ restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, authRequestor sb.AuthRequestor, options *sb.ActivateVolumeOptions, keys ...*sb.KeyData) error {
+ c.Assert(keys, HasLen, 1)
+ keyData := keys[0]
+
activated++
// XXX: this is what the real
// MockSbActivateVolumeWithKeyData will do
@@ -1937,8 +1858,7 @@ func (s *secbootSuite) checkV2Key(c *C, keyFn string, prefixToDrop, expectedKey,
ok, err := keyData.IsSnapModelAuthorized(auxKey, authModel)
c.Assert(err, IsNil)
c.Check(ok, Equals, true)
- modChecker := &mockSnapModelChecker{mockIsAuthorized: true}
- return modChecker, nil
+ return nil
})
defer restore()
@@ -2049,22 +1969,29 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyBadJ
},
}
- restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, keyData *sb.KeyData, options *sb.ActivateVolumeOptions) (sb.SnapModelChecker, error) {
+ restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, authRequestor sb.AuthRequestor, options *sb.ActivateVolumeOptions, keys ...*sb.KeyData) error {
+ c.Assert(keys, HasLen, 1)
+ keyData := keys[0]
+
// XXX: this is what the real
// MockSbActivateVolumeWithKeyData will do
_, _, err := keyData.RecoverKeys()
if err != nil {
- return nil, err
+ return err
}
c.Fatal("should not get this far")
- return nil, nil
+ return nil
})
defer restore()
defaultDevice := "device-name"
mockSealedKeyFile := makeMockSealedKeyFile(c, nil)
- opts := &secboot.UnlockVolumeUsingSealedKeyOptions{}
+ opts := &secboot.UnlockVolumeUsingSealedKeyOptions{
+ WhichModel: func() (*asserts.Model, error) {
+ return fakeModel, nil
+ },
+ }
_, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts)
c.Check(err, ErrorMatches, `cannot unlock encrypted partition: invalid key data:.*`)
@@ -2073,14 +2000,14 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyBadJ
func (s *secbootSuite) TestPCRHandleOfSealedKey(c *C) {
d := c.MkDir()
h, err := secboot.PCRHandleOfSealedKey(filepath.Join(d, "not-found"))
- c.Assert(err, ErrorMatches, "cannot open key file: .*/not-found: no such file or directory")
+ c.Assert(err, ErrorMatches, "cannot read key file .*/not-found:.* no such file or directory")
c.Assert(h, Equals, uint32(0))
skf := filepath.Join(d, "sealed-key")
// partially valid sealed key with correct header magic
c.Assert(os.WriteFile(skf, []byte{0x55, 0x53, 0x4b, 0x24, 1, 1, 1, 'k', 'e', 'y', 1, 1, 1}, 0644), IsNil)
h, err = secboot.PCRHandleOfSealedKey(skf)
- c.Assert(err, ErrorMatches, "(?s)cannot open key file: invalid key data: cannot unmarshal AFIS header: .*")
+ c.Assert(err, ErrorMatches, "(?s)cannot read key file .*: invalid key data: cannot unmarshal AFIS header: .*")
c.Check(h, Equals, uint32(0))
// TODO simulate the happy case, which needs a real (or at least
diff --git a/secboot/secboot_tpm.go b/secboot/secboot_tpm.go
index 8257ccf4009..3151f215642 100644
--- a/secboot/secboot_tpm.go
+++ b/secboot/secboot_tpm.go
@@ -2,7 +2,7 @@
//go:build !nosecboot
/*
- * Copyright (C) 2021 Canonical Ltd
+ * Copyright (C) 2021, 2024 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
@@ -21,9 +21,11 @@
package secboot
import (
+ "bytes"
"crypto/rand"
"errors"
"fmt"
+ "io"
"os"
"path/filepath"
"strings"
@@ -53,15 +55,16 @@ var (
sbMeasureSnapSystemEpochToTPM = sb_tpm2.MeasureSnapSystemEpochToTPM
sbMeasureSnapModelToTPM = sb_tpm2.MeasureSnapModelToTPM
sbBlockPCRProtectionPolicies = sb_tpm2.BlockPCRProtectionPolicies
- sbefiAddSecureBootPolicyProfile = sb_efi.AddSecureBootPolicyProfile
- sbefiAddBootManagerProfile = sb_efi.AddBootManagerProfile
+ sbefiAddPCRProfile = sb_efi.AddPCRProfile
sbefiAddSystemdStubProfile = sb_efi.AddSystemdStubProfile
sbAddSnapModelProfile = sb_tpm2.AddSnapModelProfile
- sbSealKeyToTPMMultiple = sb_tpm2.SealKeyToTPMMultiple
sbUpdateKeyPCRProtectionPolicyMultiple = sb_tpm2.UpdateKeyPCRProtectionPolicyMultiple
sbSealedKeyObjectRevokeOldPCRProtectionPolicies = (*sb_tpm2.SealedKeyObject).RevokeOldPCRProtectionPolicies
- sbNewKeyDataFromSealedKeyObjectFile = sb_tpm2.NewKeyDataFromSealedKeyObjectFile
+ sbNewFileKeyDataReader = sb.NewFileKeyDataReader
+ sbReadKeyData = sb.ReadKeyData
sbReadSealedKeyObjectFromFile = sb_tpm2.ReadSealedKeyObjectFromFile
+ sbNewTPMProtectedKey = sb_tpm2.NewTPMProtectedKey
+ sbNewKeyDataFromSealedKeyObjectFile = sb_tpm2.NewKeyDataFromSealedKeyObjectFile
randutilRandomKernelUUID = randutil.RandomKernelUUID
@@ -73,6 +76,8 @@ var (
sbTPMDictionaryAttackLockReset = (*sb_tpm2.Connection).DictionaryAttackLockReset
+ sbUpdateKeyDataPCRProtectionPolicy = sb_tpm2.UpdateKeyDataPCRProtectionPolicy
+
// check whether the interfaces match
_ (sb.SnapModel) = ModelForSealing(nil)
)
@@ -271,17 +276,72 @@ func activateVolOpts(allowRecoveryKey bool) *sb.ActivateVolumeOptions {
return &options
}
+func newAuthRequestor() (sb.AuthRequestor, error) {
+ return sb.NewSystemdAuthRequestor(
+ "Please enter passphrase for volume {{.VolumeName}} for device {{.SourceDevicePath}}",
+ "Please enter recovery key for volume {{.VolumeName}} for device {{.SourceDevicePath}}",
+ )
+}
+
+// TODO: consider moving this to secboot
+func readKeyFileImpl(keyfile string) (*sb.KeyData, *sb_tpm2.SealedKeyObject, error) {
+ f, err := os.Open(keyfile)
+ if err != nil {
+ return nil, nil, err
+ }
+ defer f.Close()
+
+ var rawPrefix = []byte("USK$")
+
+ buf := make([]byte, len(rawPrefix))
+ if _, err := io.ReadFull(f, buf); err != nil {
+ return nil, nil, err
+ }
+ if bytes.HasPrefix(buf, rawPrefix) {
+ sealedObject, err := sbReadSealedKeyObjectFromFile(keyfile)
+ if err != nil {
+ return nil, nil, fmt.Errorf("cannot read key object: %v", err)
+ }
+ keyData, err := sbNewKeyDataFromSealedKeyObjectFile(keyfile)
+ if err != nil {
+ return nil, nil, fmt.Errorf("cannot read key object as key data: %v", err)
+ }
+ return keyData, sealedObject, err
+
+ } else {
+ reader, err := sbNewFileKeyDataReader(keyfile)
+ if err != nil {
+ return nil, nil, fmt.Errorf("cannot open key data: %v", err)
+ }
+ keyData, err := sbReadKeyData(reader)
+ return keyData, nil, err
+ }
+}
+
+var readKeyFile = readKeyFileImpl
+
// unlockEncryptedPartitionWithSealedKey unseals the keyfile and opens an encrypted
// device. If activation with the sealed key fails, this function will attempt to
// activate it with the fallback recovery key instead.
func unlockEncryptedPartitionWithSealedKey(mapperName, sourceDevice, keyfile string, allowRecovery bool) (UnlockMethod, error) {
- keyData, err := sbNewKeyDataFromSealedKeyObjectFile(keyfile)
- if err != nil {
+ var keys []*sb.KeyData
+
+ keyData, _, err := readKeyFile(keyfile)
+ if os.IsNotExist(err) {
+ } else if err != nil {
return NotUnlocked, fmt.Errorf("cannot read key data: %v", err)
+ } else {
+ keys = append(keys, keyData)
}
options := activateVolOpts(allowRecovery)
+ options.Model = sb.SkipSnapModelCheck
// ignoring model checker as it doesn't work with tpm "legacy" platform key data
- _, err = sbActivateVolumeWithKeyData(mapperName, sourceDevice, keyData, options)
+ authRequestor, err := newAuthRequestor()
+ if err != nil {
+ return NotUnlocked, fmt.Errorf("cannot build an auth requestor: %v", err)
+ }
+
+ err = sbActivateVolumeWithKeyData(mapperName, sourceDevice, authRequestor, options, keys...)
if err == sb.ErrRecoveryKeyUsed {
logger.Noticef("successfully activated encrypted device %q using a fallback activation method", sourceDevice)
return UnlockedWithRecoveryKey, nil
@@ -364,54 +424,69 @@ func ProvisionForCVM(initramfsUbuntuSeedDir string) error {
// SealKeys seals the encryption keys according to the specified parameters. The
// TPM must have already been provisioned. If sealed key already exists at the
// PCR handle, SealKeys will fail and return an error.
-func SealKeys(keys []SealKeyRequest, params *SealKeysParams) error {
+func SealKeys(keys []SealKeyRequest, params *SealKeysParams) ([]byte, error) {
numModels := len(params.ModelParams)
if numModels < 1 {
- return fmt.Errorf("at least one set of model-specific parameters is required")
+ return nil, fmt.Errorf("at least one set of model-specific parameters is required")
}
tpm, err := sbConnectToDefaultTPM()
if err != nil {
- return fmt.Errorf("cannot connect to TPM: %v", err)
+ return nil, fmt.Errorf("cannot connect to TPM: %v", err)
}
defer tpm.Close()
if !isTPMEnabled(tpm) {
- return fmt.Errorf("TPM device is not enabled")
+ return nil, fmt.Errorf("TPM device is not enabled")
}
pcrProfile, err := buildPCRProtectionProfile(params.ModelParams)
if err != nil {
- return err
+ return nil, err
}
pcrHandle := params.PCRPolicyCounterHandle
logger.Noticef("sealing with PCR handle %#x", pcrHandle)
- // Seal the provided keys to the TPM
- creationParams := sb_tpm2.KeyCreationParams{
- PCRProfile: pcrProfile,
- PCRPolicyCounterHandle: tpm2.Handle(pcrHandle),
- AuthKey: params.TPMPolicyAuthKey,
- }
- sbKeys := make([]*sb_tpm2.SealKeyRequest, 0, len(keys))
- for i := range keys {
- sbKeys = append(sbKeys, &sb_tpm2.SealKeyRequest{
- Key: keys[i].Key,
- Path: keys[i].KeyFile,
- })
+ var primaryKey sb.PrimaryKey
+ if params.PrimaryKey != nil {
+ primaryKey = params.PrimaryKey
}
-
- authKey, err := sbSealKeyToTPMMultiple(tpm, sbKeys, &creationParams)
- if err != nil {
- logger.Debugf("seal key error: %v", err)
- return err
+ for _, key := range keys {
+ creationParams := &sb_tpm2.ProtectKeyParams{
+ PCRProfile: pcrProfile,
+ Role: key.Role,
+ PCRPolicyCounterHandle: tpm2.Handle(pcrHandle),
+ PrimaryKey: primaryKey,
+ }
+ protectedKey, primaryKeyOut, unlockKey, err := sbNewTPMProtectedKey(tpm, creationParams)
+ if primaryKey == nil {
+ primaryKey = primaryKeyOut
+ }
+ if err != nil {
+ return nil, err
+ }
+ token := key.KeyFile == ""
+ tokenWriter, err := key.Resetter.AddKey(key.SlotName, unlockKey, token)
+ if err != nil {
+ return nil, err
+ }
+ var keyDataWriter sb.KeyDataWriter
+ if token {
+ keyDataWriter = tokenWriter
+ } else {
+ keyDataWriter = sb.NewFileKeyDataWriter(key.KeyFile)
+ }
+ if err := protectedKey.WriteAtomic(keyDataWriter); err != nil {
+ return nil, err
+ }
}
- if params.TPMPolicyAuthKeyFile != "" {
- if err := osutil.AtomicWriteFile(params.TPMPolicyAuthKeyFile, authKey, 0600, 0); err != nil {
- return fmt.Errorf("cannot write the policy auth key file: %v", err)
+ if primaryKey != nil && params.TPMPolicyAuthKeyFile != "" {
+ if err := osutil.AtomicWriteFile(params.TPMPolicyAuthKeyFile, primaryKey, 0600, 0); err != nil {
+ return nil, fmt.Errorf("cannot write the policy auth key file: %v", err)
}
}
- return nil
+
+ return primaryKey, nil
}
// ResealKeys updates the PCR protection policy for the sealed encryption keys
@@ -437,37 +512,59 @@ func ResealKeys(params *ResealKeysParams) error {
pcrProfile, err := buildPCRProtectionProfile(params.ModelParams)
if err != nil {
- return err
+ return fmt.Errorf("cannot build new PCR protection profile: %w", err)
}
authKey, err := os.ReadFile(params.TPMPolicyAuthKeyFile)
if err != nil {
- return fmt.Errorf("cannot read the policy auth key file: %v", err)
+ return fmt.Errorf("cannot read the policy auth key file %s: %w", params.TPMPolicyAuthKeyFile, err)
}
+ hasOldObject := false
+ hasNewData := false
+
+ keyDatas := make([]*sb.KeyData, 0, numSealedKeyObjects)
sealedKeyObjects := make([]*sb_tpm2.SealedKeyObject, 0, numSealedKeyObjects)
for _, keyfile := range params.KeyFiles {
- sealedKeyObject, err := sbReadSealedKeyObjectFromFile(keyfile)
+ keyData, keyObject, err := readKeyFile(keyfile)
if err != nil {
- return err
+ return fmt.Errorf("cannot read key file %s: %w", keyfile, err)
+ }
+ keyDatas = append(keyDatas, keyData)
+ sealedKeyObjects = append(sealedKeyObjects, keyObject)
+ if keyObject == nil {
+ hasNewData = true
+ } else {
+ hasOldObject = true
}
- sealedKeyObjects = append(sealedKeyObjects, sealedKeyObject)
}
- if err := sbUpdateKeyPCRProtectionPolicyMultiple(tpm, sealedKeyObjects, authKey, pcrProfile); err != nil {
- return err
+ if hasOldObject && hasNewData {
+ return fmt.Errorf("key files are different formats")
}
- // write key files
- for i, sko := range sealedKeyObjects {
- w := sb_tpm2.NewFileSealedKeyObjectWriter(params.KeyFiles[i])
- if err := sko.WriteAtomic(w); err != nil {
- return fmt.Errorf("cannot write key data file: %v", err)
+ if hasOldObject {
+ if err := sbUpdateKeyPCRProtectionPolicyMultiple(tpm, sealedKeyObjects, authKey, pcrProfile); err != nil {
+ return fmt.Errorf("cannot update legacy PCR protection policy: %w", err)
}
+
+ // write key files
+ for i, sko := range sealedKeyObjects {
+ w := sb_tpm2.NewFileSealedKeyObjectWriter(params.KeyFiles[i])
+ if err := sko.WriteAtomic(w); err != nil {
+ return fmt.Errorf("cannot write key data file %s: %w", params.KeyFiles[i], err)
+ }
+ }
+
+ // revoke old policies via the primary key object
+ if err := sbSealedKeyObjectRevokeOldPCRProtectionPolicies(sealedKeyObjects[0], tpm, authKey); err != nil {
+ return fmt.Errorf("cannot revoke old PCR protection policies: %w", err)
+ }
+ } else {
+ return fmt.Errorf("Old code, new stuff")
}
- // revoke old policies via the primary key object
- return sbSealedKeyObjectRevokeOldPCRProtectionPolicies(sealedKeyObjects[0], tpm, authKey)
+ return nil
}
func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb_tpm2.PCRProtectionProfile, error) {
@@ -482,27 +579,14 @@ func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb_tpm2.PCRP
return nil, fmt.Errorf("cannot build EFI image load sequences: %v", err)
}
- // Add EFI secure boot policy profile
- policyParams := sb_efi.SecureBootPolicyProfileParams{
- PCRAlgorithm: tpm2.HashAlgorithmSHA256,
- LoadSequences: loadSequences,
- // TODO:UC20: set SignatureDbUpdateKeystore to support applying forbidden
- // signature updates to exclude signing keys (after rotating them).
- // This also requires integration of sbkeysync, and some work to
- // ensure that the PCR profile is updated before/after sbkeysync executes.
- }
-
- if err := sbefiAddSecureBootPolicyProfile(modelProfile, &policyParams); err != nil {
- return nil, fmt.Errorf("cannot add EFI secure boot policy profile: %v", err)
- }
-
- // Add EFI boot manager profile
- bootManagerParams := sb_efi.BootManagerProfileParams{
- PCRAlgorithm: tpm2.HashAlgorithmSHA256,
- LoadSequences: loadSequences,
- }
- if err := sbefiAddBootManagerProfile(modelProfile, &bootManagerParams); err != nil {
- return nil, fmt.Errorf("cannot add EFI boot manager profile: %v", err)
+ if err := sbefiAddPCRProfile(
+ tpm2.HashAlgorithmSHA256,
+ modelProfile.RootBranch(),
+ loadSequences,
+ sb_efi.WithSecureBootPolicyProfile(),
+ sb_efi.WithBootManagerCodeProfile(),
+ ); err != nil {
+ return nil, fmt.Errorf("cannot add EFI secure boot and boot manager policy profiles: %v", err)
}
// Add systemd EFI stub profile
@@ -512,7 +596,7 @@ func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb_tpm2.PCRP
PCRIndex: initramfsPCR,
KernelCmdlines: mp.KernelCmdlines,
}
- if err := sbefiAddSystemdStubProfile(modelProfile, &systemdStubParams); err != nil {
+ if err := sbefiAddSystemdStubProfile(modelProfile.RootBranch(), &systemdStubParams); err != nil {
return nil, fmt.Errorf("cannot add systemd EFI stub profile: %v", err)
}
}
@@ -524,7 +608,7 @@ func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb_tpm2.PCRP
PCRIndex: initramfsPCR,
Models: []sb.SnapModel{mp.Model},
}
- if err := sbAddSnapModelProfile(modelProfile, &snapModelParams); err != nil {
+ if err := sbAddSnapModelProfile(modelProfile.RootBranch(), &snapModelParams); err != nil {
return nil, fmt.Errorf("cannot add snap model profile: %v", err)
}
}
@@ -532,12 +616,7 @@ func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb_tpm2.PCRP
modelPCRProfiles = append(modelPCRProfiles, modelProfile)
}
- var pcrProfile *sb_tpm2.PCRProtectionProfile
- if numModels > 1 {
- pcrProfile = sb_tpm2.NewPCRProtectionProfile().AddProfileOR(modelPCRProfiles...)
- } else {
- pcrProfile = modelPCRProfiles[0]
- }
+ pcrProfile := sb_tpm2.NewPCRProtectionProfile().AddProfileOR(modelPCRProfiles...)
logger.Debugf("PCR protection profile:\n%s", pcrProfile.String())
@@ -581,7 +660,7 @@ func tpmProvision(tpm *sb_tpm2.Connection, mode TPMProvisionMode, lockoutAuthFil
}
// buildLoadSequences builds EFI load image event trees from this package LoadChains
-func buildLoadSequences(chains []*LoadChain) (loadseqs []*sb_efi.ImageLoadEvent, err error) {
+func buildLoadSequences(chains []*LoadChain) (loadseqs *sb_efi.ImageLoadSequences, err error) {
// this will build load event trees for the current
// device configuration, e.g. something like:
//
@@ -591,23 +670,25 @@ func buildLoadSequences(chains []*LoadChain) (loadseqs []*sb_efi.ImageLoadEvent,
// |-> normal grub -> run kernel good
// |-> run kernel try
+ loadseqs = sb_efi.NewImageLoadSequences()
+
for _, chain := range chains {
// root of load events has source Firmware
- loadseq, err := chain.loadEvent(sb_efi.Firmware)
+ loadseq, err := chain.loadEvent()
if err != nil {
return nil, err
}
- loadseqs = append(loadseqs, loadseq)
+ loadseqs.Append(loadseq)
}
return loadseqs, nil
}
// loadEvent builds the corresponding load event and its tree
-func (lc *LoadChain) loadEvent(source sb_efi.ImageLoadEventSource) (*sb_efi.ImageLoadEvent, error) {
- var next []*sb_efi.ImageLoadEvent
+func (lc *LoadChain) loadEvent() (sb_efi.ImageLoadActivity, error) {
+ var next []sb_efi.ImageLoadActivity
for _, nextChain := range lc.Next {
// everything that is not the root has source shim
- ev, err := nextChain.loadEvent(sb_efi.Shim)
+ ev, err := nextChain.loadEvent()
if err != nil {
return nil, err
}
@@ -617,11 +698,7 @@ func (lc *LoadChain) loadEvent(source sb_efi.ImageLoadEventSource) (*sb_efi.Imag
if err != nil {
return nil, err
}
- return &sb_efi.ImageLoadEvent{
- Source: source,
- Image: image,
- Next: next,
- }, nil
+ return sb_efi.NewImageLoadActivity(image).Loads(next...), nil
}
func efiImageFromBootFile(b *bootloader.BootFile) (sb_efi.Image, error) {
@@ -636,25 +713,30 @@ func efiImageFromBootFile(b *bootloader.BootFile) (sb_efi.Image, error) {
if err != nil {
return nil, err
}
- return sb_efi.SnapFileImage{
- Container: snapf,
- FileName: b.Path,
- }, nil
+ return sb_efi.NewSnapFileImage(
+ snapf,
+ b.Path,
+ ), nil
}
// PCRHandleOfSealedKey retunrs the PCR handle which was used when sealing a
// given key object.
func PCRHandleOfSealedKey(p string) (uint32, error) {
- r, err := sb_tpm2.NewFileSealedKeyObjectReader(p)
+ keyData, keyObject, err := readKeyFile(p)
if err != nil {
- return 0, fmt.Errorf("cannot open key file: %v", err)
+ return 0, fmt.Errorf("cannot read key file %s: %w", p, err)
}
- sko, err := sb_tpm2.ReadSealedKeyObject(r)
- if err != nil {
- return 0, fmt.Errorf("cannot read sealed key file: %v", err)
+ if keyObject != nil {
+ handle := uint32(keyObject.PCRPolicyCounterHandle())
+ return handle, nil
+ } else {
+ sealedKeyData, err := sb_tpm2.NewSealedKeyData(keyData)
+ if err != nil {
+ return 0, fmt.Errorf("cannot read key data in keyfile %s: %w", p, err)
+ }
+ handle := uint32(sealedKeyData.PCRPolicyCounterHandle())
+ return handle, nil
}
- handle := uint32(sko.PCRPolicyCounterHandle())
- return handle, nil
}
func tpmReleaseResourcesImpl(tpm *sb_tpm2.Connection, handle tpm2.Handle) error {
diff --git a/tests/lib/fakestore/store/store.go b/tests/lib/fakestore/store/store.go
index a2f6ec09a9d..7951a039223 100644
--- a/tests/lib/fakestore/store/store.go
+++ b/tests/lib/fakestore/store/store.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2016-2020 Canonical Ltd
+ * Copyright (C) 2016-2020, 2024 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
@@ -20,6 +20,7 @@
package store
import (
+ "bufio"
"context"
"encoding/base64"
"encoding/json"
@@ -68,6 +69,8 @@ type Store struct {
fallback *store.Store
srv *http.Server
+
+ channelRepository *ChannelRepository
}
// NewStore creates a new store server serving snaps from the given top directory and assertions from topDir/asserts. If assertFallback is true missing assertions are looked up in the main online store.
@@ -90,6 +93,9 @@ func NewStore(topDir, addr string, assertFallback bool) *Store {
Addr: addr,
Handler: mux,
},
+ channelRepository: &ChannelRepository{
+ rootDir: filepath.Join(topDir, "channels"),
+ },
}
mux.HandleFunc("/", rootEndpoint)
@@ -97,6 +103,10 @@ func NewStore(topDir, addr string, assertFallback bool) *Store {
mux.HandleFunc("/api/v1/snaps/details/", store.detailsEndpoint)
mux.HandleFunc("/api/v1/snaps/metadata", store.bulkEndpoint)
mux.Handle("/download/", http.StripPrefix("/download/", http.FileServer(http.Dir(topDir))))
+
+ mux.HandleFunc("/api/v1/snaps/auth/nonces", store.nonceEndpoint)
+ mux.HandleFunc("/api/v1/snaps/auth/sessions", store.sessionEndpoint)
+
// v2
mux.HandleFunc("/v2/assertions/", store.assertionsEndpoint)
mux.HandleFunc("/v2/snaps/refresh", store.snapActionEndpoint)
@@ -111,6 +121,14 @@ func (s *Store) URL() string {
return s.url
}
+func (s *Store) RealURL(req *http.Request) string {
+ if req.Host == "" {
+ return s.url
+ } else {
+ return fmt.Sprintf("http://%s", req.Host)
+ }
+}
+
func (s *Store) SnapsDir() string {
return s.blobDir
}
@@ -177,11 +195,12 @@ type essentialInfo struct {
Confinement string
Type string
Base string
+ /*Channels []string*/
}
var errInfo = errors.New("cannot get info")
-func snapEssentialInfo(w http.ResponseWriter, fn, snapID string, bs asserts.Backstore) (*essentialInfo, error) {
+func snapEssentialInfo(w http.ResponseWriter, fn, snapID string, bs asserts.Backstore, cs *ChannelRepository) (*essentialInfo, error) {
f, err := snapfile.Open(fn)
if err != nil {
http.Error(w, fmt.Sprintf("cannot read: %v: %v", fn, err), 400)
@@ -348,7 +367,7 @@ func (s *Store) detailsEndpoint(w http.ResponseWriter, req *http.Request) {
http.Error(w, fmt.Sprintf("internal error collecting assertions: %v", err), 500)
return
}
- snaps, err := s.collectSnaps()
+ snaps, err := s.collectSnaps(s.channelRepository)
if err != nil {
http.Error(w, fmt.Sprintf("internal error collecting snaps: %v", err), 500)
return
@@ -360,7 +379,7 @@ func (s *Store) detailsEndpoint(w http.ResponseWriter, req *http.Request) {
return
}
- essInfo, err := snapEssentialInfo(w, fn, "", bs)
+ essInfo, err := snapEssentialInfo(w, fn, "", bs, s.channelRepository)
if essInfo == nil {
if err != errInfo {
panic(err)
@@ -374,8 +393,8 @@ func (s *Store) detailsEndpoint(w http.ResponseWriter, req *http.Request) {
PackageName: essInfo.Name,
Developer: essInfo.DevelName,
DeveloperID: essInfo.DeveloperID,
- AnonDownloadURL: fmt.Sprintf("%s/download/%s", s.URL(), filepath.Base(fn)),
- DownloadURL: fmt.Sprintf("%s/download/%s", s.URL(), filepath.Base(fn)),
+ AnonDownloadURL: fmt.Sprintf("%s/download/%s", s.RealURL(req), filepath.Base(fn)),
+ DownloadURL: fmt.Sprintf("%s/download/%s", s.RealURL(req), filepath.Base(fn)),
Version: essInfo.Version,
Revision: essInfo.Revision,
DownloadDigest: hexify(essInfo.Digest),
@@ -394,7 +413,7 @@ func (s *Store) detailsEndpoint(w http.ResponseWriter, req *http.Request) {
w.Write(out)
}
-func (s *Store) collectSnaps() (map[string]string, error) {
+func (s *Store) collectSnaps(cs *ChannelRepository) (map[string]string, error) {
snapFns, err := filepath.Glob(filepath.Join(s.blobDir, "*.snap"))
if err != nil {
return nil, err
@@ -415,6 +434,19 @@ func (s *Store) collectSnaps() (map[string]string, error) {
return nil, err
}
snaps[info.SnapName()] = fn
+
+ digest, _, err := asserts.SnapFileSHA3_384(fn)
+ if err != nil {
+ return nil, err
+ }
+ channels, err := cs.findSnapChannels(digest)
+ if err != nil {
+ return nil, err
+ }
+ for _, channel := range channels {
+ snaps[fmt.Sprintf("%s|%s", info.SnapName(), channel)] = fn
+ }
+
logger.Debugf("found snap %q at %v", info.SnapName(), fn)
}
@@ -484,7 +516,7 @@ func (s *Store) bulkEndpoint(w http.ResponseWriter, req *http.Request) {
return
}
- snaps, err := s.collectSnaps()
+ snaps, err := s.collectSnaps(s.channelRepository)
if err != nil {
http.Error(w, fmt.Sprintf("internal error collecting snaps: %v", err), 500)
return
@@ -499,7 +531,7 @@ func (s *Store) bulkEndpoint(w http.ResponseWriter, req *http.Request) {
}
if fn, ok := snaps[name]; ok {
- essInfo, err := snapEssentialInfo(w, fn, pkg.SnapID, bs)
+ essInfo, err := snapEssentialInfo(w, fn, pkg.SnapID, bs, s.channelRepository)
if essInfo == nil {
if err != errInfo {
panic(err)
@@ -513,8 +545,8 @@ func (s *Store) bulkEndpoint(w http.ResponseWriter, req *http.Request) {
PackageName: essInfo.Name,
Developer: essInfo.DevelName,
DeveloperID: essInfo.DeveloperID,
- DownloadURL: fmt.Sprintf("%s/download/%s", s.URL(), filepath.Base(fn)),
- AnonDownloadURL: fmt.Sprintf("%s/download/%s", s.URL(), filepath.Base(fn)),
+ DownloadURL: fmt.Sprintf("%s/download/%s", s.RealURL(req), filepath.Base(fn)),
+ AnonDownloadURL: fmt.Sprintf("%s/download/%s", s.RealURL(req), filepath.Base(fn)),
Version: essInfo.Version,
Revision: essInfo.Revision,
DownloadDigest: hexify(essInfo.Digest),
@@ -575,8 +607,9 @@ func (s *Store) collectAssertions() (asserts.Backstore, error) {
}
type currentSnap struct {
- SnapID string `json:"snap-id"`
- InstanceKey string `json:"instance-key"`
+ SnapID string `json:"snap-id"`
+ InstanceKey string `json:"instance-key"`
+ TrackingChannel string `json:"tracking-channel"`
}
type snapAction struct {
@@ -585,6 +618,7 @@ type snapAction struct {
SnapID string `json:"snap-id"`
Name string `json:"name"`
Revision int `json:"revision,omitempty"`
+ Channel string `json:"channel,omitempty"`
}
type snapActionRequest struct {
@@ -653,7 +687,7 @@ func (s *Store) snapActionEndpoint(w http.ResponseWriter, req *http.Request) {
return
}
- snaps, err := s.collectSnaps()
+ snaps, err := s.collectSnaps(s.channelRepository)
if err != nil {
http.Error(w, fmt.Sprintf("internal error collecting snaps: %v", err), 500)
return
@@ -667,6 +701,7 @@ func (s *Store) snapActionEndpoint(w http.ResponseWriter, req *http.Request) {
Action: "refresh",
SnapID: s.SnapID,
InstanceKey: s.InstanceKey,
+ Channel: s.TrackingChannel,
}
}
}
@@ -684,8 +719,17 @@ func (s *Store) snapActionEndpoint(w http.ResponseWriter, req *http.Request) {
return
}
- if fn, ok := snaps[name]; ok {
- essInfo, err := snapEssentialInfo(w, fn, snapID, bs)
+ var snapPath string
+ var foundSnap bool
+ if a.Channel != "" {
+ snapPath, foundSnap = snaps[fmt.Sprintf("%s|%s", name, a.Channel)]
+ }
+ if !foundSnap {
+ snapPath, foundSnap = snaps[name]
+ }
+
+ if foundSnap {
+ essInfo, err := snapEssentialInfo(w, snapPath, snapID, bs, s.channelRepository)
if essInfo == nil {
if err != errInfo {
panic(err)
@@ -712,7 +756,7 @@ func (s *Store) snapActionEndpoint(w http.ResponseWriter, req *http.Request) {
logger.Debugf("requested snap %q revision %d", essInfo.Name, a.Revision)
res.Snap.Publisher.ID = essInfo.DeveloperID
res.Snap.Publisher.Username = essInfo.DevelName
- res.Snap.Download.URL = fmt.Sprintf("%s/download/%s", s.URL(), filepath.Base(fn))
+ res.Snap.Download.URL = fmt.Sprintf("%s/download/%s", s.RealURL(req), filepath.Base(snapPath))
res.Snap.Download.Sha3_384 = hexify(essInfo.Digest)
res.Snap.Download.Size = essInfo.Size
replyData.Results = append(replyData.Results, res)
@@ -869,3 +913,40 @@ func findSnapRevision(snapDigest string, bs asserts.Backstore) (*asserts.SnapRev
return snapRev, devAcct, nil
}
+
+func (s *Store) nonceEndpoint(w http.ResponseWriter, req *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(200)
+ w.Write([]byte(`{"nonce": "blah"}`))
+ return
+}
+
+func (s *Store) sessionEndpoint(w http.ResponseWriter, req *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(200)
+ w.Write([]byte(`{"macaroon": "blahblah"}`))
+ return
+}
+
+type ChannelRepository struct {
+ rootDir string
+}
+
+func (cr *ChannelRepository) findSnapChannels(snapDigest string) ([]string, error) {
+ dataPath := filepath.Join(cr.rootDir, snapDigest)
+ fd, err := os.Open(dataPath)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil, nil
+ } else {
+ return nil, err
+ }
+ } else {
+ sc := bufio.NewScanner(fd)
+ var lines []string
+ for sc.Scan() {
+ lines = append(lines, sc.Text())
+ }
+ return lines, nil
+ }
+}
diff --git a/tests/lib/fakestore/store/store_test.go b/tests/lib/fakestore/store/store_test.go
index ba780f07d6b..da4f04bd0f8 100644
--- a/tests/lib/fakestore/store/store_test.go
+++ b/tests/lib/fakestore/store/store_test.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2014-2018 Canonical Ltd
+ * Copyright (C) 2014-2018, 2024 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
@@ -430,7 +430,7 @@ func (s *storeTestSuite) TestMakeTestSnap(c *C) {
func (s *storeTestSuite) TestCollectSnaps(c *C) {
s.makeTestSnap(c, "name: foo\nversion: 1")
- snaps, err := s.store.collectSnaps()
+ snaps, err := s.store.collectSnaps(s.store.channelRepository)
c.Assert(err, IsNil)
c.Assert(snaps, DeepEquals, map[string]string{
"foo": filepath.Join(s.store.blobDir, "foo_1_all.snap"),
diff --git a/tests/lib/nested.sh b/tests/lib/nested.sh
index 18f02e56771..1ef535f09ca 100755
--- a/tests/lib/nested.sh
+++ b/tests/lib/nested.sh
@@ -415,10 +415,23 @@ nested_secboot_remove_signature() {
}
nested_secboot_sign_file() {
- local FILE="$1"
- local KEY="$2"
- local CERT="$3"
- nested_secboot_remove_signature "$FILE"
+ args=()
+ while [ "${#}" -gt 0 ]; do
+ case "${1}" in
+ --keep-signatures)
+ keep_signatures=1
+ ;;
+ *)
+ args+=("${1}")
+ esac
+ shift
+ done
+ local FILE="${args[0]}"
+ local KEY="${args[1]}"
+ local CERT="${args[2]}"
+ if [ "${keep_signatures+set}" != set ]; then
+ nested_secboot_remove_signature "$FILE"
+ fi
sbsign --key "$KEY" --cert "$CERT" --output "$FILE" "$FILE"
}
@@ -1220,7 +1233,7 @@ nested_start_core_vm_unit() {
fi
# In this case the kernel.efi is unsigned and signed with snaleoil certs
- if [ "$NESTED_FORCE_MS_KEYS" != "true" ] && [ "$NESTED_BUILD_SNAPD_FROM_CURRENT" = "true" ]; then
+ if [ "$NESTED_FORCE_MS_KEYS" != "true" ] && { [ "$NESTED_BUILD_SNAPD_FROM_CURRENT" = "true" ] || [ "${NESTED_FORCE_SNAKEOIL_KEYS:-false}" = "true" ] ; }; then
OVMF_VARS="snakeoil"
else
OVMF_VARS="ms"
diff --git a/tests/lib/uc20-create-partitions/main.go b/tests/lib/uc20-create-partitions/main.go
index a386134eeaa..38810a81039 100644
--- a/tests/lib/uc20-create-partitions/main.go
+++ b/tests/lib/uc20-create-partitions/main.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2019-2020 Canonical Ltd
+ * Copyright (C) 2019-2020, 2024 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
@@ -97,17 +97,32 @@ func main() {
}
if args.Encrypt {
- if installSideData == nil || len(installSideData.KeyForRole) == 0 {
+ if installSideData == nil || len(installSideData.ResetterForRole) == 0 {
panic("expected encryption keys")
}
- dataKey := installSideData.KeyForRole[gadget.SystemData]
- if dataKey == nil {
+ dataKeyResetter := installSideData.ResetterForRole[gadget.SystemData]
+ if dataKeyResetter == nil {
panic("ubuntu-data encryption key is unset")
}
- saveKey := installSideData.KeyForRole[gadget.SystemSave]
- if saveKey == nil {
+ dataKey, err := keys.NewEncryptionKey()
+ if err != nil {
+ panic("cannot create data key")
+ }
+ const token = false
+ if _, err := dataKeyResetter.AddKey("", secboot.DiskUnlockKey(dataKey), token); err != nil {
+ panic("cannot reset data key")
+ }
+ saveKeyResetter := installSideData.ResetterForRole[gadget.SystemSave]
+ if saveKeyResetter == nil {
panic("ubuntu-save encryption key is unset")
}
+ saveKey, err := keys.NewEncryptionKey()
+ if err != nil {
+ panic("cannot create save key")
+ }
+ if _, err := saveKeyResetter.AddKey("", secboot.DiskUnlockKey(saveKey), token); err != nil {
+ panic("cannot reset save key")
+ }
toWrite := map[string][]byte{
"unsealed-key": dataKey[:],
"save-key": saveKey[:],
@@ -117,5 +132,12 @@ func main() {
panic(err)
}
}
+
+ if err := dataKeyResetter.RemoveInstallationKey(); err != nil {
+ panic(err)
+ }
+ if err := saveKeyResetter.RemoveInstallationKey(); err != nil {
+ panic(err)
+ }
}
}
diff --git a/tests/nested/core/core20-basic/task.yaml b/tests/nested/core/core20-basic/task.yaml
index 1fa63daa738..562ea17707e 100644
--- a/tests/nested/core/core20-basic/task.yaml
+++ b/tests/nested/core/core20-basic/task.yaml
@@ -43,8 +43,12 @@ execute: |
fi
# single key for ubuntu-data and ubuntu-save
- test "$(remote.exec "sudo cryptsetup luksDump /dev/vda4 |grep Key:" | wc -l)" = "1"
- test "$(remote.exec "sudo cryptsetup luksDump /dev/vda5 |grep Key:" | wc -l)" = "1"
+ # FIXME: for now save partition has 2 keys, one which is sealed in
+ # tpm, one that is data partition plain.
+ test "$(remote.exec "sudo cryptsetup luksDump /dev/vda4 |grep Key:" | wc -l)" = "2"
+ # FIXME: for now data partition has 2 keys, default, and
+ # default-fallback. Both are sealed in tpm.
+ test "$(remote.exec "sudo cryptsetup luksDump /dev/vda5 |grep Key:" | wc -l)" = "2"
echo "Ensure 'snap debug show-keys' works as root"
remote.exec "sudo snap recovery --show-keys" > show-keys.out
@@ -59,8 +63,16 @@ execute: |
remote.exec "test -f /var/lib/snapd/device/fde/recovery.key"
remote.exec "test ! -f /var/lib/snapd/device/fde/reinstall.key"
# and each partition has 2 keys now
- test "$(remote.exec "sudo cryptsetup luksDump /dev/vda4 |grep Key:" | wc -l)" = "2"
- test "$(remote.exec "sudo cryptsetup luksDump /dev/vda5 |grep Key:" | wc -l)" = "2"
+ echo "luksDump for /dev/vda4"
+ remote.exec "sudo cryptsetup luksDump /dev/vda4"
+ echo "luksDump for /dev/vda5"
+ remote.exec "sudo cryptsetup luksDump /dev/vda5"
+ # FIXME: for now save partition has 3 keys, one which is sealed in
+ # tpm, one that is data partition plain. Then one recovery key.
+ test "$(remote.exec "sudo cryptsetup luksDump /dev/vda4 |grep Key:" | wc -l)" = "3"
+ # FIXME: for now data partition has 3 keys, default, and
+ # default-fallback. Both are sealed in tpm. Then one that is recovery.
+ test "$(remote.exec "sudo cryptsetup luksDump /dev/vda5 |grep Key:" | wc -l)" = "3"
echo "But not as user (normal file permissions prevent this)"
if remote.exec "snap recovery --show-keys"; then
@@ -76,8 +88,8 @@ execute: |
remote.exec "test ! -f /var/lib/snapd/device/fde/recovery.key"
remote.exec "test ! -f /var/lib/snapd/device/fde/reinstall.key"
# back to having just one key
- test "$(remote.exec "sudo cryptsetup luksDump /dev/vda4 |grep Key:" | wc -l)" = "1"
- test "$(remote.exec "sudo cryptsetup luksDump /dev/vda5 |grep Key:" | wc -l)" = "1"
+ test "$(remote.exec "sudo cryptsetup luksDump /dev/vda4 |grep Key:" | wc -l)" = "2"
+ test "$(remote.exec "sudo cryptsetup luksDump /dev/vda5 |grep Key:" | wc -l)" = "2"
echo "Check that the serial backed up to save is as expected"
remote.exec 'cat /var/lib/snapd/save/device/asserts-v0/serial/'"$(tests.nested get model-authority)"'/pc/*/active' >serial.saved
diff --git a/tests/nested/manual/core-factory-reset-new-secboot/task.yaml b/tests/nested/manual/core-factory-reset-new-secboot/task.yaml
new file mode 100644
index 00000000000..70b8caf2c12
--- /dev/null
+++ b/tests/nested/manual/core-factory-reset-new-secboot/task.yaml
@@ -0,0 +1,79 @@
+summary: Verify that core seeded with old secboot and updated can be reset
+
+details: |
+ This verifies that updating to new snapd and factory-reset to a
+ snapd prior to secboot changes works. This test also forces
+ resealing with new version of snapd in order to make sure that
+ keys are kept in format readable for the factory reset.
+
+systems: [ubuntu-2*]
+
+environment:
+ # 2.63 release
+ OLD_SNAPD_REVISION: "21759"
+ NESTED_BUILD_SNAPD_FROM_CURRENT: false
+ NESTED_FORCE_SNAKEOIL_KEYS: true
+
+ # hook
+ ENCRYPTION/hook: "hook"
+ NESTED_ENABLE_TPM/hook: false
+ NESTED_ENABLE_SECURE_BOOT/hook: false
+
+ # tpm
+ ENCRYPTION/tpm: "tpm"
+ NESTED_ENABLE_TPM/tpm: true
+ NESTED_ENABLE_SECURE_BOOT/tpm: true
+
+prepare: |
+ if [ "${ENCRYPTION}" = hook ]; then
+ mkdir -p ./extra-initrd/usr/bin/
+ go build -o ./extra-initrd/usr/bin/fde-reveal-key "$TESTSLIB"/fde-setup-hook/fde-setup.go
+ mkdir -p ./extra-kernel-snap/meta/hooks
+ go build -o ./extra-kernel-snap/meta/hooks/fde-setup "$TESTSLIB"/fde-setup-hook/fde-setup.go
+ fi
+
+ snap download snapd --revision="${OLD_SNAPD_REVISION}" --target-directory="$(tests.nested get extra-snaps-path)"
+
+ "$TESTSTOOLS"/snaps-state repack_snapd_deb_into_snap snapd
+
+ # Because of NESTED_BUILD_SNAPD_FROM_CURRENT=false, we need to
+ # force building the other snaps.
+ tests.nested prepare-essential-snaps
+
+ unsquashfs -d pc "$(tests.nested get extra-snaps-path)"/pc.snap
+ echo "forceresealing" >>pc/cmdline.extra
+ snap pack pc/ --filename=pc-new.snap
+ rm -rf pc/
+
+ tests.nested build-image core
+ tests.nested create-vm core
+
+execute: |
+ # Make sure we have encryption
+ remote.exec "ls /dev/mapper/ubuntu-data*"
+ remote.exec "ls /dev/mapper/ubuntu-save*"
+
+ remote.exec "snap version" | MATCH "^snapd *2.63$"
+
+ remote.push snapd-from-deb.snap
+ remote.exec "sudo snap install --dangerous snapd-from-deb.snap"
+
+ boot_id="$(tests.nested boot-id)"
+ remote.exec "sudo snap reboot" || true
+ remote.wait-for reboot "${boot_id}"
+
+ remote.exec "snap version" | NOMATCH "^snapd *2.63$"
+
+ remote.push pc-new.snap
+ boot_id="$(tests.nested boot-id)"
+ remote.exec "sudo snap install --dangerous pc-new.snap" || true
+ remote.wait-for reboot "${boot_id}"
+
+ remote.exec "cat /proc/cmdline" | MATCH "forceresealing"
+
+ boot_id="$(tests.nested boot-id)"
+ remote.exec "sudo snap reboot --factory-reset" || true
+ remote.wait-for reboot "${boot_id}"
+
+ remote.exec "sudo snap wait system seed.loaded"
+ remote.exec "snap version" | MATCH "^snapd *2.63$"
diff --git a/tests/nested/manual/muinstaller-core/task.yaml b/tests/nested/manual/muinstaller-core/task.yaml
index 98160596323..a29be3a933a 100644
--- a/tests/nested/manual/muinstaller-core/task.yaml
+++ b/tests/nested/manual/muinstaller-core/task.yaml
@@ -82,16 +82,16 @@ execute: |
fi
mv "${NESTED_ASSETS_DIR}"/pc-kernel_*.snap pc-kernel.snap
+ "${TESTSTOOLS}/snaps-state" repack_snapd_deb_into_snap snapd
+
# prepare a core seed
- # TODO:
- # - repacked snapd snap
- # (should be as simple as adding "--snap=./local-snapd.snap ...")
SEED_DIR="core-seed"
wget -q https://raw.githubusercontent.com/snapcore/models/master/ubuntu-core-"$version"-amd64-dangerous.model -O my.model
snap prepare-image \
--channel=edge \
--snap ./pc-kernel.snap \
--snap ./pc.snap \
+ --snap ./snapd-from-deb.snap \
my.model \
./"$SEED_DIR"
@@ -227,13 +227,14 @@ execute: |
remote.exec "sudo snap recovery" | MATCH "${LABEL}\s+canonical\*\*\s+ubuntu-core-$version-amd64-dangerous\s+current"
# check for unasserted snaps
- for sn in pc pc-kernel; do
+ for sn in snapd pc pc-kernel; do
sn_version=$(remote.exec "snap list ${sn}" | awk 'NR != 1 { print $2 }')
remote.exec "test -f /run/mnt/ubuntu-seed/systems/${LABEL}/snaps/${sn}_${sn_version}.snap"
done
# check for asserted snaps
- for sn in snapd core"$version"; do
+ #shellcheck disable=SC2043
+ for sn in core"$version"; do
rev=$(remote.exec "snap list ${sn}" | awk 'NR != 1 { print $3 }')
remote.exec "test -f /run/mnt/ubuntu-seed/snaps/${sn}_${rev}.snap"
done
diff --git a/tests/nested/manual/remodel-uc20-to-uc22-fakestore/prepare-device b/tests/nested/manual/remodel-uc20-to-uc22-fakestore/prepare-device
new file mode 100755
index 00000000000..357c0850fad
--- /dev/null
+++ b/tests/nested/manual/remodel-uc20-to-uc22-fakestore/prepare-device
@@ -0,0 +1,3 @@
+#!/bin/sh
+# 10.0.2.2 is the host from a nested VM
+snapctl set device-service.url=http://10.0.2.2:11029
diff --git a/tests/nested/manual/remodel-uc20-to-uc22-fakestore/repack-kernel.sh b/tests/nested/manual/remodel-uc20-to-uc22-fakestore/repack-kernel.sh
new file mode 100644
index 00000000000..d20cae86a28
--- /dev/null
+++ b/tests/nested/manual/remodel-uc20-to-uc22-fakestore/repack-kernel.sh
@@ -0,0 +1,53 @@
+#!/bin/bash
+
+set -eu
+
+version=$1
+branch=$2
+
+tmpd=$(mktemp -d)
+cleanup() {
+ rm -rf "${tmpd}"
+}
+trap cleanup EXIT
+
+# For some reason network is not yet available
+sleep 5
+
+add-apt-repository ppa:snappy-dev/image -y
+apt-get install -y golang ubuntu-core-initramfs
+
+snap download pc-kernel --channel="${version}/${branch}" --basename=pc-kernel --target-directory="${tmpd}"
+unsquashfs -d "${tmpd}/pc-kernel" "${tmpd}/pc-kernel.snap"
+
+objcopy -O binary -j .initrd "${tmpd}/pc-kernel/kernel.efi" "${tmpd}/initrd"
+objcopy -O binary -j .linux "${tmpd}/pc-kernel/kernel.efi" "${tmpd}/linux"
+objcopy -O binary -j .uname "${tmpd}/pc-kernel/kernel.efi" "${tmpd}/kver"
+
+mkdir "${tmpd}/early"
+mkdir "${tmpd}/main"
+( (cd "${tmpd}/early"; cpio -id) ; (cd "${tmpd}/main"; zstdcat | cpio -id) ) <"${tmpd}/initrd"
+
+if [ "${BUILD_FDE_HOOK-}" = 1 ]; then
+ go build -o "${tmpd}/main/usr/bin/fde-reveal-key" /project/tests/lib/fde-setup-hook
+fi
+
+go build -tags 'nomanagers withtestkeys faultinject' -o "${tmpd}/main/usr/lib/snapd/snap-bootstrap" /project/cmd/snap-bootstrap
+
+(cd "${tmpd}/early"; find . | cpio --create --quiet --format=newc --owner=0:0) >"${tmpd}/new-initrd"
+(cd "${tmpd}/main"; find . | cpio --create --quiet --format=newc --owner=0:0 | zstd -1 -T0) >>"${tmpd}/new-initrd"
+
+ubuntu-core-initramfs create-efi \
+ --kernelver "" \
+ --initrd "${tmpd}/new-initrd" \
+ --kernel "${tmpd}/linux" \
+ --key "${SNAKEOIL_KEY}" \
+ --cert "${SNAKEOIL_CERT}" \
+ --output "${tmpd}/pc-kernel/kernel.efi"
+
+
+if [ "${BUILD_FDE_HOOK-}" = 1 ]; then
+ go build -o "${tmpd}/pc-kernel/meta/hooks/fde-setup" /project/tests/lib/fde-setup-hook
+fi
+
+snap pack "${tmpd}/pc-kernel" --filename="pc-kernel-modified.snap"
diff --git a/tests/nested/manual/remodel-uc20-to-uc22-fakestore/task.yaml b/tests/nested/manual/remodel-uc20-to-uc22-fakestore/task.yaml
new file mode 100644
index 00000000000..849f3927c49
--- /dev/null
+++ b/tests/nested/manual/remodel-uc20-to-uc22-fakestore/task.yaml
@@ -0,0 +1,184 @@
+summary: Blah
+details: |
+ Blah
+
+systems: [ubuntu-20.04-64]
+
+environment:
+ NESTED_CUSTOM_MODEL: $TESTSLIB/assertions/developer1-{VERSION}-dangerous.model
+
+ # encrypted case
+ NESTED_ENABLE_TPM/encrypted: true
+ NESTED_ENABLE_SECURE_BOOT/encrypted: true
+ DISK_IS_ENCRYPTED/encrypted: true
+ BUILD_FDE_HOOK/encrypted: '0'
+ # unencrypted case
+ NESTED_ENABLE_TPM/notencrypted: false
+ NESTED_ENABLE_SECURE_BOOT/notencrypted: false
+ DISK_IS_ENCRYPTED/notencrypted: false
+ BUILD_FDE_HOOK/notencrypted: '0'
+
+ NESTED_ENABLE_TPM/hook: false
+ NESTED_ENABLE_SECURE_BOOT/hook: false
+ DISK_IS_ENCRYPTED/hook: true
+ BUILD_FDE_HOOK/hook: '1'
+
+ NESTED_SIGN_SNAPS_FAKESTORE: true
+ # for the fake store
+ NESTED_FAKESTORE_BLOB_DIR: $(pwd)/fake-store-blobdir
+ NESTED_UBUNTU_IMAGE_SNAPPY_FORCE_SAS_URL: http://localhost:11028
+ REMOTE_SAS_URL: http://10.0.2.2:11028
+
+prepare: |
+ snap install jq remarshal
+ snap install test-snapd-swtpm --edge
+
+ snap install lxd
+ lxd init --auto
+
+ mkdir -p updates/
+
+ "${TESTSTOOLS}/store-state" setup-fake-store "${NESTED_FAKESTORE_BLOB_DIR}"
+ cp "${TESTSLIB}/assertions/developer1.account" "${NESTED_FAKESTORE_BLOB_DIR}/asserts"
+ cp "${TESTSLIB}/assertions/developer1.account-key" "${NESTED_FAKESTORE_BLOB_DIR}/asserts"
+ cp "${TESTSLIB}/assertions/testrootorg-store.account-key" "${NESTED_FAKESTORE_BLOB_DIR}/asserts"
+
+ KEY_NAME=$(tests.nested download snakeoil-key)
+
+ lxc launch "ubuntu:22.04" builder-for-22
+ lxcdir="/project/$(realpath --relative-to="${PROJECT_PATH}" "${PWD}")"
+ lxc config device add builder-for-22 project disk source="${PROJECT_PATH}" path=/project shift=true
+ lxc exec --cwd "${lxcdir}" \
+ --env SNAKEOIL_KEY="${lxcdir}/${KEY_NAME}.key" \
+ --env SNAKEOIL_CERT="${lxcdir}/${KEY_NAME}.pem" \
+ --env "BUILD_FDE_HOOK=${BUILD_FDE_HOOK}" \
+ builder-for-22 -- bash -x repack-kernel.sh 22 beta
+
+ mv pc-kernel-modified.snap updates/pc-kernel-22.snap
+
+ snap download --channel="latest/edge" --basename="original-core22" "core22"
+ # shellcheck source=tests/lib/prepare.sh
+ . "$TESTSLIB/prepare.sh"
+ repack_core_snap_with_tweaks original-core22.snap updates/core22.snap
+ rm -f original-core22.{snap,assert}
+
+ snap download --channel="22/edge" --basename="original-pc-22" "pc"
+ unsquashfs -d pc original-pc-22.snap
+ rm -f original-pc-22.{snap,assert}
+ SNAKEOIL_KEY="${PWD}/${KEY_NAME}.key"
+ SNAKEOIL_CERT="${PWD}/${KEY_NAME}.pem"
+ # shellcheck source=tests/lib/nested.sh
+ . "$TESTSLIB/nested.sh"
+ nested_secboot_sign_gadget pc "${SNAKEOIL_KEY}" "${SNAKEOIL_CERT}"
+ echo "7777" > pc/serial
+ mkdir -p pc/meta/hooks/
+ cp prepare-device pc/meta/hooks/
+ echo "console=ttyS0 systemd.journald.forward_to_console=1" >>pc/cmdline.extra
+ snap pack pc updates/ --filename="pc-22.snap"
+ rm -rf pc
+
+ "$TESTSTOOLS"/store-state make-snap-installable --noack --revision 2 "${NESTED_FAKESTORE_BLOB_DIR}" "updates/core22.snap" "amcUKQILKXHHTlmSa7NMdnXSx02dNeeT"
+ "$TESTSTOOLS"/store-state make-snap-installable --noack --revision 2 "${NESTED_FAKESTORE_BLOB_DIR}" "updates/pc-kernel-22.snap" "pYVQrBcKmBa0mZ4CCN7ExT6jH8rY1hza"
+ "$TESTSTOOLS"/store-state make-snap-installable --noack --revision 3 "${NESTED_FAKESTORE_BLOB_DIR}" "updates/pc-22.snap" "UqFziVZDHLSyO3TqSWgNBoAdHbLI4dAH"
+
+ getassert() {
+ FILENAME=$1
+ ID=$2
+ SUM="$(snap info --verbose "$(realpath "${FILENAME}")" | sed '/^sha3-384: */{;s///;q;};d')"
+ cat "${TESTSLIB}/assertions/developer1.account-key"
+ echo
+ SNAPPY_FORCE_SAS_URL="${NESTED_UBUNTU_IMAGE_SNAPPY_FORCE_SAS_URL}" snap known --remote snap-declaration snap-id="${ID}" series=16
+ echo
+ SNAPPY_FORCE_SAS_URL="${NESTED_UBUNTU_IMAGE_SNAPPY_FORCE_SAS_URL}" snap known --remote snap-revision snap-sha3-384="${SUM}"
+ }
+
+ getassert "updates/core22.snap" "amcUKQILKXHHTlmSa7NMdnXSx02dNeeT" >"updates/core22.assert"
+ getassert "updates/pc-kernel-22.snap" "pYVQrBcKmBa0mZ4CCN7ExT6jH8rY1hza" >"updates/pc-kernel-22.assert"
+ getassert "updates/pc-22.snap" "UqFziVZDHLSyO3TqSWgNBoAdHbLI4dAH" >"updates/pc-22.assert"
+
+ if [ "${BUILD_FDE_HOOK-}" = 1 ]; then
+ mkdir -p ./extra-initrd/usr/bin/
+ go build -o ./extra-initrd/usr/bin/fde-reveal-key "$TESTSLIB"/fde-setup-hook/fde-setup.go
+ mkdir -p ./extra-kernel-snap/meta/hooks
+ go build -o ./extra-kernel-snap/meta/hooks/fde-setup "$TESTSLIB"/fde-setup-hook/fde-setup.go
+ fi
+
+ tests.nested prepare-essential-snaps
+ unsquashfs -d pc-20 "$(tests.nested get extra-snaps-path)/pc.snap"
+ echo "7777" > pc-20/serial
+ mkdir -p pc-20/meta/hooks/
+ cp prepare-device pc-20/meta/hooks/
+ rm "$(tests.nested get extra-snaps-path)/pc.snap"
+ echo "console=ttyS0 systemd.journald.forward_to_console=1" >>pc-20/cmdline.extra
+ snap pack pc-20/ --filename="$(tests.nested get extra-snaps-path)/pc.snap"
+ "$TESTSTOOLS"/store-state make-snap-installable --noack --revision 2 "${NESTED_FAKESTORE_BLOB_DIR}" "$(tests.nested get extra-snaps-path)/pc.snap" "UqFziVZDHLSyO3TqSWgNBoAdHbLI4dAH"
+
+ add_to_channel() {
+ FILENAME=$1
+ CHANNEL=$2
+ SUM="$(snap info --verbose "$(realpath "${FILENAME}")" | sed '/^sha3-384: */{;s///;q;};d')"
+ mkdir -p "${NESTED_FAKESTORE_BLOB_DIR}/channels"
+ echo "${CHANNEL}" >"${NESTED_FAKESTORE_BLOB_DIR}/channels/${SUM}"
+ }
+
+ add_to_channel updates/pc-kernel-22.snap 22/edge
+ add_to_channel updates/pc-22.snap 22/edge
+ add_to_channel "$(tests.nested get extra-snaps-path)/pc-kernel.snap" 20/edge
+ add_to_channel "$(tests.nested get extra-snaps-path)/pc.snap" 20/edge
+
+ for snap in "$(tests.nested get extra-snaps-path)"/snapd*.snap; do
+ add_to_channel "${snap}" latest/stable
+ done
+ add_to_channel updates/core22.snap latest/stable
+
+ # start fake device svc
+ systemd-run --collect --unit fakedevicesvc fakedevicesvc localhost:11029
+
+ NESTED_BUILD_SNAPD_FROM_CURRENT=false tests.nested build-image core
+ tests.nested create-vm core
+
+ cat <snapd-override.conf
+ [Service]
+ Environment=SNAPPY_FORCE_API_URL=${REMOTE_SAS_URL}
+ EOF
+ remote.push snapd-override.conf
+ remote.exec sudo mkdir -p /etc/systemd/system/snapd.service.d
+ remote.exec sudo cp snapd-override.conf /etc/systemd/system/snapd.service.d/
+ remote.exec sudo systemctl daemon-reload
+ remote.exec sudo systemctl restart snapd
+
+restore: |
+ # stop fake device svc
+ systemctl stop fakedevicesvc
+
+ "${TESTSTOOLS}/store-state" teardown-fake-store "${NESTED_FAKESTORE_BLOB_DIR}"
+
+ rm -rf updates/
+
+execute: |
+ if [ "${DISK_IS_ENCRYPTED}" = true ]; then
+ remote.exec "ls /dev/mapper/ubuntu-data*"
+ remote.exec "ls /dev/mapper/ubuntu-save*"
+ fi
+
+ for f in updates/{core22,pc-22,pc-kernel-22}.{snap,assert} updates/pc-22.{snap,assert} updates/pc-kernel-22.{snap,assert}; do
+ remote.push "${f}"
+ done
+ remote.push "${TESTSLIB}/assertions/developer1-22-dangerous.model"
+
+ # There is no tracks on fakestore yet. So let's remove the old versions.
+ #rm "${NESTED_FAKESTORE_BLOB_DIR}/pc.snap"
+ #rm "${NESTED_FAKESTORE_BLOB_DIR}/pc-kernel.snap"
+
+ remote.exec "snap model --assertion" | MATCH '^model: testkeys-snapd-dangerous-core-20-amd64$'
+ remote.exec "snap model --assertion" | NOMATCH '^model: testkeys-snapd-dangerous-core-22-amd64$'
+
+ boot_id="$(tests.nested boot-id)"
+ change_id="$(remote.exec sudo snap remodel --no-wait developer1-22-dangerous.model)"
+ remote.wait-for reboot "${boot_id}"
+
+ retry -n 100 --wait 5 sh -c "remote.exec sudo snap changes | MATCH '^${change_id}\s+(Done|Undone|Error)'"
+ remote.exec "sudo snap changes" | MATCH "^${change_id}\s+Done"
+
+ remote.exec "snap model --assertion" | MATCH '^model: testkeys-snapd-dangerous-core-22-amd64$'
+ remote.exec "snap model --assertion" | NOMATCH '^model: testkeys-snapd-dangerous-core-20-amd64$'
diff --git a/tests/nested/manual/uc-update-assets-secure/task.yaml b/tests/nested/manual/uc-update-assets-secure/task.yaml
index b5098e9280e..bb59c8aa936 100644
--- a/tests/nested/manual/uc-update-assets-secure/task.yaml
+++ b/tests/nested/manual/uc-update-assets-secure/task.yaml
@@ -30,36 +30,49 @@ prepare: |
SNAKEOIL_KEY="$PWD/$KEY_NAME.key"
SNAKEOIL_CERT="$PWD/$KEY_NAME.pem"
- # Save the shim before resigning
- cp pc/shim.efi.signed shim.efi.signed
+ # Remove signatures
+ cp pc/shim.efi.signed shim.efi
+ tests.nested secboot-remove-signature shim.efi
+
+ # Use a key to sign grub instead of snakeoil key
+ openssl req -new -x509 -newkey rsa:2048 -subj "/CN=old vendor certificate/" -keyout old-cert.key -out old-cert.crt -days 3650 -nodes -sha256
+ openssl x509 -outform der -in old-cert.crt -out old-cert
+ python3 generate_vendor_cert_section.py old-section old-cert
+ objcopy --update-section .vendor_cert=old-section shim.efi shim.efi.old
+
+ cp shim.efi.old pc/shim.efi.signed
+
+ tests.nested secboot-sign file pc/shim.efi.signed "${SNAKEOIL_KEY}" "${SNAKEOIL_CERT}"
+ tests.nested secboot-sign file pc/grubx64.efi "old-cert.key" "old-cert.crt"
+
+ # FIXME: this should not be needed. We will tell secboot that the
+ # new shim might try to boot the old grub. But this is not true.
+ # This will be fixed by https://github.com/snapcore/snapd/pull/13402
+ tests.nested secboot-sign file --keep-signatures pc/grubx64.efi "${SNAKEOIL_KEY}" "${SNAKEOIL_CERT}"
- # Repack pc gadget for the initial image
- tests.nested secboot-sign file pc/shim.efi.signed "$SNAKEOIL_KEY" "$SNAKEOIL_CERT"
- tests.nested secboot-sign file pc/grubx64.efi "$SNAKEOIL_KEY" "$SNAKEOIL_CERT"
old_shim_sha="$(sha256sum pc/shim.efi.signed | sed "s/ .*//")"
old_grub_sha="$(sha256sum pc/grubx64.efi | sed "s/ .*//")"
- snap pack pc "$(tests.nested get extra-snaps-path)"
- # Remove signatures
- cp shim.efi.signed shim.efi
- tests.nested secboot-remove-signature shim.efi
+ # This is the the gadget for the initial image
+ snap pack pc "$(tests.nested get extra-snaps-path)"
# Add a different vendor certificate
openssl req -new -x509 -newkey rsa:2048 -subj "/CN=new vendor certificate/" -keyout new-cert.key -out new-cert.crt -days 3650 -nodes -sha256
openssl x509 -outform der -in new-cert.crt -out new-cert
python3 generate_vendor_cert_section.py new-section new-cert
- objcopy --update-section .vendor_cert=new-section shim.efi shim.efi.out
+ objcopy --update-section .vendor_cert=new-section shim.efi shim.efi.new
+
+ cp shim.efi.new pc/shim.efi.signed
- # Sign modified shim
- cp shim.efi.out pc/shim.efi.signed
tests.nested secboot-sign file pc/shim.efi.signed "${SNAKEOIL_KEY}" "${SNAKEOIL_CERT}"
+ # Even if we install a new seed, the grub has to be signed with the
+ # key so that we do not break on a reset in the middle of the upate.
+ # (old shim must always be able to boot the new grub).
+ tests.nested secboot-sign file pc/grubx64.efi "old-cert.key" "old-cert.crt"
if [ "${UPDATE_SEED}" = true ]; then
- # Resign grub with new vendor key
- tests.nested secboot-sign file pc/grubx64.efi "new-cert.key" "new-cert.crt"
- else
- # If shim is not installed in seed, then we need to keep the snakeoil signature
- tests.nested secboot-sign file pc/grubx64.efi "${SNAKEOIL_KEY}" "${SNAKEOIL_CERT}"
+ # Resign grub with new vendor key. But we keep the signature with the old one.
+ tests.nested secboot-sign file --keep-signatures pc/grubx64.efi "new-cert.key" "new-cert.crt"
fi
new_shim_sha="$(sha256sum pc/shim.efi.signed | sed "s/ .*//")"