diff --git a/README.md b/README.md index b2add27..a3ea5d6 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,112 @@ nullboot is a boot manager for environments that do not need a boot manager. Instead of running a boot manager at boot, it directly manages the UEFI boot entries for you. +Use cases +--------- + +Usage: +1. nullbootctl +2. nullbootctl -no-tpm -output-json FILE +3. nullbootctl -no-boot-next +4. nullbootctl recovery-key [OPTIONS] + +### nullbootctl + +Finalize shim and UKI installation. + +Without any argument, nullbootctl shall: +- Install shim and UKI (Unified Kernel Image) to the EFI System Partition (ESP, + ie: /boot/efi) +- Update the sealing policy of the LUKS passphrase in the TPM +- Set EFI variable BootNext to point to the newly installed shim and kernel + +It is intended to be called upon shim and UKI update. + + +### nullbootctl -no-tpm -output-json FILE + +- Export EFI variables Boot#### and BootOrder. +- Install shim & UKI +- Update the sealing policy of the LUKS passphrase in the TPM + +This shall create a JSON file with the EFI variables Boot#### and BootOrder, as +expected on the first boot. + + +### nullbootctl -no-boot-next + +Commit EFI variable BootOrder after shim and UKI update. + +This shall: +- Install shim and UKI (possibly a no-operation) +- Update the sealing policy of the LUKS passphrase in the TPM (possibly a + no-operation) +- Commit shim and UKI by updating EFI variable BootOrder (when the system + boots on EFI variable BootNext) +- Remove old kernel + + +### nullbootctl recovery-key [OPTIONS] + +Manage recovery keys (LUKS passphrases) of a LUKS container. + +This shall setup, list or delete a recovery key for the LUKS container. + +Options: + --create [--device DEVICE] [--name NAME] + --delete [--device DEVICE] [--name NAME] + --list [--device DEVICE] + +Example: +``` +# nullbootctl recovery-key --create +Creating recovery key 'recovery-0001' in '/dev/disk/by-label/cloudimg-rootfs-enc' +18466-30786-51485-64513-29543-52270-35959-33619 + +# nullbootctl recovery-key --list +Listing recovery keys in '/dev/disk/by-label/cloudimg-rootfs-enc' +recovery-0001 + +# nullbootctl recovery-key --delete +Deleting recovery key 'recovery-0001' in '/dev/disk/by-label/cloudimg-rootfs-enc' +Cannot delete recovery key: cannot kill last remaining slot +``` + +Build +----- +``` +$ go build -o . ./... +``` + +Execute +------- +``` +$ ./nullbootctl -h +2026/05/19 09:40:54 usage: + +1. nullbootctl +2. nullbootctl -no-tpm -output-json FILE +3. nullbootctl -no-boot-next +4. nullbootctl recovery-key [OPTIONS] + +Commands: + + recovery-key + Manage FDE recovery keys (LUKS passphrases). + Options: + --create [--device DEVICE] [--name NAME] + --delete [--device DEVICE] [--name NAME] + --list [--device DEVICE] +``` + +Unit test +--------- +``` +$ go test -v -coverprofile=profile.cov ./... +$ echo $? +0 +``` + Licensing --------- This program is free software: you can redistribute it and/or modify it under diff --git a/cmd/nullbootctl/main.go b/cmd/nullbootctl/main.go index 8500c20..1ca6a31 100644 --- a/cmd/nullbootctl/main.go +++ b/cmd/nullbootctl/main.go @@ -4,19 +4,108 @@ package main -import "github.com/canonical/nullboot/efibootmgr" -import "flag" -import "log" -import "os" +import ( + "flag" + "github.com/canonical/nullboot/efibootmgr" + "github.com/canonical/nullboot/luks2" + "log" + "os" +) -var noTPM = flag.Bool("no-tpm", false, "Do not do any resealing with the TPM") -var noEfivars = flag.Bool("no-efivars", false, "Do not use or update the EFI variables. Disables kernel fallback mechanism") -var outputJSON = flag.String("output-json", "", "JSON file to write. Disables writing real EFI variables and enablement of the kernel fallback mechanism") -var noBootNext = flag.Bool("no-boot-next", false, "Disables use of BootNext. This flag must be disabled in order to upgrade to a new kernel version.") +const Usage = `usage: + +1. nullbootctl +2. nullbootctl -no-tpm -output-json FILE +3. nullbootctl -no-boot-next +4. nullbootctl recovery-key [OPTIONS] + +Commands: + + recovery-key + Manage FDE recovery keys (LUKS passphrases). + Options: + --create [--device DEVICE] [--name NAME] + --delete [--device DEVICE] [--name NAME] + --list [--device DEVICE] +` + +func usage() { + log.Print(Usage) + os.Exit(1) +} func main() { + + if len(os.Args) > 1 { + if os.Args[1] == "recovery-key" { + os.Args = os.Args[1:] // Strip the first item + cmd_recovery_key() + return + } + if os.Args[1] == "-h" || os.Args[1] == "--help" { + usage() + } + } + + cmd_default() +} + +const ( + default_cloudimg_encrypted_device = "/dev/disk/by-label/" + efibootmgr.RootfsLabel + default_recovery_name = "recovery-0001" +) + +func cmd_recovery_key() { + + var devicePath string + var recoveryName string + doCreate := flag.Bool("create", false, "Create and set a recovery key") + doList := flag.Bool("list", false, "List recovery keys") + doDelete := flag.Bool("delete", false, "Delete a recovery key") + flag.StringVar(&devicePath, "device", default_cloudimg_encrypted_device, "Device of the encrypted volume") + flag.StringVar(&recoveryName, "name", default_recovery_name, "Name of the recovery key") + + flag.Parse() + + if *doCreate && *doList { + log.Println("Options --create and --list cannot be used together") + os.Exit(1) + } + if *doCreate && *doDelete { + log.Println("Options --create and --delete cannot be used together") + os.Exit(1) + } + if *doDelete && *doList { + log.Println("Options --delete and --list cannot be used together") + os.Exit(1) + } + + var err error + if *doList { + err = luks2.ListRecoveryKeys(devicePath) + } else if *doDelete { + err = luks2.DeleteRecoveryKey(devicePath, recoveryName) + } else if *doCreate { + err = luks2.CreateRecoveryKey(devicePath, recoveryName) + } else { + log.Println("Please select at least one action: --create, --list, --delete") + os.Exit(1) + } + + if err != nil { + os.Exit(1) + } +} + +func cmd_default() { var assets *efibootmgr.TrustedAssets var err error + + noTPM := flag.Bool("no-tpm", false, "Do not do any resealing with the TPM") + noEfivars := flag.Bool("no-efivars", false, "Do not use or update the EFI variables. Disables kernel fallback mechanism") + outputJSON := flag.String("output-json", "", "JSON file to write. Disables writing real EFI variables and enablement of the kernel fallback mechanism") + noBootNext := flag.Bool("no-boot-next", false, "Disables use of BootNext. This flag must be disabled in order to upgrade to a new kernel version.") + flag.Parse() const ( diff --git a/efibootmgr/efivars.go b/efibootmgr/efivars.go index d71a3cc..e5ded38 100644 --- a/efibootmgr/efivars.go +++ b/efibootmgr/efivars.go @@ -11,7 +11,7 @@ import ( "strings" //"errors" - "github.com/canonical/go-efilib" + efi "github.com/canonical/go-efilib" efi_linux "github.com/canonical/go-efilib/linux" ) @@ -20,30 +20,30 @@ type EFIVariables interface { ListVariables() ([]efi.VariableDescriptor, error) GetVariable(guid efi.GUID, name string) (data []byte, attrs efi.VariableAttributes, err error) SetVariable(guid efi.GUID, name string, data []byte, attrs efi.VariableAttributes) error - NewFileDevicePath(filepath string, mode efi_linux.FileDevicePathMode) (efi.DevicePath, error) + NewFileDevicePath(filepath string, mode efi_linux.FilePathToDevicePathMode) (efi.DevicePath, error) } // RealEFIVariables provides the real implementation of efivars type RealEFIVariables struct{} // ListVariables proxy -func (RealEFIVariables) ListVariables() ([]efi.VariableDescriptor, error) { - return efi.ListVariables() +func (vars RealEFIVariables) ListVariables() ([]efi.VariableDescriptor, error) { + return efi.ListVariables(efi.DefaultVarContext) } // GetVariable proxy -func (RealEFIVariables) GetVariable(guid efi.GUID, name string) (data []byte, attrs efi.VariableAttributes, err error) { - return efi.ReadVariable(name, guid) +func (vars RealEFIVariables) GetVariable(guid efi.GUID, name string) (data []byte, attrs efi.VariableAttributes, err error) { + return efi.ReadVariable(efi.DefaultVarContext, name, guid) } // SetVariable proxy -func (RealEFIVariables) SetVariable(guid efi.GUID, name string, data []byte, attrs efi.VariableAttributes) error { - return efi.WriteVariable(name, guid, attrs, data) +func (vars RealEFIVariables) SetVariable(guid efi.GUID, name string, data []byte, attrs efi.VariableAttributes) error { + return efi.WriteVariable(efi.DefaultVarContext, name, guid, attrs, data) } // NewFileDevicePath proxy -func (RealEFIVariables) NewFileDevicePath(filepath string, mode efi_linux.FileDevicePathMode) (efi.DevicePath, error) { - return efi_linux.NewFileDevicePath(filepath, mode) +func (vars RealEFIVariables) NewFileDevicePath(filepath string, mode efi_linux.FilePathToDevicePathMode) (efi.DevicePath, error) { + return efi_linux.FilePathToDevicePath(filepath, mode) } type mockEFIVariable struct { @@ -87,7 +87,7 @@ func (m *MockEFIVariables) SetVariable(guid efi.GUID, name string, data []byte, } // NewFileDevicePath implements EFIVariables -func (m MockEFIVariables) NewFileDevicePath(filepath string, mode efi_linux.FileDevicePathMode) (efi.DevicePath, error) { +func (m MockEFIVariables) NewFileDevicePath(filepath string, mode efi_linux.FilePathToDevicePathMode) (efi.DevicePath, error) { file, err := appFs.Open(filepath) if err != nil { return nil, err diff --git a/efibootmgr/efivars_test.go b/efibootmgr/efivars_test.go index 3cf414a..104869e 100644 --- a/efibootmgr/efivars_test.go +++ b/efibootmgr/efivars_test.go @@ -9,7 +9,7 @@ package efibootmgr import ( "errors" - "github.com/canonical/go-efilib" + efi "github.com/canonical/go-efilib" efi_linux "github.com/canonical/go-efilib/linux" ) @@ -27,7 +27,7 @@ func (NoEFIVariables) SetVariable(guid efi.GUID, name string, data []byte, attrs return efi.ErrVarsUnavailable } -func (NoEFIVariables) NewFileDevicePath(filepath string, mode efi_linux.FileDevicePathMode) (efi.DevicePath, error) { +func (NoEFIVariables) NewFileDevicePath(filepath string, mode efi_linux.FilePathToDevicePathMode) (efi.DevicePath, error) { return nil, errors.New("Cannot access") } diff --git a/efibootmgr/reseal.go b/efibootmgr/reseal.go index 7df406c..a77d451 100644 --- a/efibootmgr/reseal.go +++ b/efibootmgr/reseal.go @@ -10,14 +10,13 @@ import ( "encoding/binary" "errors" "fmt" - "io" "log" "os" "path/filepath" "strings" "syscall" - "github.com/canonical/go-efilib" + efi "github.com/canonical/go-efilib" "github.com/canonical/go-tpm2" "github.com/canonical/tcglog-parser" "github.com/snapcore/secboot" @@ -30,14 +29,12 @@ import ( const ( keyFilePath = "device/fde/cloudimg-rootfs.sealed-key" keyringPrefix = "ubuntu-fde" - rootfsLabel = "cloudimg-rootfs-enc" + RootfsLabel = "cloudimg-rootfs-enc" ) var ( efiComputePeImageDigest = efi.ComputePeImageDigest - sbefiAddBootManagerProfile = secboot_efi.AddBootManagerProfile - sbefiAddSecureBootPolicyProfile = secboot_efi.AddSecureBootPolicyProfile - sbGetAuxiliaryKeyFromKernel = secboot.GetAuxiliaryKeyFromKernel + sbGetPrimaryKeyFromKernel = secboot.GetPrimaryKeyFromKernel sbtpmConnectToDefaultTPM = secboot_tpm2.ConnectToDefaultTPM sbtpmReadSealedKeyObjectFromFile = secboot_tpm2.ReadSealedKeyObjectFromFile sbtpmSealedKeyObjectUpdatePCRProtectionPolicy = (*secboot_tpm2.SealedKeyObject).UpdatePCRProtectionPolicy @@ -64,11 +61,7 @@ func (i *trustedEFIImage) String() string { return i.path } -func (i *trustedEFIImage) Open() (file interface { - io.ReaderAt - io.Closer - Size() int64 -}, err error) { +func (i *trustedEFIImage) Open() (imageReader secboot_efi.ImageReader, err error) { f, err := appFs.Open(i.path) if err != nil { return nil, err @@ -116,8 +109,8 @@ func resolveLink(path string) (string, error) { } } -func getPolicyAuthKeyFromKernel() (secboot_tpm2.PolicyAuthKey, error) { - devPath, err := resolveLink(filepath.Join("/dev/disk/by-label", rootfsLabel)) +func getPrimaryKeyFromKernel() (secboot.PrimaryKey, error) { + devPath, err := resolveLink(filepath.Join("/dev/disk/by-label", RootfsLabel)) if err != nil { return nil, fmt.Errorf("cannot resolve devive symlink: %w", err) } @@ -131,7 +124,7 @@ func getPolicyAuthKeyFromKernel() (secboot_tpm2.PolicyAuthKey, error) { return nil, fmt.Errorf("cannot link user keyring into process keyring: %w", err) } - key, err := sbGetAuxiliaryKeyFromKernel(keyringPrefix, devPath, false) + key, err := sbGetPrimaryKeyFromKernel(keyringPrefix, devPath, false) if err != nil { if err == secboot.ErrKernelKeyNotFound { // Work around a secboot bug @@ -145,7 +138,7 @@ func getPolicyAuthKeyFromKernel() (secboot_tpm2.PolicyAuthKey, error) { } if devPath2 == devPath { - key, err = sbGetAuxiliaryKeyFromKernel(keyringPrefix, path, false) + key, err = sbGetPrimaryKeyFromKernel(keyringPrefix, path, false) break } } @@ -156,32 +149,101 @@ func getPolicyAuthKeyFromKernel() (secboot_tpm2.PolicyAuthKey, error) { } } - return secboot_tpm2.PolicyAuthKey(key), nil + return key, nil } -func computePCRProtectionProfile(loadChains []*secboot_efi.ImageLoadEvent) (*secboot_tpm2.PCRProtectionProfile, error) { - profile := secboot_tpm2.NewPCRProtectionProfile() +// This LoadChain stuff is copied from snapd +// It gives us a structure we can introspect in unit tests as oppossed +// to the new secboot structures which are now fully opaque. + +type LoadChain struct { + *trustedEFIImage + // Next is a list of alternative chains that can be loaded + // following the boot file. + Next []*LoadChain +} + +func NewLoadChain(image *trustedEFIImage, next ...*LoadChain) *LoadChain { + return &LoadChain{ + trustedEFIImage: image, + Next: next, + } +} + +func buildLoadSequences(chains []*LoadChain) (loadseqs *secboot_efi.ImageLoadSequences, err error) { + // this will build load event trees for the current + // device configuration, e.g. something like: + // + // shim -> kernel 1 + // |-> kernel 2 + // |-> kernel ... + + loadseqs = secboot_efi.NewImageLoadSequences() + + for _, chain := range chains { + // root of load events has source Firmware + loadseq, err := chain.loadEvent() + if err != nil { + return nil, err + } + loadseqs.Append(loadseq) + } + return loadseqs, nil +} - pcr4Params := secboot_efi.BootManagerProfileParams{ - PCRAlgorithm: tpm2.HashAlgorithmSHA256, - LoadSequences: loadChains} - if err := sbefiAddBootManagerProfile(profile, &pcr4Params); err != nil { - return nil, fmt.Errorf("cannot add EFI boot manager profile: %w", err) +// loadEvent builds the corresponding load event and its tree +func (lc *LoadChain) loadEvent() (secboot_efi.ImageLoadActivity, error) { + var next []secboot_efi.ImageLoadActivity + for _, nextChain := range lc.Next { + // everything that is not the root has source shim + ev, err := nextChain.loadEvent() + if err != nil { + return nil, err + } + next = append(next, ev) } + return secboot_efi.NewImageLoadActivity(lc).Loads(next...), nil +} + +// Hook for unit tests to introspect load chains +var introspectLoadChains func( + pcrAlg tpm2.HashAlgorithmId, + rootBranch *secboot_tpm2.PCRProtectionProfileBranch, + loadChains []*LoadChain) = nil + +func computePCRProtectionProfile(loadChains []*LoadChain) (*secboot_tpm2.PCRProtectionProfile, error) { + profile := secboot_tpm2.NewPCRProtectionProfile() + + var options []secboot_efi.PCRProfileOption + options = append(options, + secboot_efi.WithSecureBootPolicyProfile(), + secboot_efi.WithBootManagerCodeProfile(), + ) + + if introspectLoadChains != nil { + introspectLoadChains(tpm2.HashAlgorithmSHA256, profile.RootBranch(), loadChains) + } else { + loadSeqs, err := buildLoadSequences(loadChains) + if err != nil { + return nil, err + } - pcr7Params := secboot_efi.SecureBootPolicyProfileParams{ - PCRAlgorithm: tpm2.HashAlgorithmSHA256, - LoadSequences: loadChains} - if err := sbefiAddSecureBootPolicyProfile(profile, &pcr7Params); err != nil { - return nil, fmt.Errorf("cannot add EFI secure boot policy profile: %w", err) + if err := secboot_efi.AddPCRProfile( + tpm2.HashAlgorithmSHA256, + profile.RootBranch(), + loadSeqs, + options..., + ); err != nil { + return nil, fmt.Errorf("cannot add PCR profile: %w", err) + } } - profile.AddPCRValue(tpm2.HashAlgorithmSHA256, 12, make([]byte, tpm2.HashAlgorithmSHA256.Size())) + profile.RootBranch().AddPCRValue(tpm2.HashAlgorithmSHA256, 12, make([]byte, tpm2.HashAlgorithmSHA256.Size())) // snap-bootstrap measures an epoch h := crypto.SHA256.New() binary.Write(h, binary.LittleEndian, uint32(0)) - profile.ExtendPCR(tpm2.HashAlgorithmSHA256, 12, h.Sum(nil)) + profile.RootBranch().ExtendPCR(tpm2.HashAlgorithmSHA256, 12, h.Sum(nil)) // XXX: The kernel EFI stub has a compiled-in commandline which isn't measured. @@ -224,24 +286,7 @@ func ResealKey(assets *TrustedAssets, km *KernelManager, esp, shimSource, vendor context := new(pcrProfileComputeContext) - shimBase := "shim" + GetEfiArchitecture() + ".efi" - - var roots []*secboot_efi.ImageLoadEvent - - for _, path := range []string{ - filepath.Join(shimSource, shimBase+".signed"), - filepath.Join(esp, "EFI", vendor, shimBase)} { - _, err := appFs.Stat(path) - if os.IsNotExist(err) { - continue - } - - roots = append(roots, &secboot_efi.ImageLoadEvent{ - Source: secboot_efi.Firmware, - Image: newTrustedEFIImage(assets, context, path)}) - } - - var kernels []*secboot_efi.ImageLoadEvent + var kernels []*LoadChain sourceKernelNames := []string{} for _, sk := range km.sourceKernels { @@ -268,22 +313,31 @@ func ResealKey(assets *TrustedAssets, km *KernelManager, esp, shimSource, vendor for _, n := range x.files { path := filepath.Join(x.dir, n) - kernels = append(kernels, &secboot_efi.ImageLoadEvent{ - Source: secboot_efi.Shim, - Image: newTrustedEFIImage(assets, context, path)}) + kernels = append(kernels, NewLoadChain(newTrustedEFIImage(assets, context, path))) } } - for _, root := range roots { - root.Next = kernels + shimBase := "shim" + GetEfiArchitecture() + ".efi" + + var shims []*LoadChain + + for _, path := range []string{ + filepath.Join(shimSource, shimBase+".signed"), + filepath.Join(esp, "EFI", vendor, shimBase)} { + _, err := appFs.Stat(path) + if os.IsNotExist(err) { + continue + } + + shims = append(shims, NewLoadChain(newTrustedEFIImage(assets, context, path), kernels...)) } - authKey, err := getPolicyAuthKeyFromKernel() + authKey, err := getPrimaryKeyFromKernel() if err != nil { return fmt.Errorf("cannot obtain auth key from kernel: %w", err) } - pcrProfile, err := computePCRProtectionProfile(roots) + pcrProfile, err := computePCRProtectionProfile(shims) if err != nil { return fmt.Errorf("cannot compute PCR profile: %w", err) } diff --git a/efibootmgr/reseal_test.go b/efibootmgr/reseal_test.go index 3c7f569..30f4f33 100644 --- a/efibootmgr/reseal_test.go +++ b/efibootmgr/reseal_test.go @@ -11,12 +11,11 @@ import ( "io/ioutil" "os" - "github.com/canonical/go-efilib" + efi "github.com/canonical/go-efilib" "github.com/canonical/go-tpm2" "github.com/canonical/go-tpm2/linux" "github.com/canonical/tcglog-parser" "github.com/snapcore/secboot" - secboot_efi "github.com/snapcore/secboot/efi" secboot_tpm2 "github.com/snapcore/secboot/tpm2" "golang.org/x/sys/unix" @@ -28,27 +27,11 @@ type resealSuite struct { mapFsMixin } -func (*resealSuite) mockSbefiAddBootManagerProfile(fn func(profile *secboot_tpm2.PCRProtectionProfile, params *secboot_efi.BootManagerProfileParams) error) (restore func()) { - orig := sbefiAddBootManagerProfile - sbefiAddBootManagerProfile = fn +func (*resealSuite) mockSbGetPrimaryKeyFromKernel(fn func(prefix, devicePath string, remove bool) (secboot.PrimaryKey, error)) (restore func()) { + orig := sbGetPrimaryKeyFromKernel + sbGetPrimaryKeyFromKernel = fn return func() { - sbefiAddBootManagerProfile = orig - } -} - -func (*resealSuite) mockSbefiAddSecureBootPolicyProfile(fn func(profile *secboot_tpm2.PCRProtectionProfile, params *secboot_efi.SecureBootPolicyProfileParams) error) (restore func()) { - orig := sbefiAddSecureBootPolicyProfile - sbefiAddSecureBootPolicyProfile = fn - return func() { - sbefiAddSecureBootPolicyProfile = orig - } -} - -func (*resealSuite) mockSbGetAuxiliaryKeyFromKernel(fn func(prefix, devicePath string, remove bool) (secboot.AuxiliaryKey, error)) (restore func()) { - orig := sbGetAuxiliaryKeyFromKernel - sbGetAuxiliaryKeyFromKernel = fn - return func() { - sbGetAuxiliaryKeyFromKernel = orig + sbGetPrimaryKeyFromKernel = orig } } @@ -68,7 +51,7 @@ func (*resealSuite) mockSbtpmReadSealedKeyObjectFromFile(fn func(path string) (* } } -func (*resealSuite) mockSbtpmSealedKeyObjectUpdatePCRProtectionPolicy(fn func(k *secboot_tpm2.SealedKeyObject, tpm *secboot_tpm2.Connection, authKey secboot_tpm2.PolicyAuthKey, profile *secboot_tpm2.PCRProtectionProfile) error) (restore func()) { +func (*resealSuite) mockSbtpmSealedKeyObjectUpdatePCRProtectionPolicy(fn func(k *secboot_tpm2.SealedKeyObject, tpm *secboot_tpm2.Connection, authKey secboot.PrimaryKey, profile *secboot_tpm2.PCRProtectionProfile) error) (restore func()) { orig := sbtpmSealedKeyObjectUpdatePCRProtectionPolicy sbtpmSealedKeyObjectUpdatePCRProtectionPolicy = fn return func() { @@ -139,11 +122,11 @@ func (s *resealSuite) TestTrustedEfiImageBad(c *check.C) { } type testResealKeyData struct { - arch string - auxiliaryKey []byte - devicePaths []string - shims [][]byte - kernels [][]byte + arch string + primaryKey secboot.PrimaryKey + devicePaths []string + shims [][]byte + kernels [][]byte } func (s *resealSuite) testResealKey(c *check.C, data *testResealKeyData) { @@ -156,13 +139,16 @@ func (s *resealSuite) testResealKey(c *check.C, data *testResealKeyData) { restore := s.mockEfiArch(data.arch) defer restore() - restore = s.mockSbefiAddBootManagerProfile(func(profile *secboot_tpm2.PCRProtectionProfile, params *secboot_efi.BootManagerProfileParams) error { - c.Assert(profile, check.NotNil) - c.Check(params.PCRAlgorithm, check.Equals, tpm2.HashAlgorithmSHA256) + introspectLoadChains = func( + pcrAlg tpm2.HashAlgorithmId, + rootBranch *secboot_tpm2.PCRProtectionProfileBranch, + loadChains []*LoadChain) { + c.Assert(rootBranch, check.NotNil) + c.Check(pcrAlg, check.Equals, tpm2.HashAlgorithmSHA256) - c.Assert(params.LoadSequences, check.HasLen, len(data.shims)) - for i, e := range params.LoadSequences { - f, err := e.Image.Open() + c.Assert(loadChains, check.HasLen, len(data.shims)) + for i, e := range loadChains { + f, err := e.Open() c.Assert(err, check.IsNil) r := io.NewSectionReader(f, 0, 1<<63-1) @@ -174,7 +160,7 @@ func (s *resealSuite) testResealKey(c *check.C, data *testResealKeyData) { c.Assert(e.Next, check.HasLen, len(data.kernels)) for i, e := range e.Next { - f, err := e.Image.Open() + f, err := e.Open() c.Assert(err, check.IsNil) r := io.NewSectionReader(f, 0, 1<<63-1) @@ -186,20 +172,11 @@ func (s *resealSuite) testResealKey(c *check.C, data *testResealKeyData) { } } - profile.AddPCRValue(tpm2.HashAlgorithmSHA256, 4, make([]byte, 32)) - return nil - }) - defer restore() - - restore = s.mockSbefiAddSecureBootPolicyProfile(func(profile *secboot_tpm2.PCRProtectionProfile, params *secboot_efi.SecureBootPolicyProfileParams) error { - c.Assert(profile, check.NotNil) - c.Check(params.PCRAlgorithm, check.Equals, tpm2.HashAlgorithmSHA256) + rootBranch.AddPCRValue(tpm2.HashAlgorithmSHA256, 4, make([]byte, 32)) - c.Assert(params.LoadSequences, check.HasLen, len(data.shims)) - for i, e := range params.LoadSequences { - c.Check(e.Source, check.Equals, secboot_efi.Firmware) - - f, err := e.Image.Open() + c.Assert(loadChains, check.HasLen, len(data.shims)) + for i, e := range loadChains { + f, err := e.Open() c.Assert(err, check.IsNil) r := io.NewSectionReader(f, 0, 1<<63-1) @@ -211,9 +188,7 @@ func (s *resealSuite) testResealKey(c *check.C, data *testResealKeyData) { c.Assert(e.Next, check.HasLen, len(data.kernels)) for i, e := range e.Next { - c.Check(e.Source, check.Equals, secboot_efi.Shim) - - f, err := e.Image.Open() + f, err := e.Open() c.Assert(err, check.IsNil) r := io.NewSectionReader(f, 0, 1<<63-1) @@ -225,13 +200,12 @@ func (s *resealSuite) testResealKey(c *check.C, data *testResealKeyData) { } } - profile.AddPCRValue(tpm2.HashAlgorithmSHA256, 7, make([]byte, 32)) - return nil - }) - defer restore() + rootBranch.AddPCRValue(tpm2.HashAlgorithmSHA256, 7, make([]byte, 32)) + } + defer func() { introspectLoadChains = nil }() n := 0 - restore = s.mockSbGetAuxiliaryKeyFromKernel(func(prefix, devicePath string, remove bool) (secboot.AuxiliaryKey, error) { + restore = s.mockSbGetPrimaryKeyFromKernel(func(prefix, devicePath string, remove bool) (secboot.PrimaryKey, error) { c.Check(prefix, check.Equals, "ubuntu-fde") c.Check(devicePath, check.Equals, data.devicePaths[n]) c.Check(remove, check.Equals, false) @@ -243,7 +217,7 @@ func (s *resealSuite) testResealKey(c *check.C, data *testResealKeyData) { return nil, secboot.ErrKernelKeyNotFound } - return data.auxiliaryKey, nil + return data.primaryKey, nil }) defer restore() @@ -268,15 +242,16 @@ func (s *resealSuite) testResealKey(c *check.C, data *testResealKeyData) { }) defer restore() - restore = s.mockSbtpmSealedKeyObjectUpdatePCRProtectionPolicy(func(k *secboot_tpm2.SealedKeyObject, tpm *secboot_tpm2.Connection, authKey secboot_tpm2.PolicyAuthKey, profile *secboot_tpm2.PCRProtectionProfile) error { + restore = s.mockSbtpmSealedKeyObjectUpdatePCRProtectionPolicy(func(k *secboot_tpm2.SealedKeyObject, tpm *secboot_tpm2.Connection, authKey secboot.PrimaryKey, profile *secboot_tpm2.PCRProtectionProfile) error { c.Check(k, check.Equals, expectedSko) c.Check(tpm, check.Equals, expectedTpm) - c.Check(authKey, check.DeepEquals, secboot_tpm2.PolicyAuthKey(data.auxiliaryKey)) + c.Check(authKey, check.DeepEquals, data.primaryKey) c.Assert(profile, check.NotNil) pcrs, _, err := profile.ComputePCRDigests(nil, tpm2.HashAlgorithmSHA256) c.Check(err, check.IsNil) - c.Check(pcrs.Equal(tpm2.PCRSelectionList{{Hash: tpm2.HashAlgorithmSHA256, Select: []int{4, 7, 12}}}), check.Equals, true) + // NOTE: does not seem like the PCR selection list thingy has a better way to introspect itself... + c.Check(pcrs.String(), check.Equals, "[{hash:TPM_ALG_SHA256, select:[4 7 12]}]") return nil }) defer restore() @@ -337,9 +312,9 @@ func (s *resealSuite) TestResealKeyBeforeNewKernel(c *check.C) { c.Check(s.fs.WriteFile("/usr/lib/linux/kernel.efi-1.0-2-generic", []byte("kernel2"), 0600), check.IsNil) s.testResealKey(c, &testResealKeyData{ - arch: "x64", - auxiliaryKey: []byte{1, 2, 3, 4, 5, 6}, - devicePaths: []string{"/dev/sda1"}, + arch: "x64", + primaryKey: []byte{1, 2, 3, 4, 5, 6}, + devicePaths: []string{"/dev/sda1"}, shims: [][]byte{ []byte("shim1"), []byte("shim1"), @@ -365,9 +340,9 @@ func (s *resealSuite) TestResealKeyAfterNewKernel(c *check.C) { c.Check(s.fs.WriteFile("/usr/lib/linux/kernel.efi-1.0-2-generic", []byte("kernel2"), 0600), check.IsNil) s.testResealKey(c, &testResealKeyData{ - arch: "x64", - auxiliaryKey: []byte{1, 2, 3, 4, 5, 6}, - devicePaths: []string{"/dev/sda1"}, + arch: "x64", + primaryKey: []byte{1, 2, 3, 4, 5, 6}, + devicePaths: []string{"/dev/sda1"}, shims: [][]byte{ []byte("shim1"), []byte("shim1"), @@ -392,9 +367,9 @@ func (s *resealSuite) TestResealKeyBeforeNewShim(c *check.C) { c.Check(s.fs.WriteFile("/usr/lib/linux/kernel.efi-1.0-1-generic", []byte("kernel1"), 0600), check.IsNil) s.testResealKey(c, &testResealKeyData{ - arch: "x64", - auxiliaryKey: []byte{1, 2, 3, 4, 5, 6}, - devicePaths: []string{"/dev/sda1"}, + arch: "x64", + primaryKey: []byte{1, 2, 3, 4, 5, 6}, + devicePaths: []string{"/dev/sda1"}, shims: [][]byte{ []byte("shim2"), []byte("shim1"), @@ -417,9 +392,9 @@ func (s *resealSuite) TestResealKeyAfterNewShim(c *check.C) { c.Check(s.fs.WriteFile("/usr/lib/linux/kernel.efi-1.0-1-generic", []byte("kernel1"), 0600), check.IsNil) s.testResealKey(c, &testResealKeyData{ - arch: "x64", - auxiliaryKey: []byte{1, 2, 3, 4, 5, 6}, - devicePaths: []string{"/dev/sda1"}, + arch: "x64", + primaryKey: []byte{1, 2, 3, 4, 5, 6}, + devicePaths: []string{"/dev/sda1"}, shims: [][]byte{ []byte("shim2"), []byte("shim2"), @@ -443,9 +418,9 @@ func (s *resealSuite) TestResealKeyBeforeRemoveKernel(c *check.C) { c.Check(s.fs.WriteFile("/usr/lib/linux/kernel.efi-1.0-2-generic", []byte("kernel2"), 0600), check.IsNil) s.testResealKey(c, &testResealKeyData{ - arch: "x64", - auxiliaryKey: []byte{1, 2, 3, 4, 5, 6}, - devicePaths: []string{"/dev/sda1"}, + arch: "x64", + primaryKey: []byte{1, 2, 3, 4, 5, 6}, + devicePaths: []string{"/dev/sda1"}, shims: [][]byte{ []byte("shim1"), []byte("shim1"), @@ -469,9 +444,9 @@ func (s *resealSuite) TestResealKeyAfterRemoveKernel(c *check.C) { c.Check(s.fs.WriteFile("/usr/lib/linux/kernel.efi-1.0-2-generic", []byte("kernel2"), 0600), check.IsNil) s.testResealKey(c, &testResealKeyData{ - arch: "x64", - auxiliaryKey: []byte{1, 2, 3, 4, 5, 6}, - devicePaths: []string{"/dev/sda1"}, + arch: "x64", + primaryKey: []byte{1, 2, 3, 4, 5, 6}, + devicePaths: []string{"/dev/sda1"}, shims: [][]byte{ []byte("shim1"), []byte("shim1"), @@ -483,7 +458,7 @@ func (s *resealSuite) TestResealKeyAfterRemoveKernel(c *check.C) { }) } -func (s *resealSuite) TestResealKeyDifferentAuxiliaryKey(c *check.C) { +func (s *resealSuite) TestResealKeyDifferentPrimaryKey(c *check.C) { c.Check(s.fs.WriteFile("/dev/sda1", nil, os.ModeDevice|0660), check.IsNil) s.symlink(c, "/dev/sda1", "/dev/disk/by-label/cloudimg-rootfs-enc") @@ -494,9 +469,9 @@ func (s *resealSuite) TestResealKeyDifferentAuxiliaryKey(c *check.C) { c.Check(s.fs.WriteFile("/usr/lib/linux/kernel.efi-1.0-1-generic", []byte("kernel1"), 0600), check.IsNil) s.testResealKey(c, &testResealKeyData{ - arch: "x64", - auxiliaryKey: []byte{5, 6, 7, 8, 9}, - devicePaths: []string{"/dev/sda1"}, + arch: "x64", + primaryKey: []byte{5, 6, 7, 8, 9}, + devicePaths: []string{"/dev/sda1"}, shims: [][]byte{ []byte("shim2"), []byte("shim1"), @@ -519,9 +494,9 @@ func (s *resealSuite) TestResealKeyDifferentBlockDevice(c *check.C) { c.Check(s.fs.WriteFile("/usr/lib/linux/kernel.efi-1.0-1-generic", []byte("kernel1"), 0600), check.IsNil) s.testResealKey(c, &testResealKeyData{ - arch: "x64", - auxiliaryKey: []byte{1, 2, 3, 4, 5, 6}, - devicePaths: []string{"/dev/vda14"}, + arch: "x64", + primaryKey: []byte{1, 2, 3, 4, 5, 6}, + devicePaths: []string{"/dev/vda14"}, shims: [][]byte{ []byte("shim2"), []byte("shim1"), @@ -545,9 +520,9 @@ func (s *resealSuite) TestResealKeyDifferentArch(c *check.C) { c.Check(s.fs.WriteFile("/usr/lib/linux/kernel.efi-1.0-2-generic", []byte("kernel2"), 0600), check.IsNil) s.testResealKey(c, &testResealKeyData{ - arch: "aa64", - auxiliaryKey: []byte{1, 2, 3, 4, 5, 6}, - devicePaths: []string{"/dev/sda1"}, + arch: "aa64", + primaryKey: []byte{1, 2, 3, 4, 5, 6}, + devicePaths: []string{"/dev/sda1"}, shims: [][]byte{ []byte("shim1"), []byte("shim1"), @@ -560,7 +535,7 @@ func (s *resealSuite) TestResealKeyDifferentArch(c *check.C) { }) } -func (s *resealSuite) TestResealKeyGetAuxiliaryKeyFromKernelBug(c *check.C) { +func (s *resealSuite) TestResealKeyGetPrimaryKeyFromKernelBug(c *check.C) { c.Check(s.fs.WriteFile("/dev/sda1", nil, os.ModeDevice|0660), check.IsNil) c.Check(s.fs.WriteFile("/dev/sda15", nil, os.ModeDevice|0660), check.IsNil) s.symlink(c, "/dev/sda1", "/dev/disk/by-label/cloudimg-rootfs-enc") @@ -574,9 +549,9 @@ func (s *resealSuite) TestResealKeyGetAuxiliaryKeyFromKernelBug(c *check.C) { c.Check(s.fs.WriteFile("/usr/lib/linux/kernel.efi-1.0-1-generic", []byte("kernel1"), 0600), check.IsNil) s.testResealKey(c, &testResealKeyData{ - arch: "x64", - auxiliaryKey: []byte{1, 2, 3, 4, 5, 6}, - devicePaths: []string{"/dev/sda1", "/dev/disk/by-partuuid/94725587-885d-4bde-bc61-078e0010057d"}, + arch: "x64", + primaryKey: []byte{1, 2, 3, 4, 5, 6}, + devicePaths: []string{"/dev/sda1", "/dev/disk/by-partuuid/94725587-885d-4bde-bc61-078e0010057d"}, shims: [][]byte{ []byte("shim2"), []byte("shim1"), @@ -608,42 +583,40 @@ func (s *resealSuite) testResealKeyUnhappy(c *check.C, data *testResealKeyUnhapp restore := s.mockEfiArch("x64") defer restore() - restore = s.mockSbefiAddBootManagerProfile(func(profile *secboot_tpm2.PCRProtectionProfile, params *secboot_efi.BootManagerProfileParams) error { + introspectLoadChains = func( + pcrAlg tpm2.HashAlgorithmId, + rootBranch *secboot_tpm2.PCRProtectionProfileBranch, + loadChains []*LoadChain) { + if data.fileLeak { - params.LoadSequences[0].Image.Open() + loadChains[0].Open() } - for _, e := range params.LoadSequences { - f, err := e.Image.Open() + for _, e := range loadChains { + f, err := e.Open() c.Assert(err, check.IsNil) f.Close() for _, e := range e.Next { - f, err := e.Image.Open() + f, err := e.Open() c.Assert(err, check.IsNil) f.Close() } } - return nil - }) - defer restore() - - restore = s.mockSbefiAddSecureBootPolicyProfile(func(profile *secboot_tpm2.PCRProtectionProfile, params *secboot_efi.SecureBootPolicyProfileParams) error { - for _, e := range params.LoadSequences { - f, err := e.Image.Open() + for _, e := range loadChains { + f, err := e.Open() c.Assert(err, check.IsNil) f.Close() for _, e := range e.Next { - f, err := e.Image.Open() + f, err := e.Open() c.Assert(err, check.IsNil) f.Close() } } - return nil - }) - defer restore() + } + defer func() { introspectLoadChains = nil }() - restore = s.mockSbGetAuxiliaryKeyFromKernel(func(prefix, devicePath string, remove bool) (secboot.AuxiliaryKey, error) { + restore = s.mockSbGetPrimaryKeyFromKernel(func(prefix, devicePath string, remove bool) (secboot.PrimaryKey, error) { if data.noAuxKey { return nil, secboot.ErrKernelKeyNotFound } @@ -667,7 +640,7 @@ func (s *resealSuite) testResealKeyUnhappy(c *check.C, data *testResealKeyUnhapp }) defer restore() - restore = s.mockSbtpmSealedKeyObjectUpdatePCRProtectionPolicy(func(k *secboot_tpm2.SealedKeyObject, tpm *secboot_tpm2.Connection, authKey secboot_tpm2.PolicyAuthKey, profile *secboot_tpm2.PCRProtectionProfile) error { + restore = s.mockSbtpmSealedKeyObjectUpdatePCRProtectionPolicy(func(k *secboot_tpm2.SealedKeyObject, tpm *secboot_tpm2.Connection, authKey secboot.PrimaryKey, profile *secboot_tpm2.PCRProtectionProfile) error { return nil }) defer restore() @@ -701,7 +674,7 @@ func (s *resealSuite) testResealKeyUnhappy(c *check.C, data *testResealKeyUnhapp return ResealKey(assets, km, "/boot/efi", "/usr/lib/nullboot/shim", "ubuntu") } -func (s *resealSuite) TestResealKeyUnhappyNoAuxiliaryKey(c *check.C) { +func (s *resealSuite) TestResealKeyUnhappyNoPrimaryKey(c *check.C) { err := s.testResealKeyUnhappy(c, &testResealKeyUnhappyData{ noAuxKey: true, }) @@ -729,93 +702,94 @@ func (s *resealSuite) TestResealKeyUnhappyNoTPM(c *check.C) { c.Check(err, check.ErrorMatches, "no TPM2 device is available") } -// The TCG log writing code is borrowed from github.com:snapcore/secboot tools/make-efi-testdata/logs.go -// to avoid checking in a binary log +// The TCG log writing code is borrowed from github.com:canonical/secboot to avoid checking in a binary log -type event struct { - PCRIndex tcglog.PCRIndex - EventType tcglog.EventType - Data tcglog.EventData -} - -type eventData interface { +type logHashData interface { Write(w io.Writer) error } -type bytesData []byte +type bytesHashData []byte -func (d bytesData) Write(w io.Writer) error { +func (d bytesHashData) Write(w io.Writer) error { _, err := w.Write(d) return err } -type logWriter struct { - algs []tpm2.HashAlgorithmId - events []*tcglog.Event +type logEvent struct { + pcrIndex tpm2.Handle + eventType tcglog.EventType + data tcglog.EventData } -func newCryptoAgileLogWriter() *logWriter { - event := &tcglog.Event{ - PCRIndex: 0, - EventType: tcglog.EventTypeNoAction, - Digests: tcglog.DigestMap{tpm2.HashAlgorithmSHA1: make(tcglog.Digest, tpm2.HashAlgorithmSHA1.Size())}, - Data: &tcglog.SpecIdEvent03{ - SpecVersionMajor: 2, - UintnSize: 2, - DigestSizes: []tcglog.EFISpecIdEventAlgorithmSize{ - {AlgorithmId: tpm2.HashAlgorithmSHA1, DigestSize: uint16(tpm2.HashAlgorithmSHA1.Size())}, - {AlgorithmId: tpm2.HashAlgorithmSHA256, DigestSize: uint16(tpm2.HashAlgorithmSHA256.Size())}}}} - - return &logWriter{ - algs: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA1, tpm2.HashAlgorithmSHA256}, - events: []*tcglog.Event{event}} +type logBuilder struct { + algs []tpm2.HashAlgorithmId + events []*tcglog.Event } -func (w *logWriter) hashLogExtendEvent(data eventData, event *event) { +func (b *logBuilder) hashLogExtendEvent(c *check.C, data logHashData, event *logEvent) { ev := &tcglog.Event{ - PCRIndex: event.PCRIndex, - EventType: event.EventType, + PCRIndex: event.pcrIndex, + EventType: event.eventType, Digests: make(tcglog.DigestMap), - Data: event.Data} + Data: event.data} - for _, alg := range w.algs { + for _, alg := range b.algs { h := alg.NewHash() - if err := data.Write(h); err != nil { - panic(err) - } + c.Assert(data.Write(h), check.IsNil) ev.Digests[alg] = h.Sum(nil) } - w.events = append(w.events, ev) - + b.events = append(b.events, ev) } func (s *resealSuite) writeMockTcglog(c *check.C) { - w := newCryptoAgileLogWriter() + builder := &logBuilder{algs: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA1, tpm2.HashAlgorithmSHA256}} + + var digestSizes []tcglog.EFISpecIdEventAlgorithmSize + for _, alg := range builder.algs { + digestSizes = append(digestSizes, + tcglog.EFISpecIdEventAlgorithmSize{ + AlgorithmId: alg, + DigestSize: uint16(alg.Size()), + }) + } + + builder.events = []*tcglog.Event{ + { + PCRIndex: 0, + EventType: tcglog.EventTypeNoAction, + Digests: tcglog.DigestMap{tpm2.HashAlgorithmSHA1: make(tpm2.Digest, tpm2.HashAlgorithmSHA1.Size())}, + Data: &tcglog.SpecIdEvent03{ + SpecVersionMajor: 2, + UintnSize: 2, + DigestSizes: digestSizes, + }, + }, + } { data := &tcglog.SeparatorEventData{Value: tcglog.SeparatorEventNormalValue} - w.hashLogExtendEvent(data, &event{ - PCRIndex: 7, - EventType: tcglog.EventTypeSeparator, - Data: data}) + builder.hashLogExtendEvent(c, data, &logEvent{ + pcrIndex: 7, + eventType: tcglog.EventTypeSeparator, + data: data}) } { data := tcglog.EFICallingEFIApplicationEvent - w.hashLogExtendEvent(data, &event{ - PCRIndex: 4, - EventType: tcglog.EventTypeEFIAction, - Data: data}) + builder.hashLogExtendEvent(c, data, &logEvent{ + pcrIndex: 4, + eventType: tcglog.EventTypeEFIAction, + data: data}) } - for _, pcr := range []tcglog.PCRIndex{0, 1, 2, 3, 4, 5, 6} { + for _, pcr := range []tpm2.Handle{0, 1, 2, 3, 4, 5, 6} { data := &tcglog.SeparatorEventData{Value: tcglog.SeparatorEventNormalValue} - w.hashLogExtendEvent(data, &event{ - PCRIndex: pcr, - EventType: tcglog.EventTypeSeparator, - Data: data}) + builder.hashLogExtendEvent(c, data, &logEvent{ + pcrIndex: pcr, + eventType: tcglog.EventTypeSeparator, + data: data}) } { - pe := bytesData("mock shim PE") + pe := bytesHashData("mock shim PE") data := &tcglog.EFIImageLoadEvent{ LocationInMemory: 0x6556c018, LengthInMemory: 955072, @@ -831,7 +805,7 @@ func (s *resealSuite) writeMockTcglog(c *check.C) { Device: 0x0}, &efi.NVMENamespaceDevicePathNode{ NamespaceID: 0x1, - NamespaceUUID: 0x0}, + NamespaceUUID: efi.EUI64{}}, &efi.HardDriveDevicePathNode{ PartitionNumber: 1, PartitionStart: 0x800, @@ -839,26 +813,26 @@ func (s *resealSuite) writeMockTcglog(c *check.C) { Signature: efi.GUIDHardDriveSignature(efi.MakeGUID(0x66de947b, 0xfdb2, 0x4525, 0xb752, [...]uint8{0x30, 0xd6, 0x6b, 0xb2, 0xb9, 0x60})), MBRType: efi.GPT}, efi.FilePathDevicePathNode("\\EFI\\ubuntu\\shimx64.efi")}} - w.hashLogExtendEvent(pe, &event{ - PCRIndex: 4, - EventType: tcglog.EventTypeEFIBootServicesApplication, - Data: data}) + builder.hashLogExtendEvent(c, pe, &logEvent{ + pcrIndex: 4, + eventType: tcglog.EventTypeEFIBootServicesApplication, + data: data}) } { - pe := bytesData("mock kernel PE") + pe := bytesHashData("mock kernel PE") data := &tcglog.EFIImageLoadEvent{ DevicePath: efi.DevicePath{efi.FilePathDevicePathNode("\\EFI\\ubuntu\\kernel.efi-1.0-1-generic")}} - w.hashLogExtendEvent(pe, &event{ - PCRIndex: 4, - EventType: tcglog.EventTypeEFIBootServicesApplication, - Data: data}) + builder.hashLogExtendEvent(c, pe, &logEvent{ + pcrIndex: 4, + eventType: tcglog.EventTypeEFIBootServicesApplication, + data: data}) } f, err := s.fs.OpenFile("/sys/kernel/security/tpm0/binary_bios_measurements", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) c.Assert(err, check.IsNil) defer f.Close() - c.Check(tcglog.WriteLog(f, w.events), check.IsNil) + c.Check(tcglog.NewLogForTesting(builder.events).Write(f), check.IsNil) } func (s *resealSuite) mockEfiComputePeImageDigest(fn func(alg crypto.Hash, r io.ReaderAt, sz int64) ([]byte, error)) (restore func()) { diff --git a/go.mod b/go.mod index d5084da..729b5bc 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,11 @@ module github.com/canonical/nullboot go 1.24.0 require ( - github.com/canonical/go-efilib v0.3.1-0.20220815143333-7e5151412e93 - github.com/canonical/go-tpm2 v0.1.0 - github.com/canonical/tcglog-parser v0.0.0-20220314144800-471071956aa1 + github.com/canonical/go-efilib v1.7.1-0.20260310185303-7166aa858b24 + github.com/canonical/go-tpm2 v1.15.0 + github.com/canonical/tcglog-parser v0.0.0-20240924110432-d15eaf652981 github.com/knqyf263/go-deb-version v0.0.0-20230223133812-3ed183d23422 - github.com/snapcore/secboot v0.0.0-20240411101434-f3ad7c92552a + github.com/snapcore/secboot v0.0.0-20260410084611-3f8b98c2db70 github.com/spf13/afero v1.15.0 golang.org/x/sys v0.38.0 golang.org/x/text v0.34.0 @@ -15,19 +15,21 @@ require ( ) require ( - github.com/canonical/go-sp800.108-kdf v0.0.0-20210315104021-ead800bbf9a0 // indirect + github.com/canonical/cpuid v0.0.0-20220614022739-219e067757cb // indirect + github.com/canonical/go-kbkdf v0.0.0-20250104172618-3b1308f9acf9 // indirect + github.com/canonical/go-password-validator v0.0.0-20250617132709-1b205303ca54 // indirect github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3 // indirect github.com/godbus/dbus v4.1.0+incompatible // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect + github.com/pilebones/go-udev v0.9.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/snapcore/go-gettext v0.0.0-20201130093759-38740d1bd3d2 // indirect github.com/snapcore/snapd v0.0.0-20240321202327-b749eda44d9f // indirect - go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect golang.org/x/crypto v0.45.0 // indirect + golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - gopkg.in/retry.v1 v1.0.3 // indirect gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect maze.io/x/crypto v0.0.0-20190131090603-9b94c9afe066 // indirect diff --git a/go.sum b/go.sum index 2ce877d..2cc6043 100644 --- a/go.sum +++ b/go.sum @@ -1,31 +1,24 @@ -github.com/bsiegert/ranges v0.0.0-20111221115336-19303dc7aa63/go.mod h1:8z71/aZjDHLs4ihK/5nD5wZVQxm/W4eRDnxQZcJmVD4= -github.com/canonical/go-efilib v0.3.1-0.20220314143719-95d50e8afc82/go.mod h1:9b2PNAuPcZsB76x75/uwH99D8CyH/A2y4rq1/+bvplg= -github.com/canonical/go-efilib v0.3.1-0.20220815143333-7e5151412e93 h1:F0bRDzPy/j2IX/iIWqCEA23S1nal+f7A+/vLyj6Ye+4= -github.com/canonical/go-efilib v0.3.1-0.20220815143333-7e5151412e93/go.mod h1:9b2PNAuPcZsB76x75/uwH99D8CyH/A2y4rq1/+bvplg= -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/cpuid v0.0.0-20220614022739-219e067757cb h1:+kA/9oHTqUx4P08ywKvmd7a1wOL3RLTrE0K958C15x8= +github.com/canonical/cpuid v0.0.0-20220614022739-219e067757cb/go.mod h1:6j8Sw3dwYVcBXltEeGklDoK/8UJVJNQPUkg1ZdQUgbk= +github.com/canonical/go-efilib v1.7.1-0.20260310185303-7166aa858b24 h1:WCrkrG2hJuPQXt+mIrCyYWY+hsXO9y/R9i/EOrt/T0M= +github.com/canonical/go-efilib v1.7.1-0.20260310185303-7166aa858b24/go.mod h1:n0Ttsy1JuHAvqaFbZBs6PAzoiiJdfkHsAmDOEbexYEQ= +github.com/canonical/go-kbkdf v0.0.0-20250104172618-3b1308f9acf9 h1:Twk1ZSTWRClfGShP16ePf2JIiayqWS4ix1rkAR6baag= +github.com/canonical/go-kbkdf v0.0.0-20250104172618-3b1308f9acf9/go.mod h1:IneQ5/yQcfPXrGekEXpR6yeea55ZD24N5+kHzeDseOM= +github.com/canonical/go-password-validator v0.0.0-20250617132709-1b205303ca54 h1:JO3wAsxjrvQDf/X3q4RLIdzDCWrFjzhwUmCKrhnrIO8= +github.com/canonical/go-password-validator v0.0.0-20250617132709-1b205303ca54/go.mod h1:Vy3kTKlJTJ7gav1xGV9Bek08cUsh90hK7pK7mY34GnU= 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.1.0 h1:2mXU+Hy+zYSxmuYys2NtPEO6NwT3Qr9Sygwtops7NYk= -github.com/canonical/go-tpm2 v0.1.0/go.mod h1:vG41hdbBjV4+/fkubTT1ENBBqSkLwLr7mCeW9Y6kpZY= -github.com/canonical/tcglog-parser v0.0.0-20220314144800-471071956aa1 h1:JSg9RHT1AMIFIiYEM7hMUe3oI+hhnLpjcFwG+hcOUqE= -github.com/canonical/tcglog-parser v0.0.0-20220314144800-471071956aa1/go.mod h1:AoJVV7tUwDDGPZkKqwqAMGdPiH7x45JLNmxFrxfoxcs= -github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/canonical/go-tpm2 v1.15.0 h1:T4dVCO8qCs76vDDs4vWNpvPdh7UHuSORPH4Scq7N2gw= +github.com/canonical/go-tpm2 v1.15.0/go.mod h1:P50xMwC7y5/uxPikzWdK4d9pW9orKi8+ZL5sBifxoBQ= +github.com/canonical/tcglog-parser v0.0.0-20240924110432-d15eaf652981 h1:vrUzSfbhl8mzdXPzjxq4jXZPCCNLv18jy6S7aVTS2tI= +github.com/canonical/tcglog-parser v0.0.0-20240924110432-d15eaf652981/go.mod h1:ywdPBqUGkuuiitPpVWCfilf2/gq+frhq4CNiNs9KyHU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -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 v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4= github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -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.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/juju/ratelimit v1.0.1 h1:+7AIFJVQ0EQgq/K9+0Krm7m530Du7tIz0METWzN0RgY= github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= github.com/knqyf263/go-deb-version v0.0.0-20230223133812-3ed183d23422 h1:PPPlUUqPP6fLudIK4n0l0VU4KT2cQGnheW9x8pNiCHI= github.com/knqyf263/go-deb-version v0.0.0-20230223133812-3ed183d23422/go.mod h1:ijAmSS4jErO6+KRzcK6ixsm3Vt96hMhJ+W+x+VmbrQA= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -33,54 +26,36 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mvo5/goconfigparser v0.0.0-20201015074339-50f22f44deb5 h1:IUtr2a2HkY+0BPb4bz7t7+p26kmp366dLSkuAodXE10= -github.com/mvo5/goconfigparser v0.0.0-20201015074339-50f22f44deb5/go.mod h1:xmt4k1xLDl8Tdan+0S/jmMK2uSUBSzTc18+5GN5Vea8= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +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/pilebones/go-udev v0.9.0 h1:N1uEO/SxUwtIctc0WLU0t69JeBxIYEYnj8lT/Nabl9Q= +github.com/pilebones/go-udev v0.9.0/go.mod h1:T2eI2tUSK0hA2WS5QLjXJUfQkluZQu+18Cqvem3CaXI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/rogpeppe/clock v0.0.0-20190514195947-2896927a307a h1:3QH7VyOaaiUHNrA9Se4YQIRkDTCw1EJls9xTUCaCeRM= -github.com/rogpeppe/clock v0.0.0-20190514195947-2896927a307a/go.mod h1:4r5QyqhjIWCcK8DO4KMclc5Iknq5qVBAlbYYzAbUScQ= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/snapcore/bolt v1.3.2-0.20210908134111-63c8bfcf7af8 h1:WmyDfH38e3MaMWrMCO5YpW96BANq5Ti2iwbliM/xTW0= github.com/snapcore/bolt v1.3.2-0.20210908134111-63c8bfcf7af8/go.mod h1:Z6z3sf12AMDjT/4tbT/PmzzdACAxkWGhkuKWiVpTWLM= -github.com/snapcore/go-gettext v0.0.0-20191107141714-82bbea49e785/go.mod h1:D3SsWAXK7wCCBZu+Vk5hc1EuKj/L3XN1puEMXTU4LrQ= github.com/snapcore/go-gettext v0.0.0-20201130093759-38740d1bd3d2 h1:nETXPg0CiJrMAwC2gqkcam9BiBWYGvTsSYRfrjOz2Kg= github.com/snapcore/go-gettext v0.0.0-20201130093759-38740d1bd3d2/go.mod h1:D3SsWAXK7wCCBZu+Vk5hc1EuKj/L3XN1puEMXTU4LrQ= -github.com/snapcore/secboot v0.0.0-20211207204151-239d06c34009/go.mod h1:72paVOkm4sJugXt+v9ItmnjXgO921D8xqsbH2OekouY= -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/snapd v0.0.0-20201005140838-501d14ac146e/go.mod h1:3xrn7QDDKymcE5VO2rgWEQ5ZAUGb9htfwlXnoel6Io8= +github.com/snapcore/secboot v0.0.0-20260410084611-3f8b98c2db70 h1:EewX3F1DSpJz8wPZxFxC3MdOnFiFVAazbOIv1OQNDME= +github.com/snapcore/secboot v0.0.0-20260410084611-3f8b98c2db70/go.mod h1:+qs2Juv0XZeTmQHJgFTtAd9520h7QUWRDyvSwbJ3xEU= github.com/snapcore/snapd v0.0.0-20240321202327-b749eda44d9f h1:ck/E3kM2clbfd0khPd4MR7aXO3ZvKHzim4+Z3uKPVsY= github.com/snapcore/snapd v0.0.0-20240321202327-b749eda44d9f/go.mod h1:fNxLckWb4/vy0xM4i7edcRh0+PlqoEL59Pyk6QcG7us= -github.com/snapcore/squashfuse v0.0.0-20171220165323-319f6d41a041/go.mod h1:8loYitFPSdoeCXBs/XjO0fyGcpgLAybOHLUsGwgMq90= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= -go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= -go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 h1:CCriYyAfq1Br1aIYettdHZTy8mBTIPo7We18TuO/bak= -go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352/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.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -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/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.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -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.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= @@ -93,7 +68,6 @@ gopkg.in/retry.v1 v1.0.3 h1:a9CArYczAVv6Qs6VGoLMio99GEs7kY9UzSF9+LD+iGs= gopkg.in/retry.v1 v1.0.3/go.mod h1:FJkXmWiMaAo7xB+xhvDF59zhfjDWyzmyAxiT4dB688g= gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 h1:yiW+nvdHb9LVqSHQBXfZCieqV4fzYhNBql77zY0ykqs= gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= maze.io/x/crypto v0.0.0-20190131090603-9b94c9afe066 h1:UrD21H1Ue5Nl8f2x/NQJBRdc49YGmla3mRStinH8CCE= diff --git a/luks2/luks2.go b/luks2/luks2.go new file mode 100644 index 0000000..3dca872 --- /dev/null +++ b/luks2/luks2.go @@ -0,0 +1,68 @@ +// This file is part of nullboot +// Copyright 2026 Canonical Ltd. +// SPDX-License-Identifier: GPL-3.0-only + +package luks2 + +import ( + "crypto/rand" + "fmt" + efi "github.com/canonical/go-efilib" + "github.com/snapcore/secboot" + _ "github.com/snapcore/secboot/luks2" // This gets the LUKS2 backend initialized +) + +func CreateRecoveryKey(devicePath string, recoveryName string) error { + fmt.Printf("Creating recovery key '%v' in '%v'\n", recoveryName, devicePath) + container, err := secboot.FindStorageContainer(efi.DefaultVarContext, devicePath) + if err != nil { + fmt.Printf("Cannot find storage (LUKS) container: %v\n", err) + return err + } + purpose := secboot.KeyringKeyPurposeUnlock + // prefix "" defaults to "ubuntu-fde" + diskUnlockKey, err := secboot.GetKeyFromKernel(efi.DefaultVarContext, container, purpose, "") + if err != nil { + fmt.Printf("Cannot get disk unlock key from kernel: %v\n", err) + return err + } + + recoveryKey := secboot.RecoveryKey{} // generate random from crypto rand package + rand.Read(recoveryKey[:]) + + keyslotName := recoveryName // anything, stored in the token + err = secboot.AddLUKS2ContainerRecoveryKey(devicePath, keyslotName, diskUnlockKey, recoveryKey) + // soon to be replaced. (package secboot.luks2) + if err != nil { + fmt.Printf("Cannot add recovery key to LUKS container: %v\n", err) + return err + } + + fmt.Printf("%s\n", recoveryKey.String()) + + return nil +} + +func ListRecoveryKeys(devicePath string) error { + fmt.Printf("Listing recovery keys in '%v'\n", devicePath) + recovery_names, err := secboot.ListLUKS2ContainerRecoveryKeyNames(devicePath) + if err != nil { + fmt.Printf("Cannot list recovery keys: %v\n", err) + return err + } + + for _, name := range recovery_names { + fmt.Printf("%v\n", name) + } + return nil +} + +func DeleteRecoveryKey(devicePath string, recoveryName string) error { + fmt.Printf("Deleting recovery key '%v' in '%v'\n", recoveryName, devicePath) + err := secboot.DeleteLUKS2ContainerKey(devicePath, recoveryName) + if err != nil { + fmt.Printf("Cannot delete recovery key: %v\n", err) + return err + } + return nil +}