Files
sbctl/keys.go
Morten Linderud 935d7f5011 keys: ensure we compare the input/output files
We only used the path for this instead of comparing the authenticode
hashes.

Signed-off-by: Morten Linderud <morten@linderud.pw>
2024-08-16 22:16:29 +02:00

161 lines
3.7 KiB
Go

package sbctl
import (
"bytes"
"crypto"
"errors"
"fmt"
"os"
"path/filepath"
"github.com/foxboron/go-uefi/authenticode"
"github.com/foxboron/go-uefi/efi"
"github.com/foxboron/sbctl/backend"
"github.com/foxboron/sbctl/certs"
"github.com/foxboron/sbctl/config"
"github.com/foxboron/sbctl/fs"
"github.com/foxboron/sbctl/hierarchy"
"github.com/spf13/afero"
)
func EnrollCustom(customBytes []byte, efivar string) error {
return efi.WriteEFIVariable(efivar, customBytes)
}
func VerifyFile(state *config.State, kh *backend.KeyHierarchy, ev hierarchy.Hierarchy, file string) (bool, error) {
peFile, err := state.Fs.Open(file)
if err != nil {
return false, err
}
defer peFile.Close()
return kh.VerifyFile(ev, peFile)
}
var ErrAlreadySigned = errors.New("already signed file")
func SignFile(state *config.State, kh *backend.KeyHierarchy, ev hierarchy.Hierarchy, file, output string) error {
// Check to see if input and output binary is the same
var same bool
// Make sure that output is always populated by atleast the file path
if output == "" {
output = file
}
// Check file exists before we do anything
if _, err := state.Fs.Stat(file); errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("%s does not exist", file)
}
// We want to write the file back with correct permissions
si, err := state.Fs.Stat(file)
if err != nil {
return fmt.Errorf("failed stat of file: %w", err)
}
peFile, err := state.Fs.Open(file)
if err != nil {
return err
}
defer peFile.Close()
inputBinary, err := authenticode.Parse(peFile)
if err != nil {
return err
}
// Check if the files are identical
if file != output {
if _, err := state.Fs.Stat(output); errors.Is(err, os.ErrExist) {
outputFile, err := state.Fs.Open(output)
if err != nil {
return err
}
defer outputFile.Close()
outputBinary, err := authenticode.Parse(outputFile)
if err != nil {
return err
}
b := outputBinary.Hash(crypto.SHA256)
bb := inputBinary.Hash(crypto.SHA256)
if bytes.Equal(b, bb) {
same = true
}
}
}
if file == output {
same = true
}
// Let's check if we have signed it already AND the original file hasn't changed
// TODO: This will run authenticode.Parse again, *and* open the file
// this should be refactored to be nicer
ok, err := VerifyFile(state, kh, ev, output)
if errors.Is(err, authenticode.ErrNoValidSignatures) {
// If we tried to verify the file, but it has signatures but nothing signed
// by our key, we catch the error and continue.
} else if errors.Is(err, os.ErrNotExist) {
// Ignore the error if the file doesn't exist
} else if ok && same {
// If already signed, and the input/output binaries are identical,
// we can just assume everything is fine.
return ErrAlreadySigned
} else if err != nil {
return err
}
b, err := kh.SignFile(ev, inputBinary)
if err != nil {
return err
}
if err = fs.WriteFile(state.Fs, output, b, si.Mode()); err != nil {
return err
}
return nil
}
// Map up our default keys in a struct
var SecureBootKeys = []struct {
Key string
Description string
}{
{
Key: "PK",
Description: "Platform Key",
},
{
Key: "KEK",
Description: "Key Exchange Key",
},
{
Key: "db",
Description: "Database Key",
},
// {
// Key: "dbx",
// Description: "Forbidden Database Key",
// },
}
// Check if we have already intialized keys in the given output directory
func CheckIfKeysInitialized(vfs afero.Fs, output string) bool {
for _, key := range SecureBootKeys {
path := filepath.Join(output, key.Key)
if _, err := vfs.Stat(path); errors.Is(err, os.ErrNotExist) {
return false
}
}
return true
}
func GetEnrolledVendorCerts() []string {
db, err := efi.Getdb()
if err != nil {
return []string{}
}
return certs.DetectVendorCerts(db)
}