Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
repos:
- repo: https://github.com/dnephin/pre-commit-golang
rev: v0.5.1
- repo: https://github.com/golangci/golangci-lint
rev: v2.12.2
hooks:
- id: go-fmt
- id: go-vet
- id: validate-toml
- id: no-go-testing
- id: go-unit-tests
- id: go-build
- id: go-mod-tidy
- id: golangci-lint
args: [--new-from-rev=HEAD]
24 changes: 12 additions & 12 deletions efibootmgr/efivars.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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{}
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 {
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions efibootmgr/efivars_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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")
}

Expand Down
160 changes: 107 additions & 53 deletions efibootmgr/reseal.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -35,9 +34,7 @@ const (

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
Expand All @@ -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
Expand Down Expand Up @@ -116,7 +109,7 @@ func resolveLink(path string) (string, error) {
}
}

func getPolicyAuthKeyFromKernel() (secboot_tpm2.PolicyAuthKey, error) {
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)
Expand All @@ -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
Expand All @@ -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
}
}
Expand All @@ -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.

Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}
Expand Down
Loading
Loading