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) }