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/ .*//")"