From cca2a12a7cfd0b08ca353bdb4169af5d7b7c83b1 Mon Sep 17 00:00:00 2001 From: igo95862 Date: Sun, 30 May 2021 14:38:22 +0300 Subject: [PATCH 01/43] Improved GetEsp function. Now checks /efi,/boot and /boot/efi for gpt partition table, vfat filesystem and ESP partition GUID. --- sbctl.go | 68 +++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/sbctl.go b/sbctl.go index 7b32cfe..462d83a 100644 --- a/sbctl.go +++ b/sbctl.go @@ -2,6 +2,7 @@ package sbctl import ( "bytes" + "encoding/json" "errors" "io" "log" @@ -15,28 +16,69 @@ import ( // Functions that doesn't fit anywhere else -// Veryvery simple check +type LsblkEntry struct { + Parttype string `json:"parttype"` + Mountpoint string `json:"mountpoint"` + Pttype string `json:"pttype"` + Fstype string `json:"fstype"` +} + +type LsblkRoot struct { + Blockdevices []LsblkEntry `json:"blockdevices"` +} + +// Slightly more advanced check func GetESP() string { - if _, err := os.Stat("/efi"); !os.IsNotExist(err) { - return "/efi" - } - out, err := exec.Command("lsblk", "-o", "PARTTYPE,MOUNTPOINT").Output() + + out, err := exec.Command( + "lsblk", + "--json", + "--output", "PARTTYPE,MOUNTPOINT,PTTYPE,FSTYPE").Output() if err != nil { - log.Fatal(err) + log.Panic(err) + } + + var lsblkRoot LsblkRoot + json.Unmarshal(out, &lsblkRoot) + + var pathBootEntry *LsblkEntry + var pathBootEfiEntry *LsblkEntry + var pathEfiEntry *LsblkEntry + + for _, lsblkEntry := range lsblkRoot.Blockdevices { + switch lsblkEntry.Mountpoint { + case "/boot": + pathBootEntry = new(LsblkEntry) + *pathBootEntry = lsblkEntry + case "/boot/efi": + pathBootEfiEntry = new(LsblkEntry) + *pathBootEfiEntry = lsblkEntry + case "/efi": + pathEfiEntry = new(LsblkEntry) + *pathEfiEntry = lsblkEntry + } } - data := string(out) - for _, lines := range strings.Split(data, "\n") { - if len(lines) < 1 { + + for _, entryToCheck := range []*LsblkEntry{pathEfiEntry, pathBootEntry, pathBootEfiEntry} { + if entryToCheck == nil { continue } - l := strings.Split(lines, " ") - if len(l) != 2 { + + if entryToCheck.Pttype != "gpt" { + continue + } + + if entryToCheck.Fstype != "vfat" { continue } - if l[0] == "c12a7328-f81f-11d2-ba4b-00a0c93ec93b" { - return l[1] + + if entryToCheck.Parttype != "c12a7328-f81f-11d2-ba4b-00a0c93ec93b" { + continue } + + return entryToCheck.Mountpoint } + return "" } From db8bbe28269fc4f92fb69c3cbae8e288864cc657 Mon Sep 17 00:00:00 2001 From: igo95862 Date: Sun, 30 May 2021 14:55:04 +0300 Subject: [PATCH 02/43] Add SYSTEMD_ESP_PATH and ESP_PATH environment variables support --- sbctl.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sbctl.go b/sbctl.go index 462d83a..07bbcb1 100644 --- a/sbctl.go +++ b/sbctl.go @@ -30,6 +30,13 @@ type LsblkRoot struct { // Slightly more advanced check func GetESP() string { + for _, env := range []string{"SYSTEMD_ESP_PATH", "ESP_PATH"} { + envEspPath, found := os.LookupEnv(env) + if found { + return envEspPath + } + } + out, err := exec.Command( "lsblk", "--json", From ba6dfc183e5a956ce44f9aa475a269c4ce4dad91 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Sun, 30 May 2021 14:15:02 +0200 Subject: [PATCH 03/43] sbctl/bundle: Do not default to ESP for fetching kernel and initramfs Most distros (I think) default to stuffing this into `/boot` so our ESP selection is going to mess this up more often then not. Signed-off-by: Morten Linderud --- bundles.go | 4 ++-- cmd/sbctl/main.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bundles.go b/bundles.go index dd1ca97..781dcb3 100644 --- a/bundles.go +++ b/bundles.go @@ -73,8 +73,8 @@ func NewBundle() *Bundle { Output: "", IntelMicrocode: "", AMDMicrocode: "", - KernelImage: filepath.Join(esp, "vmlinuz-linux"), - Initramfs: filepath.Join(esp, "initramfs-linux.img"), + KernelImage: "/boot/vmlinuz-linux", + Initramfs: "/boot/initramfs-linux.img", Cmdline: "/etc/kernel/cmdline", Splash: "", OSRelease: "/usr/lib/os-release", diff --git a/cmd/sbctl/main.go b/cmd/sbctl/main.go index 215e85d..6cd8728 100755 --- a/cmd/sbctl/main.go +++ b/cmd/sbctl/main.go @@ -239,9 +239,9 @@ func bundleCmd() *cobra.Command { f.StringVarP(&splashImg, "splash-img", "l", "", "Boot splash image location") f.StringVarP(&osRelease, "os-release", "o", "/usr/lib/os-release", "OS Release file location") f.StringVarP(&efiStub, "efi-stub", "e", "/usr/lib/systemd/boot/efi/linuxx64.efi.stub", "EFI Stub location") - f.StringVarP(&kernelImg, "kernel-img", "k", filepath.Join(esp, "vmlinuz-linux"), "Kernel image location") + f.StringVarP(&kernelImg, "kernel-img", "k", "/boot/vmlinuz-linux", "Kernel image location") f.StringVarP(&cmdline, "cmdline", "c", "/etc/kernel/cmdline", "Cmdline location") - f.StringVarP(&initramfs, "initramfs", "f", filepath.Join(esp, "initramfs-linux.img"), "Initramfs location") + f.StringVarP(&initramfs, "initramfs", "f", "/boot/initramfs-linux.img", "Initramfs location") f.StringVarP(&espPath, "esp", "p", esp, "ESP location") f.BoolVarP(&save, "save", "s", false, "save bundle to the database") return cmd From 10ff8d2a65078bc310964bd55e1926d88d582b0d Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Sun, 30 May 2021 14:34:21 +0200 Subject: [PATCH 04/43] man: Mention environment variables for ESP location Signed-off-by: Morten Linderud --- docs/sbctl.8.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/sbctl.8.txt b/docs/sbctl.8.txt index 33385ae..4f97495 100644 --- a/docs/sbctl.8.txt +++ b/docs/sbctl.8.txt @@ -144,6 +144,16 @@ All commands that take path arguments convert them into absolute paths when saving them to the database. +Environment variables +--------------------- + +**SYSTEMD_ESP_PATH**, **ESP_PATH**:: + Defines the EFI system partition (ESP) location. This overrides the + behaviour from **sbctl** where we query for the correct partition with + **lsblk**. No checks are performed on this path and can be usefull for testing + purposes. + + Files ---- **/usr/share/secureboot**:: From 1b7f188e0f6907ac1c1a490a4fd8add8583a048c Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Sun, 16 May 2021 23:06:22 +0200 Subject: [PATCH 05/43] Added new print module Signed-off-by: Morten Linderud --- logging/logging.go | 87 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 logging/logging.go diff --git a/logging/logging.go b/logging/logging.go new file mode 100644 index 0000000..afe5f96 --- /dev/null +++ b/logging/logging.go @@ -0,0 +1,87 @@ +package logging + +import ( + "fmt" + "os" + + "github.com/fatih/color" +) + +var ( + OkSym = "✔" + ErrSym = "✘" + WarnSym = "‼" + UnkwnSym = "⁇" +) +var ( + OkSymText = "[+]" + ErrSymText = "[-]" + WarnSymText = "[!]" + UnkwnSymText = "[?]" +) + +var ( + ok string + err string + warn string + unkwn string +) + +var ( + on bool +) + +func PrintOn() { + on = true +} + +func PrintOff() { + on = false +} + +func PrintWithFile(f *os.File, msg string, a ...interface{}) { + if on { + fmt.Fprintf(f, msg, a...) + } +} + +func Print(msg string, a ...interface{}) { + PrintWithFile(os.Stdout, msg, a...) +} + +// Print ok string to stdout +func Ok(m string, a ...interface{}) { + Print(fmt.Sprintf("%s %s\n", ok, fmt.Sprintf(m, a...))) +} + +func Error(m string, a ...interface{}) { + Print(fmt.Sprintf("%s %s\n", err, fmt.Sprintf(m, a...))) +} + +func Unknown(m string, a ...interface{}) { + Print(fmt.Sprintf("%s %s\n", unkwn, fmt.Sprintf(m, a...))) +} + +func Warn(m string, a ...interface{}) { + Print(fmt.Sprintf("%s %s\n", warn, fmt.Sprintf(m, a...))) +} + +func Fatal(err error) { + m := color.New(color.FgRed, color.Bold).Sprintf("%s %s", UnkwnSym, err.Error()) + PrintWithFile(os.Stderr, m) +} + +func init() { + if ok := os.Getenv("EFIBOOTCTL_UNICODE"); ok == "0" { + OkSym = OkSymText + ErrSym = ErrSymText + WarnSym = WarnSymText + UnkwnSym = UnkwnSymText + } + + ok = color.New(color.FgGreen, color.Bold).Sprintf(OkSym) + err = color.New(color.FgRed, color.Bold).Sprintf(ErrSym) + warn = color.New(color.FgYellow, color.Bold).Sprintf(WarnSym) + unkwn = color.New(color.FgRed, color.Bold).Sprintf(UnkwnSym) + PrintOn() +} From 955c547743acb080d1b8ad5d739d220d3acf3abf Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Mon, 17 May 2021 00:16:37 +0200 Subject: [PATCH 06/43] Added more fidelity to the logging methods Signed-off-by: Morten Linderud --- logging/logging.go | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/logging/logging.go b/logging/logging.go index afe5f96..a4aeccf 100644 --- a/logging/logging.go +++ b/logging/logging.go @@ -49,26 +49,48 @@ func Print(msg string, a ...interface{}) { PrintWithFile(os.Stdout, msg, a...) } +func Println(msg string) { + PrintWithFile(os.Stdout, msg+"\n") +} + +func Okf(m string, a ...interface{}) string { + return fmt.Sprintf("%s %s\n", ok, fmt.Sprintf(m, a...)) +} + // Print ok string to stdout func Ok(m string, a ...interface{}) { - Print(fmt.Sprintf("%s %s\n", ok, fmt.Sprintf(m, a...))) + Print(Okf(m, a...)) +} + +func Errorf(m string, a ...interface{}) string { + return fmt.Sprintf("%s %s\n", err, fmt.Sprintf(m, a...)) } func Error(m string, a ...interface{}) { - Print(fmt.Sprintf("%s %s\n", err, fmt.Sprintf(m, a...))) + Print(Errorf(m, a...)) +} + +func Unknownf(m string, a ...interface{}) string { + return fmt.Sprintf("%s %s\n", unkwn, fmt.Sprintf(m, a...)) } func Unknown(m string, a ...interface{}) { - Print(fmt.Sprintf("%s %s\n", unkwn, fmt.Sprintf(m, a...))) + Print(Unknownf(m, a...)) } +func Warnf(m string, a ...interface{}) string { + return fmt.Sprintf("%s %s\n", warn, fmt.Sprintf(m, a...)) +} func Warn(m string, a ...interface{}) { - Print(fmt.Sprintf("%s %s\n", warn, fmt.Sprintf(m, a...))) + Print(Warnf(m, a...)) +} + +func Fatalf(m string, a ...interface{}) string { + return color.New(color.FgRed, color.Bold).Sprintf("%s %s\n", UnkwnSym, fmt.Sprintf(m, a...)) } func Fatal(err error) { - m := color.New(color.FgRed, color.Bold).Sprintf("%s %s", UnkwnSym, err.Error()) - PrintWithFile(os.Stderr, m) + PrintWithFile(os.Stderr, Fatalf(err.Error())) } func init() { From 0d249b25df013ec320429484a1ecc1d97dbf20fc Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Mon, 17 May 2021 00:45:22 +0200 Subject: [PATCH 07/43] Added bundle cli format Signed-off-by: Morten Linderud --- bundles.go | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/bundles.go b/bundles.go index 781dcb3..484e487 100644 --- a/bundles.go +++ b/bundles.go @@ -8,6 +8,9 @@ import ( "os" "os/exec" "path/filepath" + "strings" + + "github.com/foxboron/sbctl/logging" ) type Bundle struct { @@ -108,26 +111,34 @@ func GenerateBundle(bundle *Bundle) bool { return exitError.ExitCode() == 0 } } - msg.Printf("Wrote EFI bundle %s", bundle.Output) + logging.Print("Wrote EFI bundle %s\n", bundle.Output) return true } func FormatBundle(name string, bundle *Bundle) { - msg.Printf("Bundle: %s", name) + logging.Println(name) + logging.Print("\tSigned:\t\t") + if VerifyFile(DBCert, name) { + logging.Ok("Signed") + } else { + logging.Error("Not Signed") + } + esp := GetESP() + logging.Print("\tESP Location:\t%s\n", esp) + logging.Print("\tOutput:\t\t└─%s\n", strings.TrimPrefix(bundle.Output, esp)) + logging.Print("\tEFI Stub Image:\t └─%s\n", bundle.EFIStub) + if bundle.Splash != "" { + logging.Print("\tSplash Image:\t ├─%s\n", bundle.Splash) + } + logging.Print("\tCmdline:\t ├─%s\n", bundle.Cmdline) + logging.Print("\tOS Release:\t ├─%s\n", bundle.OSRelease) + logging.Print("\tKernel Image:\t ├─%s\n", bundle.KernelImage) + logging.Print("\tInitramfs Image: └─%s\n", bundle.Initramfs) if bundle.AMDMicrocode != "" { - msg2.Printf("AMD Microcode: %s", bundle.AMDMicrocode) + logging.Print("\tAMD Microcode: └─%s\n", bundle.AMDMicrocode) } if bundle.IntelMicrocode != "" { - msg2.Printf("Intel Microcode: %s", bundle.IntelMicrocode) - } - msg2.Printf("Kernel Image: %s", bundle.KernelImage) - msg2.Printf("Initramfs Image: %s", bundle.Initramfs) - msg2.Printf("Cmdline: %s", bundle.Cmdline) - msg2.Printf("OS Release: %s", bundle.OSRelease) - msg2.Printf("EFI Stub Image: %s", bundle.EFIStub) - msg2.Printf("ESP Location: %s", bundle.ESP) - if bundle.Splash != "" { - msg2.Printf("Splash Image: %s", bundle.Splash) + logging.Print("\tIntel Microcode: └─%s\n", bundle.IntelMicrocode) } - msg2.Printf("Output: %s", bundle.Output) + logging.Println("") } From 7a4defc0c11b8700bf0ba958d16c04b3fedb8516 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Mon, 17 May 2021 00:45:51 +0200 Subject: [PATCH 08/43] Added deps Signed-off-by: Morten Linderud --- go.mod | 2 ++ go.sum | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/go.mod b/go.mod index c236a89..db6d678 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,10 @@ module github.com/foxboron/sbctl go 1.15 require ( + github.com/fatih/color v1.11.0 // indirect github.com/foxboron/go-uefi v0.0.0-20210105211851-5faf8e43ee9b github.com/google/uuid v1.1.1 + github.com/pkg/errors v0.9.1 // indirect github.com/spf13/cobra v1.0.0 golang.org/x/sys v0.0.0-20201109165425-215b40eba54c ) diff --git a/go.sum b/go.sum index 27bdcf5..7c84549 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/fatih/color v1.11.0 h1:l4iX0RqNnx/pU7rY2DB/I+znuYY0K3x6Ywac6EIr0PA= +github.com/fatih/color v1.11.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/foxboron/go-uefi v0.0.0-20210105211851-5faf8e43ee9b h1:Wsc63VYJUbbGF/YKUK9+TjguRUIKN/a5SvhB/mG94oc= github.com/foxboron/go-uefi v0.0.0-20210105211851-5faf8e43ee9b/go.mod h1:lP2qQFTFX3752ZHhqwp0U+A0d6oRHZEBn06+mMssM/g= github.com/foxboron/pkcs7 v0.0.0-20200515184129-2907ba0539a4/go.mod h1:px0/6X5Ap1wlLCWQ1DmiBULqyLBpoiXpUm0Vce+ufSk= @@ -55,6 +57,10 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN 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/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -62,6 +68,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= @@ -118,6 +126,8 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201109165425-215b40eba54c h1:+B+zPA6081G5cEb2triOIJpcvSW4AYzmIyWAqMn2JAc= golang.org/x/sys v0.0.0-20201109165425-215b40eba54c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= From 431363f2854548340c7a29676a2df93755371d7d Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Mon, 17 May 2021 00:46:45 +0200 Subject: [PATCH 09/43] Added list-files new WIP for commands Signed-off-by: Morten Linderud --- cmd/sbctl/list-files.go | 40 ++++++++++++++++++++++++ cmd/sbctl/main.go | 67 +++++++++++++++++++++++++++++++++-------- sbctl.go | 21 +++++++++---- 3 files changed, 109 insertions(+), 19 deletions(-) create mode 100644 cmd/sbctl/list-files.go diff --git a/cmd/sbctl/list-files.go b/cmd/sbctl/list-files.go new file mode 100644 index 0000000..b5b6fc4 --- /dev/null +++ b/cmd/sbctl/list-files.go @@ -0,0 +1,40 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/foxboron/sbctl" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var listFilesCmd = &cobra.Command{ + Use: "list-files", + Short: "List enrolled files", + RunE: RunList, +} + +func ListJsonOut(v interface{}) error { + b, err := json.MarshalIndent(v, "", " ") + if err != nil { + return errors.Wrapf(err, "could not marshal json") + } + fmt.Fprintf(os.Stdout, string(b)) + return nil +} + +func RunList(_ *cobra.Command, args []string) error { + files, _ := sbctl.ListFiles() + if cmdOptions.JsonOutput { + ListJsonOut(files) + } + return nil +} + +func init() { + CliCommands = append(CliCommands, cliCommand{ + Cmd: listFilesCmd, + }) +} diff --git a/cmd/sbctl/main.go b/cmd/sbctl/main.go index 6cd8728..d81c352 100755 --- a/cmd/sbctl/main.go +++ b/cmd/sbctl/main.go @@ -1,17 +1,45 @@ package main import ( + "errors" "log" "os" "path/filepath" "github.com/foxboron/sbctl" + "github.com/foxboron/sbctl/logging" "github.com/spf13/cobra" ) -var rootCmd = &cobra.Command{ - Use: "sbctl", - Short: "Secure Boot key manager", +type CmdOptions struct { + JsonOutput bool +} + +type cliCommand struct { + Cmd *cobra.Command +} + +var ( + cmdOptions = CmdOptions{} + CliCommands = []cliCommand{} + ErrSilent = errors.New("SilentErr") + rootCmd = &cobra.Command{ + Use: "sbctl", + Short: "Secure Boot Key Manager", + SilenceUsage: true, + SilenceErrors: true, + } +) + +func baseFlags(cmd *cobra.Command) { + flags := cmd.Flags() + flags.BoolVar(&cmdOptions.JsonOutput, "json", false, "Output as json") + + cmd.PreRun = func(cmd *cobra.Command, args []string) { + if cmdOptions.JsonOutput { + logging.PrintOff() + } + } } func createKeysCmd() *cobra.Command { @@ -158,15 +186,15 @@ func verifyCmd() *cobra.Command { } } -func listFilesCmd() *cobra.Command { - return &cobra.Command{ - Use: "list-files", - Short: "List enrolled files", - Run: func(cmd *cobra.Command, args []string) { - sbctl.ListFiles() - }, - } -} +// func listFilesCmd() *cobra.Command { +// return &cobra.Command{ +// Use: "list-files", +// Short: "List enrolled files", +// Run: func(cmd *cobra.Command, args []string) { +// sbctl.ListFiles() +// }, +// } +// } func bundleCmd() *cobra.Command { var amducode string @@ -334,7 +362,6 @@ func main() { rootCmd.AddCommand(signAllCmd()) rootCmd.AddCommand(statusCmd()) rootCmd.AddCommand(verifyCmd()) - rootCmd.AddCommand(listFilesCmd()) rootCmd.AddCommand(bundleCmd()) rootCmd.AddCommand(generateBundlesCmd()) rootCmd.AddCommand(removeBundleCmd()) @@ -346,7 +373,21 @@ func main() { completionCmd.AddCommand(completionZshCmd()) completionCmd.AddCommand(completionFishCmd()) rootCmd.AddCommand(completionCmd) + + for _, cmd := range CliCommands { + baseFlags(cmd.Cmd) + rootCmd.AddCommand(cmd.Cmd) + } + + rootCmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error { + cmd.Println(err) + cmd.Println(cmd.UsageString()) + return ErrSilent + }) if err := rootCmd.Execute(); err != nil { + if !errors.Is(err, ErrSilent) { + logging.Fatal(err) + } os.Exit(1) } sbctl.ColorsOff() diff --git a/sbctl.go b/sbctl.go index 07bbcb1..475d159 100644 --- a/sbctl.go +++ b/sbctl.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "errors" + "fmt" "io" "log" "os" @@ -11,7 +12,8 @@ import ( "path/filepath" "strings" - "github.com/foxboron/go-uefi/efi/attributes" + "github.com/foxboron/go-uefi/efi" + "github.com/foxboron/sbctl/logging" ) // Functions that doesn't fit anywhere else @@ -207,18 +209,25 @@ func Sign(file, output string, enroll bool) error { return err } -func ListFiles() { +func ListFiles() (SigningEntries, error) { files, err := ReadFileDatabase(DBPath) if err != nil { - err2.Printf("Couldn't open database: %s", DBPath) - return + return nil, fmt.Errorf("couldn't open database %v: %w", DBPath, err) } for path, s := range files { - msg.Printf("File: %s", path) + logging.Println(path) if path != s.OutputFile { - msg2.Printf("Output: %s", s.OutputFile) + logging.Print("Output:\t\t%s\n", s.OutputFile) + } + logging.Print("Signed:\t\t") + if VerifyFile(DBCert, s.OutputFile) { + logging.Ok("Signed") + } else { + logging.Error("Not Signed") } + logging.Println("") } + return files, nil } func CheckStatus() { From 62d653d0f85075e6149fc0746376603072c8aa28 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Mon, 17 May 2021 00:47:02 +0200 Subject: [PATCH 10/43] Added status new format Signed-off-by: Morten Linderud --- cmd/sbctl/main.go | 5 +++-- sbctl.go | 34 +++++++++++++++++++--------------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/cmd/sbctl/main.go b/cmd/sbctl/main.go index d81c352..541bf97 100755 --- a/cmd/sbctl/main.go +++ b/cmd/sbctl/main.go @@ -167,8 +167,9 @@ func statusCmd() *cobra.Command { return &cobra.Command{ Use: "status", Short: "Show current boot status", - Run: func(cmd *cobra.Command, args []string) { - sbctl.CheckStatus() + RunE: func(cmd *cobra.Command, args []string) error { + _, err := sbctl.CheckStatus() + return err }, } } diff --git a/sbctl.go b/sbctl.go index 475d159..cfe32a3 100644 --- a/sbctl.go +++ b/sbctl.go @@ -230,25 +230,29 @@ func ListFiles() (SigningEntries, error) { return files, nil } -func CheckStatus() { +func CheckStatus() (map[string]bool, error) { + return nil, fmt.Errorf("system is not booted with UEFI!") + var ret map[string]bool if _, err := os.Stat("/sys/firmware/efi/efivars"); os.IsNotExist(err) { - warning.Println("System is not booted with UEFI!") - os.Exit(1) + return nil, fmt.Errorf("system is not booted with UEFI!") } - if sm, err := attributes.ReadEfivars("SetupMode"); err == nil { - if sm.Data[0] == 1 { - warning.Println("Setup Mode: Enabled") - } else { - msg.Println("Setup Mode: Disabled") - } + logging.Print("Setup Mode:\t") + if efi.GetSetupMode() { + logging.Error("Enabled") + ret["Setup Mode"] = true + } else { + logging.Ok("Disabled") + ret["Setup Mode"] = false } - if sb, err := attributes.ReadEfivars("SecureBoot"); err == nil { - if sb.Data[0] == 1 { - msg.Println("Secure Boot: Enabled") - } else { - warning.Println("Secure Boot: Disabled") - } + logging.Print("Secure Boot:\t") + if efi.GetSecureBoot() { + logging.Ok("Enabled") + ret["Secure Boot"] = true + } else { + logging.Error("Disabled") + ret["Secure Boot"] = false } + return ret, nil } func CreateKeys() { From 2a53d5200c9a7ed4e3cfb255db2e42bbf1ad05c0 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Mon, 17 May 2021 00:47:16 +0200 Subject: [PATCH 11/43] Added list-bundles setup Signed-off-by: Morten Linderud --- sbctl.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sbctl.go b/sbctl.go index cfe32a3..9e92ca7 100644 --- a/sbctl.go +++ b/sbctl.go @@ -386,13 +386,14 @@ func GenerateAllBundles(sign bool) error { return nil } -func ListBundles() { +func ListBundles() (Bundles, error) { bundles, err := ReadBundleDatabase(BundleDBPath) if err != nil { - err2.Printf("Couldn't open database: %s", BundleDBPath) - os.Exit(1) + return nil, fmt.Errorf("couldn't open database %s: %v", BundleDBPath, err) } + logging.Println("Enrolled bundles:\n") for key, bundle := range bundles { FormatBundle(key, bundle) } + return bundles, nil } From a05e6c8fb83290820a1b4a653ff362972e6dd42b Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Mon, 17 May 2021 00:59:47 +0200 Subject: [PATCH 12/43] Fixed commands with colors off Signed-off-by: Morten Linderud --- cmd/sbctl/main.go | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/cmd/sbctl/main.go b/cmd/sbctl/main.go index 541bf97..2d54d2a 100755 --- a/cmd/sbctl/main.go +++ b/cmd/sbctl/main.go @@ -357,17 +357,25 @@ func completionFishCmd() *cobra.Command { } func main() { - rootCmd.AddCommand(createKeysCmd()) - rootCmd.AddCommand(enrollKeysCmd()) - rootCmd.AddCommand(signCmd()) - rootCmd.AddCommand(signAllCmd()) - rootCmd.AddCommand(statusCmd()) - rootCmd.AddCommand(verifyCmd()) - rootCmd.AddCommand(bundleCmd()) - rootCmd.AddCommand(generateBundlesCmd()) - rootCmd.AddCommand(removeBundleCmd()) - rootCmd.AddCommand(listBundlesCmd()) - rootCmd.AddCommand(removeFileCmd()) + cmds := []*cobra.Command{ + createKeysCmd(), + enrollKeysCmd(), + signCmd(), + signAllCmd(), + statusCmd(), + verifyCmd(), + bundleCmd(), + generateBundlesCmd(), + removeBundleCmd(), + listBundlesCmd(), + removeFileCmd(), + } + for _, c := range cmds { + c.PostRun = func(c *cobra.Command, args []string) { + sbctl.ColorsOff() + } + rootCmd.AddCommand(c) + } completionCmd := &cobra.Command{Use: "completion"} completionCmd.AddCommand(completionBashCmd()) @@ -391,5 +399,4 @@ func main() { } os.Exit(1) } - sbctl.ColorsOff() } From bb78cf9c014b2221c06b1b63f0ca2e8dc408240b Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Mon, 17 May 2021 01:47:19 +0200 Subject: [PATCH 13/43] Remove previous logging, improve error bubling Signed-off-by: Morten Linderud --- keys.go | 46 +++++++++++++++++++----------------- log.go | 65 --------------------------------------------------- sbctl.go | 71 ++++++++++++++++++++++++++++---------------------------- util.go | 18 ++------------ 4 files changed, 63 insertions(+), 137 deletions(-) delete mode 100644 log.go diff --git a/keys.go b/keys.go index f187923..adcac94 100644 --- a/keys.go +++ b/keys.go @@ -16,6 +16,7 @@ import ( "strings" "time" + "github.com/foxboron/sbctl/logging" "github.com/google/uuid" "golang.org/x/sys/unix" ) @@ -97,7 +98,7 @@ func SaveKey(k []byte, path string) { } func KeyToSiglist(UUID []byte, input string) []byte { - msg.Printf("Create EFI signature list %s.esl...", input) + logging.Print("Create EFI signature list %s.esl...", input) out, err := exec.Command( "sbsiglist", "--owner", string(UUID), @@ -110,17 +111,17 @@ func KeyToSiglist(UUID []byte, input string) []byte { return out } -func SignEFIVariable(key, cert, varname, vardatafile, output string) []byte { - msg.Printf("Signing %s with %s...", vardatafile, key) +func SignEFIVariable(key, cert, varname, vardatafile, output string) ([]byte, error) { + logging.Print("Signing %s with %s...", vardatafile, key) out, err := exec.Command("sbvarsign", "--key", key, "--cert", cert, "--output", output, varname, vardatafile).Output() if err != nil { - log.Fatalf("Failed signing EFI variable: %s", err) + return nil, fmt.Errorf("failed signing EFI variable: %v", err) } - return out + return out, nil } func SBKeySync(dir string) bool { - msg.Printf("Syncing %s to EFI variables...", dir) + logging.Print("Syncing %s to EFI variables...", dir) cmd := exec.Command("sbkeysync", "--pk", "--verbose", "--keystore", dir) var out bytes.Buffer cmd.Stdout = &out @@ -158,27 +159,25 @@ func SignFile(key, cert, file, output, checksum string) error { // Check file exists before we do anything if _, err := os.Stat(file); os.IsNotExist(err) { - return PrintGenerateError(err2, "%s does not exist!", file) + return fmt.Errorf("%s does not exist!", file) } // Let's check if we have signed it already AND the original file hasn't changed if VerifyFile(cert, output) && ChecksumFile(file) == checksum { - msg.Printf("%s has been signed...", file) + logging.Print("have already signed %s\n", file) return nil } // Let's also check if we can access the key if err := unix.Access(key, unix.R_OK); err != nil { - err2.Printf("Couldn't access %s", key) - return err + return fmt.Errorf("couldn't access %s", key) } - msg2.Printf("Signing %s...", file) + logging.Ok("signing %s", file) _, err := exec.Command("sbsign", "--key", key, "--cert", cert, "--output", output, file).Output() if err != nil { - return PrintGenerateError(err2, "Failed signing file: %s", err) + return fmt.Errorf("Failed signing file: %w", err) } - return nil } @@ -229,29 +228,33 @@ func CreateUUID() []byte { return []byte(id.String()) } -func CreateGUID(output string) []byte { +func CreateGUID(output string) ([]byte, error) { var uuid []byte guidPath := filepath.Join(output, "GUID") if _, err := os.Stat(guidPath); os.IsNotExist(err) { uuid = CreateUUID() - msg2.Printf("Created UUID %s...", uuid) + logging.Print("Created UUID %s...", uuid) err := os.WriteFile(guidPath, uuid, 0600) if err != nil { - log.Fatal(err) + return nil, err } + logging.Ok("") } else { uuid, err = os.ReadFile(guidPath) if err != nil { - log.Fatal(err) + return nil, err } - msg2.Printf("Using UUID %s...", uuid) + logging.Print("Using UUID %s...", uuid) } - return uuid + return uuid, nil } -func InitializeSecureBootKeys(output string) { +func InitializeSecureBootKeys(output string) error { os.MkdirAll(output, os.ModePerm) - uuid := CreateGUID(output) + uuid, err := CreateGUID(output) + if err != nil { + return err + } // Create the directories we need and keys for _, key := range SecureBootKeys { path := filepath.Join(output, "keys", key.Key) @@ -267,4 +270,5 @@ func InitializeSecureBootKeys(output string) { signingCertificate := fmt.Sprintf("%s.pem", signingkeyPath) SignEFIVariable(signingKey, signingCertificate, key.Key, fmt.Sprintf("%s.der.esl", keyPath), fmt.Sprintf("%s.auth", keyPath)) } + return nil } diff --git a/log.go b/log.go deleted file mode 100644 index 6d58689..0000000 --- a/log.go +++ /dev/null @@ -1,65 +0,0 @@ -package sbctl - -import ( - "bytes" - "fmt" - "log" - "os" - "os/exec" - "strings" -) - -var ( - msg *log.Logger - msg2 *log.Logger - warning *log.Logger - warning2 *log.Logger - err1 *log.Logger - err2 *log.Logger -) - -var ( - red = GetColor("setaf 1") - green = GetColor("setaf 2") - yellow = GetColor("setaf 3") - blue = GetColor("setaf 4") - bold = GetColor("bold") - off = GetColor("sgr0") - - // I didn't bother figure out how we get this to the end of the log format - // So we just clear the terminal stuff at the start of each log line - prefix = off -) - -var ( - rootMsg = "It might be necessary to run this tool as root" -) - -func GetColor(args string) string { - out, _ := exec.Command("tput", strings.Split(args, " ")...).Output() - return string(bytes.TrimSuffix(out, []byte("\n"))) -} - -func ColorsOff() { - fmt.Print(off) -} - -func init() { - msgfmt := fmt.Sprintf("%s%s%s==>%s%s ", prefix, bold, green, off, bold) - msg = log.New(os.Stdout, msgfmt, 0) - - msg2fmt := fmt.Sprintf("%s%s%s ->%s%s ", prefix, bold, blue, off, bold) - msg2 = log.New(os.Stdout, msg2fmt, 0) - - warningfmt := fmt.Sprintf("%s%s%s==> WARNING:%s%s ", prefix, bold, yellow, off, bold) - warning = log.New(os.Stderr, warningfmt, 0) - - warning2fmt := fmt.Sprintf("%s%s%s -> WARNING:%s%s ", prefix, bold, yellow, off, bold) - warning2 = log.New(os.Stderr, warning2fmt, 0) - - errfmt := fmt.Sprintf("%s%s%s==> ERROR:%s%s ", prefix, bold, red, off, bold) - err1 = log.New(os.Stderr, errfmt, 0) - - err2fmt := fmt.Sprintf("%s%s%s -> ERROR:%s%s ", prefix, bold, red, off, bold) - err2 = log.New(os.Stderr, err2fmt, 0) -} diff --git a/sbctl.go b/sbctl.go index 9e92ca7..0d81b9c 100644 --- a/sbctl.go +++ b/sbctl.go @@ -95,11 +95,9 @@ func VerifyESP() error { espPath := GetESP() files, err := ReadFileDatabase(DBPath) if err != nil { - err1.Printf("Couldn't read file database: %s", err) return err - } else { - msg.Printf("Verifying file database and EFI images in %s...", espPath) } + logging.Print("Verifying file database and EFI images in %s...\n", espPath) // Cache files we have looked at. checked := make(map[string]bool) @@ -109,13 +107,13 @@ func VerifyESP() error { // Check output file exists before checking if it's signed if _, err := os.Open(file.OutputFile); errors.Is(err, os.ErrNotExist) { - err2.Printf("%s does not exist\n", file.OutputFile) + logging.Warn("%s does not exist", file.OutputFile) } else if errors.Is(err, os.ErrPermission) { - err2.Printf("%s permission denied. Can't read file\n", file.OutputFile) + logging.Warn("%s permission denied. Can't read file\n", file.OutputFile) } else if VerifyFile(DBCert, file.OutputFile) { - msg2.Printf("%s is signed\n", file.OutputFile) + logging.Ok("%s is signed", file.OutputFile) } else { - warning2.Printf("%s is not signed\n", file.OutputFile) + logging.Error("%s is not signed", file.OutputFile) } } @@ -147,9 +145,9 @@ func VerifyESP() error { } if VerifyFile(DBCert, path) { - msg2.Printf("%s is signed\n", path) + logging.Ok("%s is signed\n", path) } else { - warning2.Printf("%s is not signed\n", path) + logging.Error("%s is not signed\n", path) } return nil }) @@ -179,8 +177,7 @@ func Sign(file, output string, enroll bool) error { files, err := ReadFileDatabase(DBPath) if err != nil { - err2.Printf("Couldn't open database: %s", DBPath) - return err + return fmt.Errorf("Couldn't open database: %s", DBPath) } if entry, ok := files[file]; ok { err = SignFile(DBKey, DBCert, entry.File, entry.OutputFile, entry.Checksum) @@ -255,13 +252,17 @@ func CheckStatus() (map[string]bool, error) { return ret, nil } -func CreateKeys() { +func CreateKeys() error { if !CheckIfKeysInitialized(KeysPath) { - msg.Printf("Creating secure boot keys...") - InitializeSecureBootKeys(DatabasePath) + logging.Print("Creating secure boot keys...") + err := InitializeSecureBootKeys(DatabasePath) + if err != nil { + return fmt.Errorf("couldn't initialize secure boot: %w", err) + } } else { - msg.Printf("Secure boot keys has been created") + logging.Ok("Secure boot keys has already been created!") } + return nil } var efivarFSFiles = []string{ @@ -270,36 +271,34 @@ var efivarFSFiles = []string{ "/sys/firmware/efi/efivars/db-d719b2cb-3d3a-4596-a3bc-dad00e67656f", } -func SyncKeys() { +func SyncKeys() error { errImmuable := false for _, file := range efivarFSFiles { b, err := IsImmutable(file) if err != nil { - err1.Printf("Couldn't read file: %s\n", file) - os.Exit(1) + return fmt.Errorf("Couldn't read file: %s\n", file) } if b { - err1.Printf("File is immutable: %s\n", file) + logging.Warn("File is immutable: %s\n", file) errImmuable = true } } if errImmuable { - err1.Println("You need to chattr -i files in efivarfs") - os.Exit(1) + return fmt.Errorf("You need to chattr -i files in efivarfs") } synced := SBKeySync(KeysPath) if !synced { - err1.Println("Couldn't sync keys") - os.Exit(1) + return errors.New("Couldn't sync keys") } else { - msg.Println("Synced keys!") + logging.Ok("Synced keys!") } + return nil } func CombineFiles(microcode, initramfs string) (*os.File, error) { tmpFile, err := os.CreateTemp("/var/tmp", "initramfs-") if err != nil { - err1.Println("Cannot create temporary file", err) + return nil, err } one, _ := os.Open(microcode) @@ -310,12 +309,12 @@ func CombineFiles(microcode, initramfs string) (*os.File, error) { _, err = io.Copy(tmpFile, one) if err != nil { - return nil, PrintGenerateError(err2, "failed to append microcode file to output: %s", err) + return nil, fmt.Errorf("failed to append microcode file to output: %w", err) } _, err = io.Copy(tmpFile, two) if err != nil { - return nil, PrintGenerateError(err2, "failed to append initramfs file to output: %s", err) + return nil, fmt.Errorf("failed to append initramfs file to output: %w", err) } return tmpFile, nil @@ -342,20 +341,22 @@ func CreateBundle(bundle Bundle) error { bundle.Initramfs = tmpFile.Name() } - out := GenerateBundle(&bundle) + out, err := GenerateBundle(&bundle) + if err != nil { + logging.Warn(err.Error()) + } if !out { - return PrintGenerateError(err2, "failed to generate bundle %s!", bundle.Output) + return fmt.Errorf("failed to generate bundle %s!", bundle.Output) } return nil } func GenerateAllBundles(sign bool) error { - msg.Println("Generating EFI bundles....") + logging.Println("Generating EFI bundles....") bundles, err := ReadBundleDatabase(BundleDBPath) if err != nil { - err2.Printf("Couldn't open database: %s", BundleDBPath) - return err + return fmt.Errorf("Couldn't open database: %s", BundleDBPath) } out_create := true out_sign := true @@ -376,11 +377,11 @@ func GenerateAllBundles(sign bool) error { } if !out_create { - return PrintGenerateError(err1, "Error generating EFI bundles") + return errors.New("Error generating EFI bundles") } if !out_sign { - return PrintGenerateError(err1, "Error signing EFI bundles") + return errors.New("Error signing EFI bundles") } return nil @@ -389,7 +390,7 @@ func GenerateAllBundles(sign bool) error { func ListBundles() (Bundles, error) { bundles, err := ReadBundleDatabase(BundleDBPath) if err != nil { - return nil, fmt.Errorf("couldn't open database %s: %v", BundleDBPath, err) + return nil, fmt.Errorf("couldn't open database: %v", err) } logging.Println("Enrolled bundles:\n") for key, bundle := range bundles { diff --git a/util.go b/util.go index ab9cace..2591c56 100644 --- a/util.go +++ b/util.go @@ -4,18 +4,11 @@ import ( "crypto/sha256" "encoding/hex" "errors" - "fmt" "log" "os" "path/filepath" ) -func PrintGenerateError(logger *log.Logger, msg string, args ...interface{}) error { - msg = fmt.Sprintf(msg, args...) - logger.Println(msg) - return errors.New(msg) -} - func ChecksumFile(file string) string { hasher := sha256.New() s, err := os.ReadFile(file) @@ -37,17 +30,11 @@ func ReadOrCreateFile(filePath string) ([]byte, error) { // os.MkdirAll simply returns nil if the directory already exists fileDir := filepath.Dir(filePath) if err = os.MkdirAll(fileDir, os.ModePerm); err != nil { - if os.IsPermission(err) { - warning.Printf(rootMsg) - } return nil, err } file, err := os.Create(filePath) if err != nil { - if os.IsPermission(err) { - warning.Printf(rootMsg) - } return nil, err } file.Close() @@ -56,9 +43,8 @@ func ReadOrCreateFile(filePath string) ([]byte, error) { f = make([]byte, 0) } else { if os.IsPermission(err) { - warning.Printf(rootMsg) + return nil, err } - return nil, err } } @@ -75,7 +61,7 @@ func IsImmutable(file string) (bool, error) { } attr, err := GetAttr(f) if err != nil { - log.Fatal(err) + return false, err } if (attr & FS_IMMUTABLE_FL) != 0 { return false, nil From b82e17e2ed738fa90e8bb9d88ca60d0ceacf04a2 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Mon, 17 May 2021 01:47:33 +0200 Subject: [PATCH 14/43] Return errors when generating bundles Signed-off-by: Morten Linderud --- bundles.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bundles.go b/bundles.go index 484e487..aac11b9 100644 --- a/bundles.go +++ b/bundles.go @@ -86,7 +86,7 @@ func NewBundle() *Bundle { } } -func GenerateBundle(bundle *Bundle) bool { +func GenerateBundle(bundle *Bundle) (bool, error) { args := []string{ "--add-section", fmt.Sprintf(".osrel=%s", bundle.OSRelease), "--change-section-vma", ".osrel=0x20000", "--add-section", fmt.Sprintf(".cmdline=%s", bundle.Cmdline), "--change-section-vma", ".cmdline=0x30000", @@ -104,15 +104,14 @@ func GenerateBundle(bundle *Bundle) bool { cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { if errors.Is(err, exec.ErrNotFound) { - err2.Printf(err.Error()) - return false + return false, err } if exitError, ok := err.(*exec.ExitError); ok { - return exitError.ExitCode() == 0 + return exitError.ExitCode() == 0, nil } } logging.Print("Wrote EFI bundle %s\n", bundle.Output) - return true + return true, nil } func FormatBundle(name string, bundle *Bundle) { From 3568e9d34b49339568c343b730404ba1cf2b6d56 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Mon, 17 May 2021 01:47:52 +0200 Subject: [PATCH 15/43] sbctl: Buble up errors from the "library" Signed-off-by: Morten Linderud --- cmd/sbctl/main.go | 96 ++++++++++++++++++++++++----------------------- 1 file changed, 50 insertions(+), 46 deletions(-) diff --git a/cmd/sbctl/main.go b/cmd/sbctl/main.go index 2d54d2a..c902bfe 100755 --- a/cmd/sbctl/main.go +++ b/cmd/sbctl/main.go @@ -2,7 +2,6 @@ package main import ( "errors" - "log" "os" "path/filepath" @@ -46,8 +45,8 @@ func createKeysCmd() *cobra.Command { return &cobra.Command{ Use: "create-keys", Short: "Create a set of secure boot signing keys", - Run: func(cmd *cobra.Command, args []string) { - sbctl.CreateKeys() + RunE: func(cmd *cobra.Command, args []string) error { + return sbctl.CreateKeys() }, } } @@ -56,8 +55,8 @@ func enrollKeysCmd() *cobra.Command { return &cobra.Command{ Use: "enroll-keys", Short: "Enroll the current keys to EFI", - Run: func(cmd *cobra.Command, args []string) { - sbctl.SyncKeys() + RunE: func(cmd *cobra.Command, args []string) error { + return sbctl.SyncKeys() }, } } @@ -69,28 +68,30 @@ func signCmd() *cobra.Command { cmd := &cobra.Command{ Use: "sign", Short: "Sign a file with secure boot keys", - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { if len(args) < 1 { - log.Fatalf("Requires a file to sign...\n") + logging.Print("Requires a file to sign\n") + os.Exit(1) } // Ensure we have absolute paths file, err := filepath.Abs(args[0]) if err != nil { - log.Fatal(err) + return err } if output == "" { output = file } else { output, err = filepath.Abs(output) if err != nil { - log.Fatal(err) + return err } } if err := sbctl.Sign(file, output, save); err != nil { - log.Fatalln(err) + return err } + return nil }, } f := cmd.Flags() @@ -104,22 +105,21 @@ func signAllCmd() *cobra.Command { cmd := &cobra.Command{ Use: "sign-all", Short: "Sign all enrolled files with secure boot keys", - Run: func(cmd *cobra.Command, args []string) { - var outBundle error - outSign := false - + RunE: func(cmd *cobra.Command, args []string) error { if generate { - outBundle = sbctl.GenerateAllBundles(true) + if err := sbctl.GenerateAllBundles(true); err != nil { + logging.Fatal(err) + } } files, err := sbctl.ReadFileDatabase(sbctl.DBPath) if err != nil { - log.Fatalln(err) + return err } for _, entry := range files { - if sbctl.SignFile(sbctl.DBKey, sbctl.DBCert, entry.File, entry.OutputFile, entry.Checksum) != nil { - outSign = true + if err := sbctl.SignFile(sbctl.DBKey, sbctl.DBCert, entry.File, entry.OutputFile, entry.Checksum); err != nil { + logging.Fatal(err) continue } @@ -130,10 +130,7 @@ func signAllCmd() *cobra.Command { sbctl.WriteFileDatabase(sbctl.DBPath, files) } - - if outBundle != nil || outSign { - log.Fatalln("Errors were encountered, see above") - } + return nil }, } f := cmd.Flags() @@ -145,20 +142,22 @@ func removeFileCmd() *cobra.Command { return &cobra.Command{ Use: "remove-file", Short: "Remove file from database", - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { if len(args) < 1 { - log.Fatal("Need to specify file") + logging.Println("Need to specify file") + os.Exit(1) } files, err := sbctl.ReadFileDatabase(sbctl.DBPath) if err != nil { - log.Fatalln(err) + return err } if _, ok := files[args[0]]; !ok { - log.Printf("File %s doesn't exist in database!\n", args[0]) + logging.Print("File %s doesn't exist in database!\n", args[0]) os.Exit(1) } delete(files, args[0]) sbctl.WriteFileDatabase(sbctl.DBPath, files) + return nil }, } } @@ -178,11 +177,12 @@ func verifyCmd() *cobra.Command { return &cobra.Command{ Use: "verify", Short: "Find and check if files in the ESP are signed or not", - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { if err := sbctl.VerifyESP(); err != nil { // Really need to sort out the low level error handling - os.Exit(1) + return err } + return nil }, } } @@ -211,9 +211,10 @@ func bundleCmd() *cobra.Command { cmd := &cobra.Command{ Use: "bundle", Short: "Bundle the needed files for an EFI stub image", - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { if len(args) < 1 { - log.Fatalf("Requires a file to sign...\n") + logging.Print("Requires a file to sign...\n") + os.Exit(1) } checkFiles := []string{amducode, intelucode, splashImg, osRelease, efiStub, kernelImg, cmdline, initramfs} for _, path := range checkFiles { @@ -221,14 +222,14 @@ func bundleCmd() *cobra.Command { continue } if _, err := os.Stat(path); os.IsNotExist(err) { - log.Fatalf("%s does not exist!", path) + logging.Print("%s does not exist!\n", path) os.Exit(1) } } bundle := sbctl.NewBundle() output, err := filepath.Abs(args[0]) if err != nil { - log.Fatal(err) + return err } // Fail early if user wants to save bundle but doesn't have permissions var bundles sbctl.Bundles @@ -237,7 +238,7 @@ func bundleCmd() *cobra.Command { // to use ":=", which shadows the "bundles" variable bundles, err = sbctl.ReadBundleDatabase(sbctl.BundleDBPath) if err != nil { - log.Fatalln(err) + return err } } bundle.Output = output @@ -251,14 +252,14 @@ func bundleCmd() *cobra.Command { bundle.EFIStub = efiStub bundle.ESP = espPath if err = sbctl.CreateBundle(*bundle); err != nil { - log.Fatalln(err) - os.Exit(1) + return err } if save { bundles[bundle.Output] = bundle sbctl.WriteBundleDatabase(sbctl.BundleDBPath, bundles) sbctl.FormatBundle(bundle.Output, bundle) } + return nil }, } esp := sbctl.GetESP() @@ -281,8 +282,8 @@ func generateBundlesCmd() *cobra.Command { cmd := &cobra.Command{ Use: "generate-bundles", Short: "Generate all EFI stub bundles", - Run: func(cmd *cobra.Command, args []string) { - sbctl.GenerateAllBundles(sign) + RunE: func(cmd *cobra.Command, args []string) error { + return sbctl.GenerateAllBundles(sign) }, } f := cmd.Flags() @@ -294,8 +295,12 @@ func listBundlesCmd() *cobra.Command { return &cobra.Command{ Use: "list-bundles", Short: "List stored bundles", - Run: func(cmd *cobra.Command, args []string) { - sbctl.ListBundles() + RunE: func(cmd *cobra.Command, args []string) error { + _, err := sbctl.ListBundles() + if err != nil { + return err + } + return nil }, } } @@ -304,21 +309,23 @@ func removeBundleCmd() *cobra.Command { return &cobra.Command{ Use: "remove-bundle", Short: "Remove bundle from database", - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { if len(args) < 1 { - log.Fatal("Need to specify file") + logging.Print("Need to specify file\n") + os.Exit(1) } bundles, err := sbctl.ReadBundleDatabase(sbctl.BundleDBPath) if err != nil { - log.Fatalln(err) + return err } if _, ok := bundles[args[0]]; !ok { - log.Printf("Bundle %s doesn't exist in database!\n", args[0]) + logging.Print("Bundle %s doesn't exist in database!\n", args[0]) os.Exit(1) } delete(bundles, args[0]) sbctl.WriteBundleDatabase(sbctl.BundleDBPath, bundles) + return nil }, } } @@ -371,9 +378,6 @@ func main() { removeFileCmd(), } for _, c := range cmds { - c.PostRun = func(c *cobra.Command, args []string) { - sbctl.ColorsOff() - } rootCmd.AddCommand(c) } From 1508b290d65c582534c015f26cd4e24f39cdef7c Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Tue, 18 May 2021 01:31:51 +0200 Subject: [PATCH 16/43] Moved json out function Signed-off-by: Morten Linderud --- cmd/sbctl/list-files.go | 2 +- cmd/sbctl/main.go | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/cmd/sbctl/list-files.go b/cmd/sbctl/list-files.go index b5b6fc4..9d6e27b 100644 --- a/cmd/sbctl/list-files.go +++ b/cmd/sbctl/list-files.go @@ -28,7 +28,7 @@ func ListJsonOut(v interface{}) error { func RunList(_ *cobra.Command, args []string) error { files, _ := sbctl.ListFiles() if cmdOptions.JsonOutput { - ListJsonOut(files) + JsonOut(files) } return nil } diff --git a/cmd/sbctl/main.go b/cmd/sbctl/main.go index c902bfe..d77ee17 100755 --- a/cmd/sbctl/main.go +++ b/cmd/sbctl/main.go @@ -1,7 +1,9 @@ package main import ( + "encoding/json" "errors" + "fmt" "os" "path/filepath" @@ -41,6 +43,15 @@ func baseFlags(cmd *cobra.Command) { } } +func JsonOut(v interface{}) error { + b, err := json.MarshalIndent(v, "", " ") + if err != nil { + return fmt.Errorf("could not marshal json: %w", err) + } + fmt.Fprintf(os.Stdout, string(b)) + return nil +} + func createKeysCmd() *cobra.Command { return &cobra.Command{ Use: "create-keys", @@ -187,16 +198,6 @@ func verifyCmd() *cobra.Command { } } -// func listFilesCmd() *cobra.Command { -// return &cobra.Command{ -// Use: "list-files", -// Short: "List enrolled files", -// Run: func(cmd *cobra.Command, args []string) { -// sbctl.ListFiles() -// }, -// } -// } - func bundleCmd() *cobra.Command { var amducode string var intelucode string From 23381e01111ab30316ecf0be8d66182ffa656f42 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Tue, 18 May 2021 01:32:12 +0200 Subject: [PATCH 17/43] Added NotOK instead of "Error". Makes more sense semantically Signed-off-by: Morten Linderud --- logging/logging.go | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/logging/logging.go b/logging/logging.go index a4aeccf..be6f908 100644 --- a/logging/logging.go +++ b/logging/logging.go @@ -9,19 +9,22 @@ import ( var ( OkSym = "✔" - ErrSym = "✘" + NotOkSym = "✘" WarnSym = "‼" UnkwnSym = "⁇" + ErrSym = "⁇" ) var ( OkSymText = "[+]" - ErrSymText = "[-]" + NotOkSymText = "[-]" WarnSymText = "[!]" UnkwnSymText = "[?]" + ErrSymText = "[?]" ) var ( ok string + notok string err string warn string unkwn string @@ -62,12 +65,13 @@ func Ok(m string, a ...interface{}) { Print(Okf(m, a...)) } -func Errorf(m string, a ...interface{}) string { - return fmt.Sprintf("%s %s\n", err, fmt.Sprintf(m, a...)) +func NotOkf(m string, a ...interface{}) string { + return fmt.Sprintf("%s %s\n", notok, fmt.Sprintf(m, a...)) } -func Error(m string, a ...interface{}) { - Print(Errorf(m, a...)) +// Print ok string to stdout +func NotOk(m string, a ...interface{}) { + Print(NotOkf(m, a...)) } func Unknownf(m string, a ...interface{}) string { @@ -93,15 +97,25 @@ func Fatal(err error) { PrintWithFile(os.Stderr, Fatalf(err.Error())) } +func Errorf(m string, a ...interface{}) string { + return color.New(color.FgRed, color.Bold).Sprintf("%s\n", fmt.Sprintf(m, a...)) +} + +func Error(err error) { + PrintWithFile(os.Stderr, Errorf(err.Error())) +} + func init() { if ok := os.Getenv("EFIBOOTCTL_UNICODE"); ok == "0" { OkSym = OkSymText + NotOkSym = NotOkSymText ErrSym = ErrSymText WarnSym = WarnSymText UnkwnSym = UnkwnSymText } ok = color.New(color.FgGreen, color.Bold).Sprintf(OkSym) + notok = color.New(color.FgRed, color.Bold).Sprintf(NotOkSym) err = color.New(color.FgRed, color.Bold).Sprintf(ErrSym) warn = color.New(color.FgYellow, color.Bold).Sprintf(WarnSym) unkwn = color.New(color.FgRed, color.Bold).Sprintf(UnkwnSym) From 3d7f09498845bb897e47ed7eda8707e2586c8831 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Tue, 18 May 2021 01:33:24 +0200 Subject: [PATCH 18/43] Added an iter function Signed-off-by: Morten Linderud --- database.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/database.go b/database.go index b382da8..c398f4e 100644 --- a/database.go +++ b/database.go @@ -2,6 +2,7 @@ package sbctl import ( "encoding/json" + "fmt" "log" "os" ) @@ -36,3 +37,16 @@ func WriteFileDatabase(dbpath string, files SigningEntries) { log.Fatal(err) } } + +func SigningEntryIter(fn func(s *SigningEntry) error) error { + files, err := ReadFileDatabase(DBPath) + if err != nil { + return fmt.Errorf("couldn't open database %v: %w", DBPath, err) + } + for _, s := range files { + if err := fn(s); err != nil { + return err + } + } + return nil +} From fb9b3c7b3336fa449619fdfc460d728974be08dc Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Tue, 18 May 2021 01:47:29 +0200 Subject: [PATCH 19/43] =?UTF-8?q?=F0=9F=A4=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Morten Linderud --- bundles.go | 4 +-- cmd/sbctl/list-files.go | 43 +++++++++++++++++++++++--------- cmd/sbctl/main.go | 16 +++--------- cmd/sbctl/status.go | 49 ++++++++++++++++++++++++++++++++++++ keys.go | 19 ++++++++++---- sbctl.go | 55 +++-------------------------------------- 6 files changed, 104 insertions(+), 82 deletions(-) create mode 100644 cmd/sbctl/status.go diff --git a/bundles.go b/bundles.go index aac11b9..c0c9fc3 100644 --- a/bundles.go +++ b/bundles.go @@ -117,10 +117,10 @@ func GenerateBundle(bundle *Bundle) (bool, error) { func FormatBundle(name string, bundle *Bundle) { logging.Println(name) logging.Print("\tSigned:\t\t") - if VerifyFile(DBCert, name) { + if ok, _ := VerifyFile(DBCert, name); ok { logging.Ok("Signed") } else { - logging.Error("Not Signed") + logging.NotOk("Not Signed") } esp := GetESP() logging.Print("\tESP Location:\t%s\n", esp) diff --git a/cmd/sbctl/list-files.go b/cmd/sbctl/list-files.go index 9d6e27b..3d46399 100644 --- a/cmd/sbctl/list-files.go +++ b/cmd/sbctl/list-files.go @@ -1,12 +1,10 @@ package main import ( - "encoding/json" "fmt" - "os" "github.com/foxboron/sbctl" - "github.com/pkg/errors" + "github.com/foxboron/sbctl/logging" "github.com/spf13/cobra" ) @@ -16,17 +14,40 @@ var listFilesCmd = &cobra.Command{ RunE: RunList, } -func ListJsonOut(v interface{}) error { - b, err := json.MarshalIndent(v, "", " ") - if err != nil { - return errors.Wrapf(err, "could not marshal json") - } - fmt.Fprintf(os.Stdout, string(b)) - return nil +type JsonFile struct { + sbctl.SigningEntry + IsSigned bool `json:"is_signed"` } func RunList(_ *cobra.Command, args []string) error { - files, _ := sbctl.ListFiles() + files := []JsonFile{} + var isSigned bool + err := sbctl.SigningEntryIter( + func(s *sbctl.SigningEntry) error { + ok, err := sbctl.VerifyFile(sbctl.DBCert, s.OutputFile) + if err != nil { + return err + } + logging.Println(s.File) + logging.Print("Signed:\t\t") + if ok { + isSigned = true + logging.Ok("Signed") + } else if !ok { + isSigned = false + logging.NotOk("Not Signed") + } + if s.File != s.OutputFile { + logging.Print("Output File:\t%s\n", s.OutputFile) + } + fmt.Println("") + files = append(files, JsonFile{*s, isSigned}) + return nil + }, + ) + if err != nil { + return err + } if cmdOptions.JsonOutput { JsonOut(files) } diff --git a/cmd/sbctl/main.go b/cmd/sbctl/main.go index d77ee17..634bcd5 100755 --- a/cmd/sbctl/main.go +++ b/cmd/sbctl/main.go @@ -173,17 +173,6 @@ func removeFileCmd() *cobra.Command { } } -func statusCmd() *cobra.Command { - return &cobra.Command{ - Use: "status", - Short: "Show current boot status", - RunE: func(cmd *cobra.Command, args []string) error { - _, err := sbctl.CheckStatus() - return err - }, - } -} - func verifyCmd() *cobra.Command { return &cobra.Command{ Use: "verify", @@ -370,7 +359,6 @@ func main() { enrollKeysCmd(), signCmd(), signAllCmd(), - statusCmd(), verifyCmd(), bundleCmd(), generateBundlesCmd(), @@ -399,7 +387,9 @@ func main() { return ErrSilent }) if err := rootCmd.Execute(); err != nil { - if !errors.Is(err, ErrSilent) { + if errors.Is(err, os.ErrPermission) { + logging.Error(fmt.Errorf("sbtl requires root to run: %w", err)) + } else if !errors.Is(err, ErrSilent) { logging.Fatal(err) } os.Exit(1) diff --git a/cmd/sbctl/status.go b/cmd/sbctl/status.go new file mode 100644 index 0000000..36523b5 --- /dev/null +++ b/cmd/sbctl/status.go @@ -0,0 +1,49 @@ +package main + +import ( + "fmt" + "os" + + "github.com/foxboron/go-uefi/efi" + "github.com/foxboron/sbctl/logging" + "github.com/spf13/cobra" +) + +var statusCmd = &cobra.Command{ + Use: "status", + Short: "Show current boot status", + RunE: RunStatus, +} + +func RunStatus(cmd *cobra.Command, args []string) error { + ret := map[string]bool{} + if _, err := os.Stat("/sys/firmware/efi/efivars"); os.IsNotExist(err) { + return fmt.Errorf("system is not booted with UEFI!") + } + logging.Print("Setup Mode:\t") + if efi.GetSetupMode() { + logging.NotOk("Enabled") + ret["Setup Mode"] = true + } else { + logging.Ok("Disabled") + ret["Setup Mode"] = false + } + logging.Print("Secure Boot:\t") + if efi.GetSecureBoot() { + logging.Ok("Enabled") + ret["Secure Boot"] = true + } else { + logging.NotOk("Disabled") + ret["Secure Boot"] = false + } + if cmdOptions.JsonOutput { + JsonOut(ret) + } + return nil +} + +func init() { + CliCommands = append(CliCommands, cliCommand{ + Cmd: statusCmd, + }) +} diff --git a/keys.go b/keys.go index adcac94..4e84a27 100644 --- a/keys.go +++ b/keys.go @@ -145,14 +145,18 @@ func SBKeySync(dir string) bool { return true } -func VerifyFile(cert, file string) bool { +func VerifyFile(cert, file string) (bool, error) { + if err := unix.Access(cert, unix.R_OK); err != nil { + return false, fmt.Errorf("couldn't access %s: %w", cert, err) + } + cmd := exec.Command("sbverify", "--cert", cert, file) if err := cmd.Run(); err != nil { if exitError, ok := err.(*exec.ExitError); ok { - return exitError.ExitCode() == 0 + return exitError.ExitCode() == 0, nil } } - return true + return true, nil } func SignFile(key, cert, file, output, checksum string) error { @@ -163,7 +167,12 @@ func SignFile(key, cert, file, output, checksum string) error { } // Let's check if we have signed it already AND the original file hasn't changed - if VerifyFile(cert, output) && ChecksumFile(file) == checksum { + var ok bool + ok, err := VerifyFile(cert, output) + if err != nil { + return err + } + if ok && ChecksumFile(file) == checksum { logging.Print("have already signed %s\n", file) return nil } @@ -174,7 +183,7 @@ func SignFile(key, cert, file, output, checksum string) error { } logging.Ok("signing %s", file) - _, err := exec.Command("sbsign", "--key", key, "--cert", cert, "--output", output, file).Output() + _, err = exec.Command("sbsign", "--key", key, "--cert", cert, "--output", output, file).Output() if err != nil { return fmt.Errorf("Failed signing file: %w", err) } diff --git a/sbctl.go b/sbctl.go index 0d81b9c..b721295 100644 --- a/sbctl.go +++ b/sbctl.go @@ -12,7 +12,6 @@ import ( "path/filepath" "strings" - "github.com/foxboron/go-uefi/efi" "github.com/foxboron/sbctl/logging" ) @@ -110,10 +109,10 @@ func VerifyESP() error { logging.Warn("%s does not exist", file.OutputFile) } else if errors.Is(err, os.ErrPermission) { logging.Warn("%s permission denied. Can't read file\n", file.OutputFile) - } else if VerifyFile(DBCert, file.OutputFile) { + } else if ok, _ := VerifyFile(DBCert, file.OutputFile); ok { logging.Ok("%s is signed", file.OutputFile) } else { - logging.Error("%s is not signed", file.OutputFile) + logging.NotOk("%s is not signed", file.OutputFile) } } @@ -144,10 +143,10 @@ func VerifyESP() error { return nil } - if VerifyFile(DBCert, path) { + if ok, _ := VerifyFile(DBCert, path); ok { logging.Ok("%s is signed\n", path) } else { - logging.Error("%s is not signed\n", path) + logging.NotOk("%s is not signed\n", path) } return nil }) @@ -206,52 +205,6 @@ func Sign(file, output string, enroll bool) error { return err } -func ListFiles() (SigningEntries, error) { - files, err := ReadFileDatabase(DBPath) - if err != nil { - return nil, fmt.Errorf("couldn't open database %v: %w", DBPath, err) - } - for path, s := range files { - logging.Println(path) - if path != s.OutputFile { - logging.Print("Output:\t\t%s\n", s.OutputFile) - } - logging.Print("Signed:\t\t") - if VerifyFile(DBCert, s.OutputFile) { - logging.Ok("Signed") - } else { - logging.Error("Not Signed") - } - logging.Println("") - } - return files, nil -} - -func CheckStatus() (map[string]bool, error) { - return nil, fmt.Errorf("system is not booted with UEFI!") - var ret map[string]bool - if _, err := os.Stat("/sys/firmware/efi/efivars"); os.IsNotExist(err) { - return nil, fmt.Errorf("system is not booted with UEFI!") - } - logging.Print("Setup Mode:\t") - if efi.GetSetupMode() { - logging.Error("Enabled") - ret["Setup Mode"] = true - } else { - logging.Ok("Disabled") - ret["Setup Mode"] = false - } - logging.Print("Secure Boot:\t") - if efi.GetSecureBoot() { - logging.Ok("Enabled") - ret["Secure Boot"] = true - } else { - logging.Error("Disabled") - ret["Secure Boot"] = false - } - return ret, nil -} - func CreateKeys() error { if !CheckIfKeysInitialized(KeysPath) { logging.Print("Creating secure boot keys...") From 30e16f5bd779aa72d32ae646627e68acc8a932a7 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Tue, 18 May 2021 01:56:08 +0200 Subject: [PATCH 20/43] Catch for unknown command Signed-off-by: Morten Linderud --- cmd/sbctl/main.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/sbctl/main.go b/cmd/sbctl/main.go index 634bcd5..e8b6b57 100755 --- a/cmd/sbctl/main.go +++ b/cmd/sbctl/main.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "github.com/foxboron/sbctl" "github.com/foxboron/sbctl/logging" @@ -387,7 +388,9 @@ func main() { return ErrSilent }) if err := rootCmd.Execute(); err != nil { - if errors.Is(err, os.ErrPermission) { + if strings.HasPrefix(err.Error(), "unknown comman") { + logging.Println(err.Error()) + } else if errors.Is(err, os.ErrPermission) { logging.Error(fmt.Errorf("sbtl requires root to run: %w", err)) } else if !errors.Is(err, ErrSilent) { logging.Fatal(err) From 70b00f3184666007b1fe4777a290ee03b68234f6 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Tue, 18 May 2021 18:00:30 +0200 Subject: [PATCH 21/43] Added new error Signed-off-by: Morten Linderud --- cmd/sbctl/main.go | 5 ++++- sbctl.go | 10 ++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) mode change 100755 => 100644 cmd/sbctl/main.go diff --git a/cmd/sbctl/main.go b/cmd/sbctl/main.go old mode 100755 new mode 100644 index e8b6b57..92f7b4b --- a/cmd/sbctl/main.go +++ b/cmd/sbctl/main.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "os" - "path/filepath" "strings" "github.com/foxboron/sbctl" @@ -382,16 +381,20 @@ func main() { rootCmd.AddCommand(cmd.Cmd) } + // This returns i the flag is not found with a specific error rootCmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error { cmd.Println(err) cmd.Println(cmd.UsageString()) return ErrSilent }) + if err := rootCmd.Execute(); err != nil { if strings.HasPrefix(err.Error(), "unknown comman") { logging.Println(err.Error()) } else if errors.Is(err, os.ErrPermission) { logging.Error(fmt.Errorf("sbtl requires root to run: %w", err)) + } else if errors.Is(err, sbctl.ErrImmutable) { + logging.Error(err) } else if !errors.Is(err, ErrSilent) { logging.Fatal(err) } diff --git a/sbctl.go b/sbctl.go index b721295..d3d14d0 100644 --- a/sbctl.go +++ b/sbctl.go @@ -224,20 +224,22 @@ var efivarFSFiles = []string{ "/sys/firmware/efi/efivars/db-d719b2cb-3d3a-4596-a3bc-dad00e67656f", } +var ErrImmutable = errors.New("You need to chattr -i files in efivarfs") + func SyncKeys() error { errImmuable := false for _, file := range efivarFSFiles { b, err := IsImmutable(file) if err != nil { - return fmt.Errorf("Couldn't read file: %s\n", file) + return fmt.Errorf("Couldn't read file: %s", file) } - if b { - logging.Warn("File is immutable: %s\n", file) + if !b { + logging.Warn("File is immutable: %s", file) errImmuable = true } } if errImmuable { - return fmt.Errorf("You need to chattr -i files in efivarfs") + return ErrImmutable } synced := SBKeySync(KeysPath) if !synced { From d0022cb3b296c539af7245fd057bab7f788a16a3 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Tue, 18 May 2021 19:57:40 +0200 Subject: [PATCH 22/43] Added BundleIter Signed-off-by: Morten Linderud --- bundles.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/bundles.go b/bundles.go index c0c9fc3..d9febbb 100644 --- a/bundles.go +++ b/bundles.go @@ -51,6 +51,19 @@ func WriteBundleDatabase(dbpath string, bundles Bundles) { } } +func BundleIter(fn func(s *Bundle) error) error { + files, err := ReadBundleDatabase(BundleDBPath) + if err != nil { + return err + } + for _, s := range files { + if err := fn(s); err != nil { + return err + } + } + return nil +} + func GetEfistub() string { candidates := []string{ "/lib/systemd/boot/efi/linuxx64.efi.stub", From 877ab49ae61469b1a05a7a133b422992b069a493 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Tue, 18 May 2021 19:58:02 +0200 Subject: [PATCH 23/43] Implement GetGUID Signed-off-by: Morten Linderud --- keys.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/keys.go b/keys.go index 4e84a27..1fb534d 100644 --- a/keys.go +++ b/keys.go @@ -34,6 +34,8 @@ var ( DBCert = filepath.Join(KeysPath, "db", "db.pem") DBPath = filepath.Join(DatabasePath, "files.db") + + GUIDPath = filepath.Join(DatabasePath, "GUID") ) func CreateKey(path, name string) []byte { @@ -258,6 +260,18 @@ func CreateGUID(output string) ([]byte, error) { return uuid, nil } +func GetGUID() (uuid.UUID, error) { + b, err := os.ReadFile(GUIDPath) + if err != nil { + return [16]byte{}, err + } + u, err := uuid.ParseBytes(b) + if err != nil { + return [16]byte{}, err + } + return u, err +} + func InitializeSecureBootKeys(output string) error { os.MkdirAll(output, os.ModePerm) uuid, err := CreateGUID(output) From adadb52e737d8ec132be6396df67b7a0cc5ac920 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Tue, 18 May 2021 19:58:24 +0200 Subject: [PATCH 24/43] Give status the ability to display owner GUID Signed-off-by: Morten Linderud --- cmd/sbctl/status.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cmd/sbctl/status.go b/cmd/sbctl/status.go index 36523b5..e58134f 100644 --- a/cmd/sbctl/status.go +++ b/cmd/sbctl/status.go @@ -5,6 +5,7 @@ import ( "os" "github.com/foxboron/go-uefi/efi" + "github.com/foxboron/sbctl" "github.com/foxboron/sbctl/logging" "github.com/spf13/cobra" ) @@ -16,10 +17,17 @@ var statusCmd = &cobra.Command{ } func RunStatus(cmd *cobra.Command, args []string) error { - ret := map[string]bool{} + ret := map[string]interface{}{} if _, err := os.Stat("/sys/firmware/efi/efivars"); os.IsNotExist(err) { return fmt.Errorf("system is not booted with UEFI!") } + u, err := sbctl.GetGUID() + if err != nil { + return err + } + logging.Print("Owner GUID:\t") + logging.Println(u.String()) + ret["Owner GUID"] = u.String() logging.Print("Setup Mode:\t") if efi.GetSetupMode() { logging.NotOk("Enabled") From 3505f1b57174243a685f161f88795fb71701a557 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Tue, 18 May 2021 20:59:51 +0200 Subject: [PATCH 25/43] New structure Signed-off-by: Morten Linderud --- bundles.go | 29 --- cmd/sbctl/bundle.go | 99 +++++++++++ cmd/sbctl/completions.go | 51 ++++++ cmd/sbctl/create-keys.go | 20 +++ cmd/sbctl/enroll-keys.go | 20 +++ cmd/sbctl/generate-bundles.go | 30 ++++ cmd/sbctl/list-bundles.go | 73 ++++++++ cmd/sbctl/main.go | 323 ---------------------------------- cmd/sbctl/remove-bundle.go | 38 ++++ cmd/sbctl/remove-files.go | 37 ++++ cmd/sbctl/sign-all.go | 55 ++++++ cmd/sbctl/sign.go | 58 ++++++ cmd/sbctl/verify.go | 24 +++ sbctl.go | 12 -- 14 files changed, 505 insertions(+), 364 deletions(-) create mode 100644 cmd/sbctl/bundle.go create mode 100644 cmd/sbctl/completions.go create mode 100644 cmd/sbctl/create-keys.go create mode 100644 cmd/sbctl/enroll-keys.go create mode 100644 cmd/sbctl/generate-bundles.go create mode 100644 cmd/sbctl/list-bundles.go create mode 100644 cmd/sbctl/remove-bundle.go create mode 100644 cmd/sbctl/remove-files.go create mode 100644 cmd/sbctl/sign-all.go create mode 100644 cmd/sbctl/sign.go create mode 100644 cmd/sbctl/verify.go diff --git a/bundles.go b/bundles.go index d9febbb..25820fb 100644 --- a/bundles.go +++ b/bundles.go @@ -8,7 +8,6 @@ import ( "os" "os/exec" "path/filepath" - "strings" "github.com/foxboron/sbctl/logging" ) @@ -126,31 +125,3 @@ func GenerateBundle(bundle *Bundle) (bool, error) { logging.Print("Wrote EFI bundle %s\n", bundle.Output) return true, nil } - -func FormatBundle(name string, bundle *Bundle) { - logging.Println(name) - logging.Print("\tSigned:\t\t") - if ok, _ := VerifyFile(DBCert, name); ok { - logging.Ok("Signed") - } else { - logging.NotOk("Not Signed") - } - esp := GetESP() - logging.Print("\tESP Location:\t%s\n", esp) - logging.Print("\tOutput:\t\t└─%s\n", strings.TrimPrefix(bundle.Output, esp)) - logging.Print("\tEFI Stub Image:\t └─%s\n", bundle.EFIStub) - if bundle.Splash != "" { - logging.Print("\tSplash Image:\t ├─%s\n", bundle.Splash) - } - logging.Print("\tCmdline:\t ├─%s\n", bundle.Cmdline) - logging.Print("\tOS Release:\t ├─%s\n", bundle.OSRelease) - logging.Print("\tKernel Image:\t ├─%s\n", bundle.KernelImage) - logging.Print("\tInitramfs Image: └─%s\n", bundle.Initramfs) - if bundle.AMDMicrocode != "" { - logging.Print("\tAMD Microcode: └─%s\n", bundle.AMDMicrocode) - } - if bundle.IntelMicrocode != "" { - logging.Print("\tIntel Microcode: └─%s\n", bundle.IntelMicrocode) - } - logging.Println("") -} diff --git a/cmd/sbctl/bundle.go b/cmd/sbctl/bundle.go new file mode 100644 index 0000000..06f013d --- /dev/null +++ b/cmd/sbctl/bundle.go @@ -0,0 +1,99 @@ +package main + +import ( + "os" + "path/filepath" + + "github.com/foxboron/sbctl" + "github.com/foxboron/sbctl/logging" + "github.com/spf13/cobra" +) + +var ( + amducode string + intelucode string + splashImg string + osRelease string + efiStub string + kernelImg string + cmdline string + initramfs string + espPath string + saveBundle bool +) + +var bundleCmd = &cobra.Command{ + Use: "bundle", + Short: "Bundle the needed files for an EFI stub image", + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + logging.Print("Requires a file to sign...\n") + os.Exit(1) + } + checkFiles := []string{amducode, intelucode, splashImg, osRelease, efiStub, kernelImg, cmdline, initramfs} + for _, path := range checkFiles { + if path == "" { + continue + } + if _, err := os.Stat(path); os.IsNotExist(err) { + logging.Print("%s does not exist!\n", path) + os.Exit(1) + } + } + bundle := sbctl.NewBundle() + output, err := filepath.Abs(args[0]) + if err != nil { + return err + } + // Fail early if user wants to save bundle but doesn't have permissions + var bundles sbctl.Bundles + if saveBundle { + // "err" needs to have been declared before this, otherwise it's necessary + // to use ":=", which shadows the "bundles" variable + bundles, err = sbctl.ReadBundleDatabase(sbctl.BundleDBPath) + if err != nil { + return err + } + } + bundle.Output = output + bundle.IntelMicrocode = intelucode + bundle.AMDMicrocode = amducode + bundle.KernelImage = kernelImg + bundle.Initramfs = initramfs + bundle.Cmdline = cmdline + bundle.Splash = splashImg + bundle.OSRelease = osRelease + bundle.EFIStub = efiStub + bundle.ESP = espPath + if err = sbctl.CreateBundle(*bundle); err != nil { + return err + } + if saveBundle { + bundles[bundle.Output] = bundle + sbctl.WriteBundleDatabase(sbctl.BundleDBPath, bundles) + } + return nil + }, +} + +func bundleCmdFlags(cmd *cobra.Command) { + esp := sbctl.GetESP() + f := cmd.Flags() + f.StringVarP(&amducode, "amducode", "a", "", "AMD microcode location") + f.StringVarP(&intelucode, "intelucode", "i", "", "Intel microcode location") + f.StringVarP(&splashImg, "splash-img", "l", "", "Boot splash image location") + f.StringVarP(&osRelease, "os-release", "o", "/usr/lib/os-release", "OS Release file location") + f.StringVarP(&efiStub, "efi-stub", "e", "/usr/lib/systemd/boot/efi/linuxx64.efi.stub", "EFI Stub location") + f.StringVarP(&kernelImg, "kernel-img", "k", "/boot/vmlinuz-linux", "Kernel image location") + f.StringVarP(&cmdline, "cmdline", "c", "/etc/kernel/cmdline", "Cmdline location") + f.StringVarP(&initramfs, "initramfs", "f", "/boot/initramfs-linux.img", "Initramfs location") + f.StringVarP(&espPath, "esp", "p", esp, "ESP location") + f.BoolVarP(&saveBundle, "save", "s", false, "save bundle to the database") +} + +func init() { + bundleCmdFlags(bundleCmd) + CliCommands = append(CliCommands, cliCommand{ + Cmd: bundleCmd, + }) +} diff --git a/cmd/sbctl/completions.go b/cmd/sbctl/completions.go new file mode 100644 index 0000000..fdcb774 --- /dev/null +++ b/cmd/sbctl/completions.go @@ -0,0 +1,51 @@ +package main + +import ( + "os" + + "github.com/spf13/cobra" +) + +var completionCmd = &cobra.Command{Use: "completion"} + +func completionBashCmd() *cobra.Command { + var completionCmd = &cobra.Command{ + Use: "bash", + Hidden: true, + Run: func(cmd *cobra.Command, args []string) { + rootCmd.GenBashCompletion(os.Stdout) + }, + } + return completionCmd +} + +func completionZshCmd() *cobra.Command { + var completionCmd = &cobra.Command{ + Use: "zsh", + Hidden: true, + Run: func(cmd *cobra.Command, args []string) { + rootCmd.GenZshCompletion(os.Stdout) + }, + } + return completionCmd +} + +func completionFishCmd() *cobra.Command { + var completionCmd = &cobra.Command{ + Use: "fish", + Hidden: true, + Run: func(cmd *cobra.Command, args []string) { + rootCmd.GenFishCompletion(os.Stdout, true) + }, + } + return completionCmd +} + +func init() { + completionCmd.AddCommand(completionBashCmd()) + completionCmd.AddCommand(completionZshCmd()) + completionCmd.AddCommand(completionFishCmd()) + CliCommands = append(CliCommands, cliCommand{ + Cmd: completionCmd, + }) +} diff --git a/cmd/sbctl/create-keys.go b/cmd/sbctl/create-keys.go new file mode 100644 index 0000000..6288a98 --- /dev/null +++ b/cmd/sbctl/create-keys.go @@ -0,0 +1,20 @@ +package main + +import ( + "github.com/foxboron/sbctl" + "github.com/spf13/cobra" +) + +var createKeysCmd = &cobra.Command{ + Use: "create-keys", + Short: "Create a set of secure boot signing keys", + RunE: func(cmd *cobra.Command, args []string) error { + return sbctl.CreateKeys() + }, +} + +func init() { + CliCommands = append(CliCommands, cliCommand{ + Cmd: createKeysCmd, + }) +} diff --git a/cmd/sbctl/enroll-keys.go b/cmd/sbctl/enroll-keys.go new file mode 100644 index 0000000..b62c462 --- /dev/null +++ b/cmd/sbctl/enroll-keys.go @@ -0,0 +1,20 @@ +package main + +import ( + "github.com/foxboron/sbctl" + "github.com/spf13/cobra" +) + +var enrollKeysCmd = &cobra.Command{ + Use: "enroll-keys", + Short: "Enroll the current keys to EFI", + RunE: func(cmd *cobra.Command, args []string) error { + return sbctl.SyncKeys() + }, +} + +func init() { + CliCommands = append(CliCommands, cliCommand{ + Cmd: enrollKeysCmd, + }) +} diff --git a/cmd/sbctl/generate-bundles.go b/cmd/sbctl/generate-bundles.go new file mode 100644 index 0000000..0e45f09 --- /dev/null +++ b/cmd/sbctl/generate-bundles.go @@ -0,0 +1,30 @@ +package main + +import ( + "github.com/foxboron/sbctl" + "github.com/spf13/cobra" +) + +var ( + sign bool +) + +var generateBundlesCmd = &cobra.Command{ + Use: "generate-bundles", + Short: "Generate all EFI stub bundles", + RunE: func(cmd *cobra.Command, args []string) error { + return sbctl.GenerateAllBundles(sign) + }, +} + +func generateBundlesCmdFlags(cmd *cobra.Command) { + f := cmd.Flags() + f.BoolVarP(&sign, "sign", "s", false, "Sign all the generated bundles") +} + +func init() { + generateBundlesCmdFlags(generateBundlesCmd) + CliCommands = append(CliCommands, cliCommand{ + Cmd: generateBundlesCmd, + }) +} diff --git a/cmd/sbctl/list-bundles.go b/cmd/sbctl/list-bundles.go new file mode 100644 index 0000000..fc80e11 --- /dev/null +++ b/cmd/sbctl/list-bundles.go @@ -0,0 +1,73 @@ +package main + +import ( + "strings" + + "github.com/foxboron/sbctl" + "github.com/foxboron/sbctl/logging" + "github.com/spf13/cobra" +) + +type JsonBundle struct { + sbctl.Bundle + IsSigned bool `json:"is_signed"` +} + +var listBundlesCmd = &cobra.Command{ + Use: "list-bundles", + Short: "List stored bundles", + RunE: func(cmd *cobra.Command, args []string) error { + bundles := []JsonBundle{} + var isSigned bool + err := sbctl.BundleIter( + func(s *sbctl.Bundle) error { + ok, err := sbctl.VerifyFile(sbctl.DBCert, s.Output) + if err != nil { + return err + } + logging.Println("Enrolled bundles:\n") + logging.Println(s.Output) + logging.Print("\tSigned:\t\t") + if ok { + isSigned = true + logging.Ok("Signed") + } else { + isSigned = false + logging.NotOk("Not Signed") + } + esp := sbctl.GetESP() + logging.Print("\tESP Location:\t%s\n", esp) + logging.Print("\tOutput:\t\t└─%s\n", strings.TrimPrefix(s.Output, esp)) + logging.Print("\tEFI Stub Image:\t └─%s\n", s.EFIStub) + if s.Splash != "" { + logging.Print("\tSplash Image:\t ├─%s\n", s.Splash) + } + logging.Print("\tCmdline:\t ├─%s\n", s.Cmdline) + logging.Print("\tOS Release:\t ├─%s\n", s.OSRelease) + logging.Print("\tKernel Image:\t ├─%s\n", s.KernelImage) + logging.Print("\tInitramfs Image: └─%s\n", s.Initramfs) + if s.AMDMicrocode != "" { + logging.Print("\tAMD Microcode: └─%s\n", s.AMDMicrocode) + } + if s.IntelMicrocode != "" { + logging.Print("\tIntel Microcode: └─%s\n", s.IntelMicrocode) + } + bundles = append(bundles, JsonBundle{*s, isSigned}) + logging.Println("") + return nil + }) + if err != nil { + return err + } + if cmdOptions.JsonOutput { + JsonOut(bundles) + } + return nil + }, +} + +func init() { + CliCommands = append(CliCommands, cliCommand{ + Cmd: listBundlesCmd, + }) +} diff --git a/cmd/sbctl/main.go b/cmd/sbctl/main.go index 92f7b4b..5d1d6e9 100644 --- a/cmd/sbctl/main.go +++ b/cmd/sbctl/main.go @@ -52,330 +52,7 @@ func JsonOut(v interface{}) error { return nil } -func createKeysCmd() *cobra.Command { - return &cobra.Command{ - Use: "create-keys", - Short: "Create a set of secure boot signing keys", - RunE: func(cmd *cobra.Command, args []string) error { - return sbctl.CreateKeys() - }, - } -} - -func enrollKeysCmd() *cobra.Command { - return &cobra.Command{ - Use: "enroll-keys", - Short: "Enroll the current keys to EFI", - RunE: func(cmd *cobra.Command, args []string) error { - return sbctl.SyncKeys() - }, - } -} - -func signCmd() *cobra.Command { - var save bool - var output string - - cmd := &cobra.Command{ - Use: "sign", - Short: "Sign a file with secure boot keys", - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - logging.Print("Requires a file to sign\n") - os.Exit(1) - } - - // Ensure we have absolute paths - file, err := filepath.Abs(args[0]) - if err != nil { - return err - } - if output == "" { - output = file - } else { - output, err = filepath.Abs(output) - if err != nil { - return err - } - } - - if err := sbctl.Sign(file, output, save); err != nil { - return err - } - return nil - }, - } - f := cmd.Flags() - f.BoolVarP(&save, "save", "s", false, "save file to the database") - f.StringVarP(&output, "output", "o", "", "output filename. Default replaces the file") - return cmd -} - -func signAllCmd() *cobra.Command { - var generate bool - cmd := &cobra.Command{ - Use: "sign-all", - Short: "Sign all enrolled files with secure boot keys", - RunE: func(cmd *cobra.Command, args []string) error { - if generate { - if err := sbctl.GenerateAllBundles(true); err != nil { - logging.Fatal(err) - } - } - - files, err := sbctl.ReadFileDatabase(sbctl.DBPath) - if err != nil { - return err - } - for _, entry := range files { - - if err := sbctl.SignFile(sbctl.DBKey, sbctl.DBCert, entry.File, entry.OutputFile, entry.Checksum); err != nil { - logging.Fatal(err) - continue - } - - // Update checksum after we signed it - checksum := sbctl.ChecksumFile(entry.File) - entry.Checksum = checksum - files[entry.File] = entry - sbctl.WriteFileDatabase(sbctl.DBPath, files) - - } - return nil - }, - } - f := cmd.Flags() - f.BoolVarP(&generate, "generate", "g", false, "run all generate-* sub-commands before signing") - return cmd -} - -func removeFileCmd() *cobra.Command { - return &cobra.Command{ - Use: "remove-file", - Short: "Remove file from database", - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - logging.Println("Need to specify file") - os.Exit(1) - } - files, err := sbctl.ReadFileDatabase(sbctl.DBPath) - if err != nil { - return err - } - if _, ok := files[args[0]]; !ok { - logging.Print("File %s doesn't exist in database!\n", args[0]) - os.Exit(1) - } - delete(files, args[0]) - sbctl.WriteFileDatabase(sbctl.DBPath, files) - return nil - }, - } -} - -func verifyCmd() *cobra.Command { - return &cobra.Command{ - Use: "verify", - Short: "Find and check if files in the ESP are signed or not", - RunE: func(cmd *cobra.Command, args []string) error { - if err := sbctl.VerifyESP(); err != nil { - // Really need to sort out the low level error handling - return err - } - return nil - }, - } -} - -func bundleCmd() *cobra.Command { - var amducode string - var intelucode string - var splashImg string - var osRelease string - var efiStub string - var kernelImg string - var cmdline string - var initramfs string - var espPath string - var save bool - cmd := &cobra.Command{ - Use: "bundle", - Short: "Bundle the needed files for an EFI stub image", - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - logging.Print("Requires a file to sign...\n") - os.Exit(1) - } - checkFiles := []string{amducode, intelucode, splashImg, osRelease, efiStub, kernelImg, cmdline, initramfs} - for _, path := range checkFiles { - if path == "" { - continue - } - if _, err := os.Stat(path); os.IsNotExist(err) { - logging.Print("%s does not exist!\n", path) - os.Exit(1) - } - } - bundle := sbctl.NewBundle() - output, err := filepath.Abs(args[0]) - if err != nil { - return err - } - // Fail early if user wants to save bundle but doesn't have permissions - var bundles sbctl.Bundles - if save { - // "err" needs to have been declared before this, otherwise it's necessary - // to use ":=", which shadows the "bundles" variable - bundles, err = sbctl.ReadBundleDatabase(sbctl.BundleDBPath) - if err != nil { - return err - } - } - bundle.Output = output - bundle.IntelMicrocode = intelucode - bundle.AMDMicrocode = amducode - bundle.KernelImage = kernelImg - bundle.Initramfs = initramfs - bundle.Cmdline = cmdline - bundle.Splash = splashImg - bundle.OSRelease = osRelease - bundle.EFIStub = efiStub - bundle.ESP = espPath - if err = sbctl.CreateBundle(*bundle); err != nil { - return err - } - if save { - bundles[bundle.Output] = bundle - sbctl.WriteBundleDatabase(sbctl.BundleDBPath, bundles) - sbctl.FormatBundle(bundle.Output, bundle) - } - return nil - }, - } - esp := sbctl.GetESP() - f := cmd.Flags() - f.StringVarP(&amducode, "amducode", "a", "", "AMD microcode location") - f.StringVarP(&intelucode, "intelucode", "i", "", "Intel microcode location") - f.StringVarP(&splashImg, "splash-img", "l", "", "Boot splash image location") - f.StringVarP(&osRelease, "os-release", "o", "/usr/lib/os-release", "OS Release file location") - f.StringVarP(&efiStub, "efi-stub", "e", "/usr/lib/systemd/boot/efi/linuxx64.efi.stub", "EFI Stub location") - f.StringVarP(&kernelImg, "kernel-img", "k", "/boot/vmlinuz-linux", "Kernel image location") - f.StringVarP(&cmdline, "cmdline", "c", "/etc/kernel/cmdline", "Cmdline location") - f.StringVarP(&initramfs, "initramfs", "f", "/boot/initramfs-linux.img", "Initramfs location") - f.StringVarP(&espPath, "esp", "p", esp, "ESP location") - f.BoolVarP(&save, "save", "s", false, "save bundle to the database") - return cmd -} - -func generateBundlesCmd() *cobra.Command { - var sign bool - cmd := &cobra.Command{ - Use: "generate-bundles", - Short: "Generate all EFI stub bundles", - RunE: func(cmd *cobra.Command, args []string) error { - return sbctl.GenerateAllBundles(sign) - }, - } - f := cmd.Flags() - f.BoolVarP(&sign, "sign", "s", false, "Sign all the generated bundles") - return cmd -} - -func listBundlesCmd() *cobra.Command { - return &cobra.Command{ - Use: "list-bundles", - Short: "List stored bundles", - RunE: func(cmd *cobra.Command, args []string) error { - _, err := sbctl.ListBundles() - if err != nil { - return err - } - return nil - }, - } -} - -func removeBundleCmd() *cobra.Command { - return &cobra.Command{ - Use: "remove-bundle", - Short: "Remove bundle from database", - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - logging.Print("Need to specify file\n") - os.Exit(1) - } - bundles, err := sbctl.ReadBundleDatabase(sbctl.BundleDBPath) - if err != nil { - return err - } - - if _, ok := bundles[args[0]]; !ok { - logging.Print("Bundle %s doesn't exist in database!\n", args[0]) - os.Exit(1) - } - delete(bundles, args[0]) - sbctl.WriteBundleDatabase(sbctl.BundleDBPath, bundles) - return nil - }, - } -} - -func completionBashCmd() *cobra.Command { - var completionCmd = &cobra.Command{ - Use: "bash", - Hidden: true, - Run: func(cmd *cobra.Command, args []string) { - rootCmd.GenBashCompletion(os.Stdout) - }, - } - return completionCmd -} - -func completionZshCmd() *cobra.Command { - var completionCmd = &cobra.Command{ - Use: "zsh", - Hidden: true, - Run: func(cmd *cobra.Command, args []string) { - rootCmd.GenZshCompletion(os.Stdout) - }, - } - return completionCmd -} - -func completionFishCmd() *cobra.Command { - var completionCmd = &cobra.Command{ - Use: "fish", - Hidden: true, - Run: func(cmd *cobra.Command, args []string) { - rootCmd.GenFishCompletion(os.Stdout, true) - }, - } - return completionCmd -} - func main() { - cmds := []*cobra.Command{ - createKeysCmd(), - enrollKeysCmd(), - signCmd(), - signAllCmd(), - verifyCmd(), - bundleCmd(), - generateBundlesCmd(), - removeBundleCmd(), - listBundlesCmd(), - removeFileCmd(), - } - for _, c := range cmds { - rootCmd.AddCommand(c) - } - - completionCmd := &cobra.Command{Use: "completion"} - completionCmd.AddCommand(completionBashCmd()) - completionCmd.AddCommand(completionZshCmd()) - completionCmd.AddCommand(completionFishCmd()) - rootCmd.AddCommand(completionCmd) - for _, cmd := range CliCommands { baseFlags(cmd.Cmd) rootCmd.AddCommand(cmd.Cmd) diff --git a/cmd/sbctl/remove-bundle.go b/cmd/sbctl/remove-bundle.go new file mode 100644 index 0000000..9358f92 --- /dev/null +++ b/cmd/sbctl/remove-bundle.go @@ -0,0 +1,38 @@ +package main + +import ( + "os" + + "github.com/foxboron/sbctl" + "github.com/foxboron/sbctl/logging" + "github.com/spf13/cobra" +) + +var removeBundleCmd = &cobra.Command{ + Use: "remove-bundle", + Short: "Remove bundle from database", + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + logging.Print("Need to specify file\n") + os.Exit(1) + } + bundles, err := sbctl.ReadBundleDatabase(sbctl.BundleDBPath) + if err != nil { + return err + } + + if _, ok := bundles[args[0]]; !ok { + logging.Print("Bundle %s doesn't exist in database!\n", args[0]) + os.Exit(1) + } + delete(bundles, args[0]) + sbctl.WriteBundleDatabase(sbctl.BundleDBPath, bundles) + return nil + }, +} + +func init() { + CliCommands = append(CliCommands, cliCommand{ + Cmd: removeBundleCmd, + }) +} diff --git a/cmd/sbctl/remove-files.go b/cmd/sbctl/remove-files.go new file mode 100644 index 0000000..4fa3f26 --- /dev/null +++ b/cmd/sbctl/remove-files.go @@ -0,0 +1,37 @@ +package main + +import ( + "os" + + "github.com/foxboron/sbctl" + "github.com/foxboron/sbctl/logging" + "github.com/spf13/cobra" +) + +var removeFileCmd = &cobra.Command{ + Use: "remove-file", + Short: "Remove file from database", + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + logging.Println("Need to specify file") + os.Exit(1) + } + files, err := sbctl.ReadFileDatabase(sbctl.DBPath) + if err != nil { + return err + } + if _, ok := files[args[0]]; !ok { + logging.Print("File %s doesn't exist in database!\n", args[0]) + os.Exit(1) + } + delete(files, args[0]) + sbctl.WriteFileDatabase(sbctl.DBPath, files) + return nil + }, +} + +func init() { + CliCommands = append(CliCommands, cliCommand{ + Cmd: removeFileCmd, + }) +} diff --git a/cmd/sbctl/sign-all.go b/cmd/sbctl/sign-all.go new file mode 100644 index 0000000..756e628 --- /dev/null +++ b/cmd/sbctl/sign-all.go @@ -0,0 +1,55 @@ +package main + +import ( + "github.com/foxboron/sbctl" + "github.com/foxboron/sbctl/logging" + "github.com/spf13/cobra" +) + +var ( + generate bool +) + +var signAllCmd = &cobra.Command{ + Use: "sign-all", + Short: "Sign all enrolled files with secure boot keys", + RunE: func(cmd *cobra.Command, args []string) error { + if generate { + if err := sbctl.GenerateAllBundles(true); err != nil { + logging.Fatal(err) + } + } + + files, err := sbctl.ReadFileDatabase(sbctl.DBPath) + if err != nil { + return err + } + for _, entry := range files { + + if err := sbctl.SignFile(sbctl.DBKey, sbctl.DBCert, entry.File, entry.OutputFile, entry.Checksum); err != nil { + logging.Fatal(err) + continue + } + + // Update checksum after we signed it + checksum := sbctl.ChecksumFile(entry.File) + entry.Checksum = checksum + files[entry.File] = entry + sbctl.WriteFileDatabase(sbctl.DBPath, files) + + } + return nil + }, +} + +func signAllCmdFlags(cmd *cobra.Command) { + f := cmd.Flags() + f.BoolVarP(&generate, "generate", "g", false, "run all generate-* sub-commands before signing") +} + +func init() { + signAllCmdFlags(signAllCmd) + CliCommands = append(CliCommands, cliCommand{ + Cmd: signAllCmd, + }) +} diff --git a/cmd/sbctl/sign.go b/cmd/sbctl/sign.go new file mode 100644 index 0000000..7e48ca2 --- /dev/null +++ b/cmd/sbctl/sign.go @@ -0,0 +1,58 @@ +package main + +import ( + "os" + "path/filepath" + + "github.com/foxboron/sbctl" + "github.com/foxboron/sbctl/logging" + "github.com/spf13/cobra" +) + +var ( + save bool + output string +) + +var signCmd = &cobra.Command{ + Use: "sign", + Short: "Sign a file with secure boot keys", + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + logging.Print("Requires a file to sign\n") + os.Exit(1) + } + + // Ensure we have absolute paths + file, err := filepath.Abs(args[0]) + if err != nil { + return err + } + if output == "" { + output = file + } else { + output, err = filepath.Abs(output) + if err != nil { + return err + } + } + + if err := sbctl.Sign(file, output, save); err != nil { + return err + } + return nil + }, +} + +func signCmdFlags(cmd *cobra.Command) { + f := cmd.Flags() + f.BoolVarP(&save, "save", "s", false, "save file to the database") + f.StringVarP(&output, "output", "o", "", "output filename. Default replaces the file") +} + +func init() { + signCmdFlags(signCmd) + CliCommands = append(CliCommands, cliCommand{ + Cmd: signCmd, + }) +} diff --git a/cmd/sbctl/verify.go b/cmd/sbctl/verify.go new file mode 100644 index 0000000..b5ee9af --- /dev/null +++ b/cmd/sbctl/verify.go @@ -0,0 +1,24 @@ +package main + +import ( + "github.com/foxboron/sbctl" + "github.com/spf13/cobra" +) + +var verifyCmd = &cobra.Command{ + Use: "verify", + Short: "Find and check if files in the ESP are signed or not", + RunE: func(cmd *cobra.Command, args []string) error { + if err := sbctl.VerifyESP(); err != nil { + // Really need to sort out the low level error handling + return err + } + return nil + }, +} + +func init() { + CliCommands = append(CliCommands, cliCommand{ + Cmd: verifyCmd, + }) +} diff --git a/sbctl.go b/sbctl.go index d3d14d0..c720034 100644 --- a/sbctl.go +++ b/sbctl.go @@ -341,15 +341,3 @@ func GenerateAllBundles(sign bool) error { return nil } - -func ListBundles() (Bundles, error) { - bundles, err := ReadBundleDatabase(BundleDBPath) - if err != nil { - return nil, fmt.Errorf("couldn't open database: %v", err) - } - logging.Println("Enrolled bundles:\n") - for key, bundle := range bundles { - FormatBundle(key, bundle) - } - return bundles, nil -} From a5e0551e5686b7104a0d3e3688a33c0953cce3a3 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Tue, 18 May 2021 21:01:06 +0200 Subject: [PATCH 26/43] GUID package Signed-off-by: Morten Linderud --- guid.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ keys.go | 42 ------------------------------------------ 2 files changed, 51 insertions(+), 42 deletions(-) create mode 100644 guid.go diff --git a/guid.go b/guid.go new file mode 100644 index 0000000..bb528a3 --- /dev/null +++ b/guid.go @@ -0,0 +1,51 @@ +package sbctl + +import ( + "io/ioutil" + "log" + "path/filepath" + + "github.com/foxboron/sbctl/logging" + "github.com/google/uuid" +) + +func CreateUUID() []byte { + id, err := uuid.NewRandom() + if err != nil { + log.Fatal(err) + } + return []byte(id.String()) +} + +func CreateGUID(output string) ([]byte, error) { + var uuid []byte + guidPath := filepath.Join(output, "GUID") + if _, err := os.Stat(guidPath); os.IsNotExist(err) { + logging.Print("Created UUID %s...", uuid) + uuid = CreateUUID() + err := ioutil.WriteFile(guidPath, uuid, 0600) + if err != nil { + return nil, err + } + logging.Ok("") + } else { + uuid, err = ioutil.ReadFile(guidPath) + if err != nil { + return nil, err + } + logging.Print("Using UUID %s...", uuid) + } + return uuid, nil +} + +func GetGUID() (uuid.UUID, error) { + b, err := os.ReadFile(GUIDPath) + if err != nil { + return [16]byte{}, err + } + u, err := uuid.ParseBytes(b) + if err != nil { + return [16]byte{}, err + } + return u, err +} diff --git a/keys.go b/keys.go index 1fb534d..6a561ca 100644 --- a/keys.go +++ b/keys.go @@ -17,7 +17,6 @@ import ( "time" "github.com/foxboron/sbctl/logging" - "github.com/google/uuid" "golang.org/x/sys/unix" ) @@ -231,47 +230,6 @@ func CheckIfKeysInitialized(output string) bool { return true } -func CreateUUID() []byte { - id, err := uuid.NewRandom() - if err != nil { - log.Fatal(err) - } - return []byte(id.String()) -} - -func CreateGUID(output string) ([]byte, error) { - var uuid []byte - guidPath := filepath.Join(output, "GUID") - if _, err := os.Stat(guidPath); os.IsNotExist(err) { - uuid = CreateUUID() - logging.Print("Created UUID %s...", uuid) - err := os.WriteFile(guidPath, uuid, 0600) - if err != nil { - return nil, err - } - logging.Ok("") - } else { - uuid, err = os.ReadFile(guidPath) - if err != nil { - return nil, err - } - logging.Print("Using UUID %s...", uuid) - } - return uuid, nil -} - -func GetGUID() (uuid.UUID, error) { - b, err := os.ReadFile(GUIDPath) - if err != nil { - return [16]byte{}, err - } - u, err := uuid.ParseBytes(b) - if err != nil { - return [16]byte{}, err - } - return u, err -} - func InitializeSecureBootKeys(output string) error { os.MkdirAll(output, os.ModePerm) uuid, err := CreateGUID(output) From 342ba34a1759e1c206659bbd728652af61a613db Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Tue, 18 May 2021 23:29:54 +0200 Subject: [PATCH 27/43] Fixup Signed-off-by: Morten Linderud --- cmds.go | 1 - guid.go | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 cmds.go diff --git a/cmds.go b/cmds.go deleted file mode 100644 index 681ced5..0000000 --- a/cmds.go +++ /dev/null @@ -1 +0,0 @@ -package sbctl diff --git a/guid.go b/guid.go index bb528a3..31db805 100644 --- a/guid.go +++ b/guid.go @@ -3,6 +3,7 @@ package sbctl import ( "io/ioutil" "log" + "os" "path/filepath" "github.com/foxboron/sbctl/logging" From 235238c987ea0d1c1ae3f251057b8d62669b33b9 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Tue, 18 May 2021 23:34:59 +0200 Subject: [PATCH 28/43] Fixed lint issues Signed-off-by: Morten Linderud --- cmd/sbctl/main.go | 2 +- cmd/sbctl/status.go | 2 +- keys.go | 6 +++--- logging/logging.go | 5 ----- sbctl.go | 16 ++++++++-------- 5 files changed, 13 insertions(+), 18 deletions(-) diff --git a/cmd/sbctl/main.go b/cmd/sbctl/main.go index 5d1d6e9..c2997bc 100644 --- a/cmd/sbctl/main.go +++ b/cmd/sbctl/main.go @@ -48,7 +48,7 @@ func JsonOut(v interface{}) error { if err != nil { return fmt.Errorf("could not marshal json: %w", err) } - fmt.Fprintf(os.Stdout, string(b)) + fmt.Fprint(os.Stdout, string(b)) return nil } diff --git a/cmd/sbctl/status.go b/cmd/sbctl/status.go index e58134f..6a5fda2 100644 --- a/cmd/sbctl/status.go +++ b/cmd/sbctl/status.go @@ -19,7 +19,7 @@ var statusCmd = &cobra.Command{ func RunStatus(cmd *cobra.Command, args []string) error { ret := map[string]interface{}{} if _, err := os.Stat("/sys/firmware/efi/efivars"); os.IsNotExist(err) { - return fmt.Errorf("system is not booted with UEFI!") + return fmt.Errorf("system is not booted with UEFI") } u, err := sbctl.GetGUID() if err != nil { diff --git a/keys.go b/keys.go index 6a561ca..1856c70 100644 --- a/keys.go +++ b/keys.go @@ -164,7 +164,7 @@ func SignFile(key, cert, file, output, checksum string) error { // Check file exists before we do anything if _, err := os.Stat(file); os.IsNotExist(err) { - return fmt.Errorf("%s does not exist!", file) + return fmt.Errorf("%s does not exist", file) } // Let's check if we have signed it already AND the original file hasn't changed @@ -183,10 +183,10 @@ func SignFile(key, cert, file, output, checksum string) error { return fmt.Errorf("couldn't access %s", key) } - logging.Ok("signing %s", file) + logging.Ok("Signing %s", file) _, err = exec.Command("sbsign", "--key", key, "--cert", cert, "--output", output, file).Output() if err != nil { - return fmt.Errorf("Failed signing file: %w", err) + return fmt.Errorf("failed signing file: %w", err) } return nil } diff --git a/logging/logging.go b/logging/logging.go index be6f908..b225f47 100644 --- a/logging/logging.go +++ b/logging/logging.go @@ -12,20 +12,17 @@ var ( NotOkSym = "✘" WarnSym = "‼" UnkwnSym = "⁇" - ErrSym = "⁇" ) var ( OkSymText = "[+]" NotOkSymText = "[-]" WarnSymText = "[!]" UnkwnSymText = "[?]" - ErrSymText = "[?]" ) var ( ok string notok string - err string warn string unkwn string ) @@ -109,14 +106,12 @@ func init() { if ok := os.Getenv("EFIBOOTCTL_UNICODE"); ok == "0" { OkSym = OkSymText NotOkSym = NotOkSymText - ErrSym = ErrSymText WarnSym = WarnSymText UnkwnSym = UnkwnSymText } ok = color.New(color.FgGreen, color.Bold).Sprintf(OkSym) notok = color.New(color.FgRed, color.Bold).Sprintf(NotOkSym) - err = color.New(color.FgRed, color.Bold).Sprintf(ErrSym) warn = color.New(color.FgYellow, color.Bold).Sprintf(WarnSym) unkwn = color.New(color.FgRed, color.Bold).Sprintf(UnkwnSym) PrintOn() diff --git a/sbctl.go b/sbctl.go index c720034..5dc21d7 100644 --- a/sbctl.go +++ b/sbctl.go @@ -176,7 +176,7 @@ func Sign(file, output string, enroll bool) error { files, err := ReadFileDatabase(DBPath) if err != nil { - return fmt.Errorf("Couldn't open database: %s", DBPath) + return fmt.Errorf("couldn't open database: %s", DBPath) } if entry, ok := files[file]; ok { err = SignFile(DBKey, DBCert, entry.File, entry.OutputFile, entry.Checksum) @@ -224,14 +224,14 @@ var efivarFSFiles = []string{ "/sys/firmware/efi/efivars/db-d719b2cb-3d3a-4596-a3bc-dad00e67656f", } -var ErrImmutable = errors.New("You need to chattr -i files in efivarfs") +var ErrImmutable = errors.New("you need to chattr -i files in efivarfs") func SyncKeys() error { errImmuable := false for _, file := range efivarFSFiles { b, err := IsImmutable(file) if err != nil { - return fmt.Errorf("Couldn't read file: %s", file) + return fmt.Errorf("couldn't read file: %s", file) } if !b { logging.Warn("File is immutable: %s", file) @@ -243,7 +243,7 @@ func SyncKeys() error { } synced := SBKeySync(KeysPath) if !synced { - return errors.New("Couldn't sync keys") + return errors.New("couldn't sync keys") } else { logging.Ok("Synced keys!") } @@ -301,7 +301,7 @@ func CreateBundle(bundle Bundle) error { logging.Warn(err.Error()) } if !out { - return fmt.Errorf("failed to generate bundle %s!", bundle.Output) + return fmt.Errorf("failed to generate bundle %s", bundle.Output) } return nil @@ -311,7 +311,7 @@ func GenerateAllBundles(sign bool) error { logging.Println("Generating EFI bundles....") bundles, err := ReadBundleDatabase(BundleDBPath) if err != nil { - return fmt.Errorf("Couldn't open database: %s", BundleDBPath) + return fmt.Errorf("couldn't open database: %s", BundleDBPath) } out_create := true out_sign := true @@ -332,11 +332,11 @@ func GenerateAllBundles(sign bool) error { } if !out_create { - return errors.New("Error generating EFI bundles") + return errors.New("error generating EFI bundles") } if !out_sign { - return errors.New("Error signing EFI bundles") + return errors.New("error signing EFI bundles") } return nil From f01453a9786d7a5a620a0ac1c03cbbf090b52eb4 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Tue, 18 May 2021 23:40:26 +0200 Subject: [PATCH 29/43] Change immutable error a little bit Signed-off-by: Morten Linderud --- cmd/sbctl/main.go | 2 +- sbctl.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/sbctl/main.go b/cmd/sbctl/main.go index c2997bc..5b4286b 100644 --- a/cmd/sbctl/main.go +++ b/cmd/sbctl/main.go @@ -71,7 +71,7 @@ func main() { } else if errors.Is(err, os.ErrPermission) { logging.Error(fmt.Errorf("sbtl requires root to run: %w", err)) } else if errors.Is(err, sbctl.ErrImmutable) { - logging.Error(err) + logging.Println("You need to chattr -i files in efivarfs") } else if !errors.Is(err, ErrSilent) { logging.Fatal(err) } diff --git a/sbctl.go b/sbctl.go index 5dc21d7..de10fb3 100644 --- a/sbctl.go +++ b/sbctl.go @@ -224,7 +224,7 @@ var efivarFSFiles = []string{ "/sys/firmware/efi/efivars/db-d719b2cb-3d3a-4596-a3bc-dad00e67656f", } -var ErrImmutable = errors.New("you need to chattr -i files in efivarfs") +var ErrImmutable = errors.New("file is immutable") func SyncKeys() error { errImmuable := false From 97435cc48e6d8ea7a53131af10b20c2cfa34243e Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Wed, 19 May 2021 00:23:52 +0200 Subject: [PATCH 30/43] More internal restructuring Move more logic top-level, move prints to top-level Signed-off-by: Morten Linderud --- bundles.go | 3 --- cmd/sbctl/bundle.go | 1 + cmd/sbctl/enroll-keys.go | 26 +++++++++++++++++++++++++- cmd/sbctl/verify.go | 1 - sbctl.go | 38 +++++--------------------------------- util.go | 23 +++++++++++++++-------- 6 files changed, 46 insertions(+), 46 deletions(-) diff --git a/bundles.go b/bundles.go index 25820fb..1cc0214 100644 --- a/bundles.go +++ b/bundles.go @@ -8,8 +8,6 @@ import ( "os" "os/exec" "path/filepath" - - "github.com/foxboron/sbctl/logging" ) type Bundle struct { @@ -122,6 +120,5 @@ func GenerateBundle(bundle *Bundle) (bool, error) { return exitError.ExitCode() == 0, nil } } - logging.Print("Wrote EFI bundle %s\n", bundle.Output) return true, nil } diff --git a/cmd/sbctl/bundle.go b/cmd/sbctl/bundle.go index 06f013d..5daa9da 100644 --- a/cmd/sbctl/bundle.go +++ b/cmd/sbctl/bundle.go @@ -68,6 +68,7 @@ var bundleCmd = &cobra.Command{ if err = sbctl.CreateBundle(*bundle); err != nil { return err } + logging.Print("Wrote EFI bundle %s\n", bundle.Output) if saveBundle { bundles[bundle.Output] = bundle sbctl.WriteBundleDatabase(sbctl.BundleDBPath, bundles) diff --git a/cmd/sbctl/enroll-keys.go b/cmd/sbctl/enroll-keys.go index b62c462..bbd9da6 100644 --- a/cmd/sbctl/enroll-keys.go +++ b/cmd/sbctl/enroll-keys.go @@ -1,7 +1,11 @@ package main import ( + "errors" + "fmt" + "github.com/foxboron/sbctl" + "github.com/foxboron/sbctl/logging" "github.com/spf13/cobra" ) @@ -9,7 +13,27 @@ var enrollKeysCmd = &cobra.Command{ Use: "enroll-keys", Short: "Enroll the current keys to EFI", RunE: func(cmd *cobra.Command, args []string) error { - return sbctl.SyncKeys() + var isImmutable bool + for _, file := range sbctl.EfivarFSFiles { + err := sbctl.IsImmutable(file) + if errors.Is(err, sbctl.ErrImmutable) { + isImmutable = true + logging.Warn("File is immutable: %s", file) + } else if errors.Is(err, sbctl.ErrNotImmutable) { + continue + } else if err != nil { + return fmt.Errorf("couldn't read file: %s", file) + } + } + if isImmutable { + return sbctl.ErrImmutable + } + synced := sbctl.SBKeySync(sbctl.KeysPath) + if !synced { + return errors.New("couldn't sync keys") + } + logging.Ok("Synced keys!") + return nil }, } diff --git a/cmd/sbctl/verify.go b/cmd/sbctl/verify.go index b5ee9af..3b88e12 100644 --- a/cmd/sbctl/verify.go +++ b/cmd/sbctl/verify.go @@ -10,7 +10,6 @@ var verifyCmd = &cobra.Command{ Short: "Find and check if files in the ESP are signed or not", RunE: func(cmd *cobra.Command, args []string) error { if err := sbctl.VerifyESP(); err != nil { - // Really need to sort out the low level error handling return err } return nil diff --git a/sbctl.go b/sbctl.go index de10fb3..6d226c8 100644 --- a/sbctl.go +++ b/sbctl.go @@ -143,7 +143,11 @@ func VerifyESP() error { return nil } - if ok, _ := VerifyFile(DBCert, path); ok { + ok, err := VerifyFile(DBCert, path) + if err != nil { + return err + } + if ok { logging.Ok("%s is signed\n", path) } else { logging.NotOk("%s is not signed\n", path) @@ -218,38 +222,6 @@ func CreateKeys() error { return nil } -var efivarFSFiles = []string{ - "/sys/firmware/efi/efivars/PK-8be4df61-93ca-11d2-aa0d-00e098032b8c", - "/sys/firmware/efi/efivars/KEK-8be4df61-93ca-11d2-aa0d-00e098032b8c", - "/sys/firmware/efi/efivars/db-d719b2cb-3d3a-4596-a3bc-dad00e67656f", -} - -var ErrImmutable = errors.New("file is immutable") - -func SyncKeys() error { - errImmuable := false - for _, file := range efivarFSFiles { - b, err := IsImmutable(file) - if err != nil { - return fmt.Errorf("couldn't read file: %s", file) - } - if !b { - logging.Warn("File is immutable: %s", file) - errImmuable = true - } - } - if errImmuable { - return ErrImmutable - } - synced := SBKeySync(KeysPath) - if !synced { - return errors.New("couldn't sync keys") - } else { - logging.Ok("Synced keys!") - } - return nil -} - func CombineFiles(microcode, initramfs string) (*os.File, error) { tmpFile, err := os.CreateTemp("/var/tmp", "initramfs-") if err != nil { diff --git a/util.go b/util.go index 2591c56..fa9a06d 100644 --- a/util.go +++ b/util.go @@ -52,19 +52,26 @@ func ReadOrCreateFile(filePath string) ([]byte, error) { return f, nil } -func IsImmutable(file string) (bool, error) { +var EfivarFSFiles = []string{ + "/sys/firmware/efi/efivars/PK-8be4df61-93ca-11d2-aa0d-00e098032b8c", + "/sys/firmware/efi/efivars/KEK-8be4df61-93ca-11d2-aa0d-00e098032b8c", + "/sys/firmware/efi/efivars/db-d719b2cb-3d3a-4596-a3bc-dad00e67656f", +} + +var ErrImmutable = errors.New("file is immutable") +var ErrNotImmutable = errors.New("file is not immutable") + +func IsImmutable(file string) error { f, err := os.Open(file) - if errors.Is(err, os.ErrNotExist) { - return false, nil - } else if err != nil { - return false, err + if err != nil { + return err } attr, err := GetAttr(f) if err != nil { - return false, err + return err } if (attr & FS_IMMUTABLE_FL) != 0 { - return false, nil + return ErrImmutable } - return true, nil + return ErrNotImmutable } From 3454841a75c07fcf698af86ef7054d857dc6a7c2 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Wed, 19 May 2021 00:38:25 +0200 Subject: [PATCH 31/43] Moved create-keys top-level Signed-off-by: Morten Linderud --- cmd/sbctl/create-keys.go | 14 +++++++++++++- sbctl.go | 15 +-------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/cmd/sbctl/create-keys.go b/cmd/sbctl/create-keys.go index 6288a98..2631c08 100644 --- a/cmd/sbctl/create-keys.go +++ b/cmd/sbctl/create-keys.go @@ -1,7 +1,10 @@ package main import ( + "fmt" + "github.com/foxboron/sbctl" + "github.com/foxboron/sbctl/logging" "github.com/spf13/cobra" ) @@ -9,7 +12,16 @@ var createKeysCmd = &cobra.Command{ Use: "create-keys", Short: "Create a set of secure boot signing keys", RunE: func(cmd *cobra.Command, args []string) error { - return sbctl.CreateKeys() + if !sbctl.CheckIfKeysInitialized(sbctl.KeysPath) { + logging.Print("Creating secure boot keys...") + err := sbctl.InitializeSecureBootKeys(sbctl.DatabasePath) + if err != nil { + return fmt.Errorf("couldn't initialize secure boot: %w", err) + } + } else { + logging.Ok("Secure boot keys has already been created!") + } + return nil }, } diff --git a/sbctl.go b/sbctl.go index 6d226c8..0aad999 100644 --- a/sbctl.go +++ b/sbctl.go @@ -209,19 +209,6 @@ func Sign(file, output string, enroll bool) error { return err } -func CreateKeys() error { - if !CheckIfKeysInitialized(KeysPath) { - logging.Print("Creating secure boot keys...") - err := InitializeSecureBootKeys(DatabasePath) - if err != nil { - return fmt.Errorf("couldn't initialize secure boot: %w", err) - } - } else { - logging.Ok("Secure boot keys has already been created!") - } - return nil -} - func CombineFiles(microcode, initramfs string) (*os.File, error) { tmpFile, err := os.CreateTemp("/var/tmp", "initramfs-") if err != nil { @@ -270,7 +257,7 @@ func CreateBundle(bundle Bundle) error { out, err := GenerateBundle(&bundle) if err != nil { - logging.Warn(err.Error()) + return err } if !out { return fmt.Errorf("failed to generate bundle %s", bundle.Output) From a318695f44bcdd94376d924c67ccd24ac3ff6522 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Wed, 19 May 2021 00:55:57 +0200 Subject: [PATCH 32/43] Moved generate-bundles top-level Signed-off-by: Morten Linderud --- cmd/sbctl/generate-bundles.go | 41 ++++++++++++++++++++++++++++++++++- sbctl.go | 35 ------------------------------ 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/cmd/sbctl/generate-bundles.go b/cmd/sbctl/generate-bundles.go index 0e45f09..4ae0bd2 100644 --- a/cmd/sbctl/generate-bundles.go +++ b/cmd/sbctl/generate-bundles.go @@ -1,7 +1,11 @@ package main import ( + "errors" + "fmt" + "github.com/foxboron/sbctl" + "github.com/foxboron/sbctl/logging" "github.com/spf13/cobra" ) @@ -13,7 +17,42 @@ var generateBundlesCmd = &cobra.Command{ Use: "generate-bundles", Short: "Generate all EFI stub bundles", RunE: func(cmd *cobra.Command, args []string) error { - return sbctl.GenerateAllBundles(sign) + logging.Println("Generating EFI bundles....") + out_create := true + out_sign := true + err := sbctl.BundleIter(func(bundle *sbctl.Bundle) error { + err := sbctl.CreateBundle(*bundle) + if err != nil { + fmt.Println(err) + out_create = false + return nil + } + logging.Print("Wrote EFI bundle %s\n", bundle.Output) + if sign { + file := bundle.Output + err = sbctl.SignFile(sbctl.DBKey, sbctl.DBCert, file, file, "") + if errors.Is(err, sbctl.ErrAlreadySigned) { + logging.Unknown("Bundle has already been signed") + } else if err != nil { + out_sign = false + } else { + logging.Ok("Signed %s", file) + } + } + return nil + }) + + if !out_create { + return errors.New("error generating EFI bundles") + } + + if !out_sign { + return errors.New("error signing EFI bundles") + } + if err != nil { + return err + } + return nil }, } diff --git a/sbctl.go b/sbctl.go index 0aad999..df2d510 100644 --- a/sbctl.go +++ b/sbctl.go @@ -265,38 +265,3 @@ func CreateBundle(bundle Bundle) error { return nil } - -func GenerateAllBundles(sign bool) error { - logging.Println("Generating EFI bundles....") - bundles, err := ReadBundleDatabase(BundleDBPath) - if err != nil { - return fmt.Errorf("couldn't open database: %s", BundleDBPath) - } - out_create := true - out_sign := true - for _, bundle := range bundles { - err := CreateBundle(*bundle) - if err != nil { - out_create = false - continue - } - - if sign { - file := bundle.Output - err = SignFile(DBKey, DBCert, file, file, "") - if err != nil { - out_sign = false - } - } - } - - if !out_create { - return errors.New("error generating EFI bundles") - } - - if !out_sign { - return errors.New("error signing EFI bundles") - } - - return nil -} From 3f05d1df5224c8ae26af4c3ba31377f705df12f3 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Wed, 19 May 2021 00:56:08 +0200 Subject: [PATCH 33/43] Propegate errors better Signed-off-by: Morten Linderud --- cmd/sbctl/sign-all.go | 17 ++++++++++++----- keys.go | 13 +++++++------ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/cmd/sbctl/sign-all.go b/cmd/sbctl/sign-all.go index 756e628..ccaddff 100644 --- a/cmd/sbctl/sign-all.go +++ b/cmd/sbctl/sign-all.go @@ -1,6 +1,8 @@ package main import ( + "errors" + "github.com/foxboron/sbctl" "github.com/foxboron/sbctl/logging" "github.com/spf13/cobra" @@ -15,8 +17,9 @@ var signAllCmd = &cobra.Command{ Short: "Sign all enrolled files with secure boot keys", RunE: func(cmd *cobra.Command, args []string) error { if generate { - if err := sbctl.GenerateAllBundles(true); err != nil { - logging.Fatal(err) + sign = true + if err := generateBundlesCmd.RunE(cmd, args); err != nil { + return err } } @@ -26,9 +29,13 @@ var signAllCmd = &cobra.Command{ } for _, entry := range files { - if err := sbctl.SignFile(sbctl.DBKey, sbctl.DBCert, entry.File, entry.OutputFile, entry.Checksum); err != nil { - logging.Fatal(err) - continue + err := sbctl.SignFile(sbctl.DBKey, sbctl.DBCert, entry.File, entry.OutputFile, entry.Checksum) + if errors.Is(err, sbctl.ErrAlreadySigned) { + logging.Print("File have already been signed %s\n", entry.OutputFile) + } else if err != nil { + return err + } else { + logging.Ok("Signed %s", entry.OutputFile) } // Update checksum after we signed it diff --git a/keys.go b/keys.go index 1856c70..3253ac4 100644 --- a/keys.go +++ b/keys.go @@ -7,6 +7,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/pem" + "errors" "fmt" "log" "math/big" @@ -160,30 +161,30 @@ func VerifyFile(cert, file string) (bool, error) { return true, nil } +var ErrAlreadySigned = errors.New("already signed file") + func SignFile(key, cert, file, output, checksum string) error { // Check file exists before we do anything - if _, err := os.Stat(file); os.IsNotExist(err) { + if _, err := os.Stat(file); errors.Is(err, os.ErrNotExist) { return fmt.Errorf("%s does not exist", file) } // Let's check if we have signed it already AND the original file hasn't changed - var ok bool ok, err := VerifyFile(cert, output) if err != nil { return err } + if ok && ChecksumFile(file) == checksum { - logging.Print("have already signed %s\n", file) - return nil + return ErrAlreadySigned } // Let's also check if we can access the key if err := unix.Access(key, unix.R_OK); err != nil { - return fmt.Errorf("couldn't access %s", key) + return fmt.Errorf("couldn't access %s: %w", key, err) } - logging.Ok("Signing %s", file) _, err = exec.Command("sbsign", "--key", key, "--cert", cert, "--output", output, file).Output() if err != nil { return fmt.Errorf("failed signing file: %w", err) From 6dfc186d43d8cb891f8b6a8717db6488136ffccc Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Wed, 19 May 2021 22:33:50 +0200 Subject: [PATCH 34/43] enroll changes Signed-off-by: Morten Linderud --- cmd/sbctl/enroll-keys.go | 1 + guid.go | 4 ---- keys.go | 24 +++++++++++++----------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/cmd/sbctl/enroll-keys.go b/cmd/sbctl/enroll-keys.go index bbd9da6..d38f72f 100644 --- a/cmd/sbctl/enroll-keys.go +++ b/cmd/sbctl/enroll-keys.go @@ -28,6 +28,7 @@ var enrollKeysCmd = &cobra.Command{ if isImmutable { return sbctl.ErrImmutable } + logging.Print("Syncing %s to EFI variables...", sbctl.KeysPath) synced := sbctl.SBKeySync(sbctl.KeysPath) if !synced { return errors.New("couldn't sync keys") diff --git a/guid.go b/guid.go index 31db805..0a25b1d 100644 --- a/guid.go +++ b/guid.go @@ -6,7 +6,6 @@ import ( "os" "path/filepath" - "github.com/foxboron/sbctl/logging" "github.com/google/uuid" ) @@ -22,19 +21,16 @@ func CreateGUID(output string) ([]byte, error) { var uuid []byte guidPath := filepath.Join(output, "GUID") if _, err := os.Stat(guidPath); os.IsNotExist(err) { - logging.Print("Created UUID %s...", uuid) uuid = CreateUUID() err := ioutil.WriteFile(guidPath, uuid, 0600) if err != nil { return nil, err } - logging.Ok("") } else { uuid, err = ioutil.ReadFile(guidPath) if err != nil { return nil, err } - logging.Print("Using UUID %s...", uuid) } return uuid, nil } diff --git a/keys.go b/keys.go index 3253ac4..a63d716 100644 --- a/keys.go +++ b/keys.go @@ -99,22 +99,20 @@ func SaveKey(k []byte, path string) { } -func KeyToSiglist(UUID []byte, input string) []byte { - logging.Print("Create EFI signature list %s.esl...", input) - out, err := exec.Command( +func KeyToSiglist(UUID []byte, input string) error { + _, err := exec.Command( "sbsiglist", "--owner", string(UUID), "--type", "x509", "--output", fmt.Sprintf("%s.esl", input), input, ).Output() if err != nil { - log.Fatalf("Failed creating signature list: %s", err) + return err } - return out + return nil } func SignEFIVariable(key, cert, varname, vardatafile, output string) ([]byte, error) { - logging.Print("Signing %s with %s...", vardatafile, key) out, err := exec.Command("sbvarsign", "--key", key, "--cert", cert, "--output", output, varname, vardatafile).Output() if err != nil { return nil, fmt.Errorf("failed signing EFI variable: %v", err) @@ -123,7 +121,6 @@ func SignEFIVariable(key, cert, varname, vardatafile, output string) ([]byte, er } func SBKeySync(dir string) bool { - logging.Print("Syncing %s to EFI variables...", dir) cmd := exec.Command("sbkeysync", "--pk", "--verbose", "--keystore", dir) var out bytes.Buffer cmd.Stdout = &out @@ -237,6 +234,7 @@ func InitializeSecureBootKeys(output string) error { if err != nil { return err } + logging.Print("Using Owner UUID %s\n", uuid) // Create the directories we need and keys for _, key := range SecureBootKeys { path := filepath.Join(output, "keys", key.Key) @@ -244,13 +242,17 @@ func InitializeSecureBootKeys(output string) error { keyPath := filepath.Join(path, key.Key) pk := CreateKey(keyPath, key.Description) SaveKey(pk, keyPath) - KeyToSiglist(uuid, fmt.Sprintf("%s.der", keyPath)) - // Confusing code - // TODO: make it cleaner + derSiglist := fmt.Sprintf("%s.der", keyPath) + if err := KeyToSiglist(uuid, derSiglist); err != nil { + return err + } + logging.Print("Created EFI signature list %s.esl...", derSiglist) signingkeyPath := filepath.Join(output, "keys", key.SignedWith, key.SignedWith) signingKey := fmt.Sprintf("%s.key", signingkeyPath) signingCertificate := fmt.Sprintf("%s.pem", signingkeyPath) - SignEFIVariable(signingKey, signingCertificate, key.Key, fmt.Sprintf("%s.der.esl", keyPath), fmt.Sprintf("%s.auth", keyPath)) + vardatafile := fmt.Sprintf("%s.der.esl", keyPath) + logging.Print("Signing %s with %s...", vardatafile, key.Key) + SignEFIVariable(signingKey, signingCertificate, key.Key, vardatafile, fmt.Sprintf("%s.auth", keyPath)) } return nil } From b49ebbb8bfdda9bd4138f48dde69144e03df50c6 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Wed, 19 May 2021 23:56:58 +0200 Subject: [PATCH 35/43] Added CanVerifyFiles Signed-off-by: Morten Linderud --- keys.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/keys.go b/keys.go index a63d716..b5e2bee 100644 --- a/keys.go +++ b/keys.go @@ -38,6 +38,14 @@ var ( GUIDPath = filepath.Join(DatabasePath, "GUID") ) +// Check if we can access the db certificate to verify files +func CanVerifyFiles() error { + if err := unix.Access(DBCert, unix.R_OK); err != nil { + return fmt.Errorf("couldn't access %s: %w", DBCert, err) + } + return nil +} + func CreateKey(path, name string) []byte { serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) From 8b4fc4072408278c55125dcf225326dc6b424bf9 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Wed, 19 May 2021 23:57:08 +0200 Subject: [PATCH 36/43] Added internal functions for checked paths, and CheckMSDos Signed-off-by: Morten Linderud --- util.go | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/util.go b/util.go index fa9a06d..03033d1 100644 --- a/util.go +++ b/util.go @@ -1,12 +1,15 @@ package sbctl import ( + "bytes" "crypto/sha256" "encoding/hex" "errors" + "io" "log" "os" "path/filepath" + "strings" ) func ChecksumFile(file string) string { @@ -75,3 +78,36 @@ func IsImmutable(file string) error { } return ErrNotImmutable } + +func CheckMSDos(path string) (bool, error) { + r, err := os.Open(path) + if err != nil { + return false, err + } + defer r.Close() + + // We are looking for MS-DOS executables. + // They contain "MZ" as the two first bytes + var header [2]byte + if _, err = io.ReadFull(r, header[:]); err != nil { + return false, err + } + if !bytes.Equal(header[:], []byte{0x4d, 0x5a}) { + return false, nil + } + return true, nil +} + +var ( + checked = make(map[string]bool) +) + +func AddChecked(path string) { + normalized := strings.Join(strings.Split(path, "/")[2:], "/") + checked[normalized] = true +} + +func InChecked(path string) bool { + normalized := strings.Join(strings.Split(path, "/")[2:], "/") + return checked[normalized] +} From 0d121672ca9f08c7702eef2d4c416ddcaba6129d Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Wed, 19 May 2021 23:57:23 +0200 Subject: [PATCH 37/43] Move verify to top-level Signed-off-by: Morten Linderud --- cmd/sbctl/verify.go | 67 ++++++++++++++++++++++++++++++++++++++- sbctl.go | 76 --------------------------------------------- 2 files changed, 66 insertions(+), 77 deletions(-) diff --git a/cmd/sbctl/verify.go b/cmd/sbctl/verify.go index 3b88e12..42857b7 100644 --- a/cmd/sbctl/verify.go +++ b/cmd/sbctl/verify.go @@ -1,7 +1,13 @@ package main import ( + "errors" + "log" + "os" + "path/filepath" + "github.com/foxboron/sbctl" + "github.com/foxboron/sbctl/logging" "github.com/spf13/cobra" ) @@ -9,9 +15,68 @@ var verifyCmd = &cobra.Command{ Use: "verify", Short: "Find and check if files in the ESP are signed or not", RunE: func(cmd *cobra.Command, args []string) error { - if err := sbctl.VerifyESP(); err != nil { + // Exit early if we can't verify files + if err := sbctl.CanVerifyFiles(); err != nil { + return err + } + espPath := sbctl.GetESP() + logging.Print("Verifying file database and EFI images in %s...\n", espPath) + if err := sbctl.SigningEntryIter(func(file *sbctl.SigningEntry) error { + sbctl.AddChecked(file.OutputFile) + // Check output file exists before checking if it's signed + if _, err := os.Open(file.OutputFile); errors.Is(err, os.ErrNotExist) { + logging.Warn("%s does not exist", file.OutputFile) + return nil + } else if errors.Is(err, os.ErrPermission) { + logging.Warn("%s permission denied. Can't read file\n", file.OutputFile) + return nil + } + ok, err := sbctl.VerifyFile(sbctl.DBCert, file.OutputFile) + if err != nil { + return err + } + if ok { + logging.Ok("%s is signed", file.OutputFile) + } else { + logging.NotOk("%s is not signed", file.OutputFile) + } + return nil + }); err != nil { return err } + + if err := filepath.Walk(espPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if fi, _ := os.Stat(path); fi.IsDir() { + return nil + } + + if sbctl.InChecked(path) { + return nil + } + + ok, err := sbctl.CheckMSDos(path) + if err != nil { + return err + } + if !ok { + return nil + } + ok, err = sbctl.VerifyFile(sbctl.DBCert, path) + if err != nil { + return err + } + if ok { + logging.Ok("%s is signed\n", path) + } else { + logging.NotOk("%s is not signed\n", path) + } + return nil + }); err != nil { + log.Println(err) + } return nil }, } diff --git a/sbctl.go b/sbctl.go index df2d510..5824396 100644 --- a/sbctl.go +++ b/sbctl.go @@ -1,18 +1,13 @@ package sbctl import ( - "bytes" "encoding/json" - "errors" "fmt" "io" "log" "os" "os/exec" "path/filepath" - "strings" - - "github.com/foxboron/sbctl/logging" ) // Functions that doesn't fit anywhere else @@ -90,77 +85,6 @@ func GetESP() string { return "" } -func VerifyESP() error { - espPath := GetESP() - files, err := ReadFileDatabase(DBPath) - if err != nil { - return err - } - logging.Print("Verifying file database and EFI images in %s...\n", espPath) - - // Cache files we have looked at. - checked := make(map[string]bool) - for _, file := range files { - normalized := strings.Join(strings.Split(file.OutputFile, "/")[2:], "/") - checked[normalized] = true - - // Check output file exists before checking if it's signed - if _, err := os.Open(file.OutputFile); errors.Is(err, os.ErrNotExist) { - logging.Warn("%s does not exist", file.OutputFile) - } else if errors.Is(err, os.ErrPermission) { - logging.Warn("%s permission denied. Can't read file\n", file.OutputFile) - } else if ok, _ := VerifyFile(DBCert, file.OutputFile); ok { - logging.Ok("%s is signed", file.OutputFile) - } else { - logging.NotOk("%s is not signed", file.OutputFile) - } - } - - err = filepath.Walk(espPath, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if fi, _ := os.Stat(path); fi.IsDir() { - return nil - } - - // Don't check files we have checked - normalized := strings.Join(strings.Split(path, "/")[2:], "/") - if ok := checked[normalized]; ok { - return nil - } - - r, _ := os.Open(path) - defer r.Close() - - // We are looking for MS-DOS executables. - // They contain "MZ" as the two first bytes - var header [2]byte - if _, err = io.ReadFull(r, header[:]); err != nil { - return nil - } - if !bytes.Equal(header[:], []byte{0x4d, 0x5a}) { - return nil - } - - ok, err := VerifyFile(DBCert, path) - if err != nil { - return err - } - if ok { - logging.Ok("%s is signed\n", path) - } else { - logging.NotOk("%s is not signed\n", path) - } - return nil - }) - if err != nil { - log.Println(err) - } - - return nil -} - func Sign(file, output string, enroll bool) error { file, err := filepath.Abs(file) if err != nil { From fe514e1af79edf6e0092c2dc9385a60542f2bf5e Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Thu, 20 May 2021 00:26:59 +0200 Subject: [PATCH 38/43] Added errors to WriteFileDatabase Signed-off-by: Morten Linderud --- cmd/sbctl/remove-files.go | 4 +++- cmd/sbctl/sign-all.go | 4 +++- cmd/sbctl/verify.go | 3 +-- database.go | 8 ++++---- sbctl.go | 8 ++++++-- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/cmd/sbctl/remove-files.go b/cmd/sbctl/remove-files.go index 4fa3f26..2367ebc 100644 --- a/cmd/sbctl/remove-files.go +++ b/cmd/sbctl/remove-files.go @@ -25,7 +25,9 @@ var removeFileCmd = &cobra.Command{ os.Exit(1) } delete(files, args[0]) - sbctl.WriteFileDatabase(sbctl.DBPath, files) + if err := sbctl.WriteFileDatabase(sbctl.DBPath, files); err != nil { + return err + } return nil }, } diff --git a/cmd/sbctl/sign-all.go b/cmd/sbctl/sign-all.go index ccaddff..0af7ba2 100644 --- a/cmd/sbctl/sign-all.go +++ b/cmd/sbctl/sign-all.go @@ -42,7 +42,9 @@ var signAllCmd = &cobra.Command{ checksum := sbctl.ChecksumFile(entry.File) entry.Checksum = checksum files[entry.File] = entry - sbctl.WriteFileDatabase(sbctl.DBPath, files) + if err := sbctl.WriteFileDatabase(sbctl.DBPath, files); err != nil { + return err + } } return nil diff --git a/cmd/sbctl/verify.go b/cmd/sbctl/verify.go index 42857b7..c3cd6f5 100644 --- a/cmd/sbctl/verify.go +++ b/cmd/sbctl/verify.go @@ -2,7 +2,6 @@ package main import ( "errors" - "log" "os" "path/filepath" @@ -75,7 +74,7 @@ var verifyCmd = &cobra.Command{ } return nil }); err != nil { - log.Println(err) + return err } return nil }, diff --git a/database.go b/database.go index c398f4e..530702d 100644 --- a/database.go +++ b/database.go @@ -3,7 +3,6 @@ package sbctl import ( "encoding/json" "fmt" - "log" "os" ) @@ -27,15 +26,16 @@ func ReadFileDatabase(dbpath string) (SigningEntries, error) { return files, nil } -func WriteFileDatabase(dbpath string, files SigningEntries) { +func WriteFileDatabase(dbpath string, files SigningEntries) error { data, err := json.MarshalIndent(files, "", " ") if err != nil { - log.Fatal(err) + return err } err = os.WriteFile(dbpath, data, 0644) if err != nil { - log.Fatal(err) + return err } + return nil } func SigningEntryIter(fn func(s *SigningEntry) error) error { diff --git a/sbctl.go b/sbctl.go index 5824396..69e98ed 100644 --- a/sbctl.go +++ b/sbctl.go @@ -115,7 +115,9 @@ func Sign(file, output string, enroll bool) error { checksum := ChecksumFile(file) entry.Checksum = checksum files[file] = entry - WriteFileDatabase(DBPath, files) + if err := WriteFileDatabase(DBPath, files); err != nil { + return err + } } else { err = SignFile(DBKey, DBCert, file, output, "") // return early if signing fails @@ -127,7 +129,9 @@ func Sign(file, output string, enroll bool) error { if enroll { checksum := ChecksumFile(file) files[file] = &SigningEntry{File: file, OutputFile: output, Checksum: checksum} - WriteFileDatabase(DBPath, files) + if err := WriteFileDatabase(DBPath, files); err != nil { + return err + } } return err From 6b0242c9536e11e4dcc7566ac38bb07479f2dd94 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Thu, 20 May 2021 00:51:54 +0200 Subject: [PATCH 39/43] Added print layout for key syncing Signed-off-by: Morten Linderud --- cmd/sbctl/enroll-keys.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/sbctl/enroll-keys.go b/cmd/sbctl/enroll-keys.go index d38f72f..459b096 100644 --- a/cmd/sbctl/enroll-keys.go +++ b/cmd/sbctl/enroll-keys.go @@ -28,11 +28,12 @@ var enrollKeysCmd = &cobra.Command{ if isImmutable { return sbctl.ErrImmutable } - logging.Print("Syncing %s to EFI variables...", sbctl.KeysPath) + logging.Print("Syncing keys to EFI variables...") synced := sbctl.SBKeySync(sbctl.KeysPath) if !synced { return errors.New("couldn't sync keys") } + logging.Println("") logging.Ok("Synced keys!") return nil }, From 57a1c93eb9ace664bab050e1389f1a87b4717c8a Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Thu, 20 May 2021 01:08:45 +0200 Subject: [PATCH 40/43] Remove last of the log.* stuff Signed-off-by: Morten Linderud --- bundles.go | 11 +++++---- cmd/sbctl/bundle.go | 5 ++++- cmd/sbctl/remove-bundle.go | 5 ++++- cmd/sbctl/sign-all.go | 5 ++++- guid.go | 6 +---- keys.go | 46 ++++++++++++++++++++------------------ sbctl.go | 14 ++++++++---- util.go | 7 +++--- 8 files changed, 57 insertions(+), 42 deletions(-) diff --git a/bundles.go b/bundles.go index 1cc0214..f7157c9 100644 --- a/bundles.go +++ b/bundles.go @@ -4,10 +4,11 @@ import ( "encoding/json" "errors" "fmt" - "log" "os" "os/exec" "path/filepath" + + "github.com/foxboron/sbctl/logging" ) type Bundle struct { @@ -37,15 +38,16 @@ func ReadBundleDatabase(dbpath string) (Bundles, error) { return bundles, nil } -func WriteBundleDatabase(dbpath string, bundles Bundles) { +func WriteBundleDatabase(dbpath string, bundles Bundles) error { data, err := json.MarshalIndent(bundles, "", " ") if err != nil { - log.Fatal(err) + return err } err = os.WriteFile(dbpath, data, 0644) if err != nil { - log.Fatal(err) + return err } + return nil } func BundleIter(fn func(s *Bundle) error) error { @@ -120,5 +122,6 @@ func GenerateBundle(bundle *Bundle) (bool, error) { return exitError.ExitCode() == 0, nil } } + logging.Print("Wrote EFI bundle %s\n", bundle.Output) return true, nil } diff --git a/cmd/sbctl/bundle.go b/cmd/sbctl/bundle.go index 5daa9da..b509fc8 100644 --- a/cmd/sbctl/bundle.go +++ b/cmd/sbctl/bundle.go @@ -71,7 +71,10 @@ var bundleCmd = &cobra.Command{ logging.Print("Wrote EFI bundle %s\n", bundle.Output) if saveBundle { bundles[bundle.Output] = bundle - sbctl.WriteBundleDatabase(sbctl.BundleDBPath, bundles) + err := sbctl.WriteBundleDatabase(sbctl.BundleDBPath, bundles) + if err != nil { + return err + } } return nil }, diff --git a/cmd/sbctl/remove-bundle.go b/cmd/sbctl/remove-bundle.go index 9358f92..1977f31 100644 --- a/cmd/sbctl/remove-bundle.go +++ b/cmd/sbctl/remove-bundle.go @@ -26,7 +26,10 @@ var removeBundleCmd = &cobra.Command{ os.Exit(1) } delete(bundles, args[0]) - sbctl.WriteBundleDatabase(sbctl.BundleDBPath, bundles) + err = sbctl.WriteBundleDatabase(sbctl.BundleDBPath, bundles) + if err != nil { + return err + } return nil }, } diff --git a/cmd/sbctl/sign-all.go b/cmd/sbctl/sign-all.go index 0af7ba2..18297c0 100644 --- a/cmd/sbctl/sign-all.go +++ b/cmd/sbctl/sign-all.go @@ -39,7 +39,10 @@ var signAllCmd = &cobra.Command{ } // Update checksum after we signed it - checksum := sbctl.ChecksumFile(entry.File) + checksum, err := sbctl.ChecksumFile(entry.File) + if err != nil { + return err + } entry.Checksum = checksum files[entry.File] = entry if err := sbctl.WriteFileDatabase(sbctl.DBPath, files); err != nil { diff --git a/guid.go b/guid.go index 0a25b1d..a8ab9c3 100644 --- a/guid.go +++ b/guid.go @@ -2,7 +2,6 @@ package sbctl import ( "io/ioutil" - "log" "os" "path/filepath" @@ -10,10 +9,7 @@ import ( ) func CreateUUID() []byte { - id, err := uuid.NewRandom() - if err != nil { - log.Fatal(err) - } + id, _ := uuid.NewRandom() return []byte(id.String()) } diff --git a/keys.go b/keys.go index b5e2bee..e6174b3 100644 --- a/keys.go +++ b/keys.go @@ -9,7 +9,6 @@ import ( "encoding/pem" "errors" "fmt" - "log" "math/big" "os" "os/exec" @@ -46,12 +45,9 @@ func CanVerifyFiles() error { return nil } -func CreateKey(path, name string) []byte { +func CreateKey(path, name string) ([]byte, error) { serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - log.Fatalf("Failed to generate serial number: %v", err) - } + serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit) c := x509.Certificate{ SerialNumber: serialNumber, PublicKeyAlgorithm: x509.RSA, @@ -66,45 +62,45 @@ func CreateKey(path, name string) []byte { } priv, err := rsa.GenerateKey(rand.Reader, RSAKeySize) if err != nil { - log.Fatal(err) + return nil, err } derBytes, err := x509.CreateCertificate(rand.Reader, &c, &c, &priv.PublicKey, priv) if err != nil { - log.Fatalf("Failed to create certificate: %v", err) + return nil, err } keyOut, err := os.OpenFile(fmt.Sprintf("%s.key", path), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { - log.Fatalf("Failed to open key.pem for writing: %v", err) + return nil, fmt.Errorf("Failed to open key.pem for writing: %v", err) } privBytes, err := x509.MarshalPKCS8PrivateKey(priv) if err != nil { - log.Fatalf("Unable to marshal private key: %v", err) + return nil, fmt.Errorf("Unable to marshal private key: %v", err) } if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { - log.Fatalf("Failed to write data to key.pem: %v", err) + return nil, fmt.Errorf("Failed to write data to key.pem: %v", err) } if err := keyOut.Close(); err != nil { - log.Fatalf("Error closing key.pem: %v", err) + return nil, fmt.Errorf("Error closing key.pem: %v", err) } - return derBytes + return derBytes, nil } -func SaveKey(k []byte, path string) { +func SaveKey(k []byte, path string) error { err := os.WriteFile(fmt.Sprintf("%s.der", path), k, 0644) if err != nil { - log.Fatal(err) + return err } certOut, err := os.Create(fmt.Sprintf("%s.pem", path)) if err != nil { - log.Fatalf("Failed to open cert.pem for writing: %v", err) + return err } if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: k}); err != nil { - log.Fatalf("Failed to write data to cert.pem: %v", err) + return err } if err := certOut.Close(); err != nil { - log.Fatalf("Error closing cert.pem: %v", err) + return err } - + return nil } func KeyToSiglist(UUID []byte, input string) error { @@ -180,8 +176,11 @@ func SignFile(key, cert, file, output, checksum string) error { if err != nil { return err } - - if ok && ChecksumFile(file) == checksum { + chk, err := ChecksumFile(file) + if err != nil { + return err + } + if ok && chk == checksum { return ErrAlreadySigned } @@ -248,7 +247,10 @@ func InitializeSecureBootKeys(output string) error { path := filepath.Join(output, "keys", key.Key) os.MkdirAll(path, os.ModePerm) keyPath := filepath.Join(path, key.Key) - pk := CreateKey(keyPath, key.Description) + pk, err := CreateKey(keyPath, key.Description) + if err != nil { + return err + } SaveKey(pk, keyPath) derSiglist := fmt.Sprintf("%s.der", keyPath) if err := KeyToSiglist(uuid, derSiglist); err != nil { diff --git a/sbctl.go b/sbctl.go index 69e98ed..070ca45 100644 --- a/sbctl.go +++ b/sbctl.go @@ -88,7 +88,7 @@ func GetESP() string { func Sign(file, output string, enroll bool) error { file, err := filepath.Abs(file) if err != nil { - log.Fatal(err) + return err } if output == "" { @@ -96,7 +96,7 @@ func Sign(file, output string, enroll bool) error { } else { output, err = filepath.Abs(output) if err != nil { - log.Fatal(err) + return err } } @@ -112,7 +112,10 @@ func Sign(file, output string, enroll bool) error { if err != nil { return err } - checksum := ChecksumFile(file) + checksum, err := ChecksumFile(file) + if err != nil { + return err + } entry.Checksum = checksum files[file] = entry if err := WriteFileDatabase(DBPath, files); err != nil { @@ -127,7 +130,10 @@ func Sign(file, output string, enroll bool) error { } if enroll { - checksum := ChecksumFile(file) + checksum, err := ChecksumFile(file) + if err != nil { + return err + } files[file] = &SigningEntry{File: file, OutputFile: output, Checksum: checksum} if err := WriteFileDatabase(DBPath, files); err != nil { return err diff --git a/util.go b/util.go index 03033d1..ebc77a0 100644 --- a/util.go +++ b/util.go @@ -6,21 +6,20 @@ import ( "encoding/hex" "errors" "io" - "log" "os" "path/filepath" "strings" ) -func ChecksumFile(file string) string { +func ChecksumFile(file string) (string, error) { hasher := sha256.New() s, err := os.ReadFile(file) if err != nil { - log.Fatal(err) + return "", err } hasher.Write(s) - return hex.EncodeToString(hasher.Sum(nil)) + return hex.EncodeToString(hasher.Sum(nil)), nil } func ReadOrCreateFile(filePath string) ([]byte, error) { From ba0cee81154ed3b5d012f4893cd32587ddc0e72b Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Thu, 20 May 2021 01:12:48 +0200 Subject: [PATCH 41/43] Make lint happy Signed-off-by: Morten Linderud --- keys.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/keys.go b/keys.go index e6174b3..b00ad27 100644 --- a/keys.go +++ b/keys.go @@ -70,17 +70,17 @@ func CreateKey(path, name string) ([]byte, error) { } keyOut, err := os.OpenFile(fmt.Sprintf("%s.key", path), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { - return nil, fmt.Errorf("Failed to open key.pem for writing: %v", err) + return nil, fmt.Errorf("failed to open key.pem for writing: %v", err) } privBytes, err := x509.MarshalPKCS8PrivateKey(priv) if err != nil { - return nil, fmt.Errorf("Unable to marshal private key: %v", err) + return nil, fmt.Errorf("unable to marshal private key: %v", err) } if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { - return nil, fmt.Errorf("Failed to write data to key.pem: %v", err) + return nil, fmt.Errorf("failed to write data to key.pem: %v", err) } if err := keyOut.Close(); err != nil { - return nil, fmt.Errorf("Error closing key.pem: %v", err) + return nil, fmt.Errorf("error closing key.pem: %v", err) } return derBytes, nil } From 550b4e63650759876f9d3103f018ab3f1599576b Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Sat, 22 May 2021 23:41:46 +0200 Subject: [PATCH 42/43] Move global flags to persistent Signed-off-by: Morten Linderud --- cmd/sbctl/main.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/sbctl/main.go b/cmd/sbctl/main.go index 5b4286b..9daa7ac 100644 --- a/cmd/sbctl/main.go +++ b/cmd/sbctl/main.go @@ -33,7 +33,7 @@ var ( ) func baseFlags(cmd *cobra.Command) { - flags := cmd.Flags() + flags := cmd.PersistentFlags() flags.BoolVar(&cmdOptions.JsonOutput, "json", false, "Output as json") cmd.PreRun = func(cmd *cobra.Command, args []string) { @@ -54,10 +54,11 @@ func JsonOut(v interface{}) error { func main() { for _, cmd := range CliCommands { - baseFlags(cmd.Cmd) rootCmd.AddCommand(cmd.Cmd) } + baseFlags(rootCmd) + // This returns i the flag is not found with a specific error rootCmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error { cmd.Println(err) From ae1aec15fbd1398328da3ee0753afaf307a3985d Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Sun, 30 May 2021 15:18:09 +0200 Subject: [PATCH 43/43] sbctl: Ensure all commands inherit stdout turning off Signed-off-by: Morten Linderud --- cmd/sbctl/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/sbctl/main.go b/cmd/sbctl/main.go index 9daa7ac..d427f46 100644 --- a/cmd/sbctl/main.go +++ b/cmd/sbctl/main.go @@ -36,7 +36,7 @@ func baseFlags(cmd *cobra.Command) { flags := cmd.PersistentFlags() flags.BoolVar(&cmdOptions.JsonOutput, "json", false, "Output as json") - cmd.PreRun = func(cmd *cobra.Command, args []string) { + cmd.PersistentPreRun = func(cmd *cobra.Command, args []string) { if cmdOptions.JsonOutput { logging.PrintOff() }