Signed-off-by: Morten Linderud <morten@linderud.pw>
This commit is contained in:
Morten Linderud 2020-05-03 19:41:09 +02:00
commit 0b5d4a46ea
No known key found for this signature in database
GPG Key ID: E742683BA08CB2FF
16 changed files with 1413 additions and 0 deletions

18
LICENSE Normal file
View File

@ -0,0 +1,18 @@
Copyright 2020 Morten Linderud
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

24
Makefile Normal file
View File

@ -0,0 +1,24 @@
PROGNM ?= sbctl
PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin
SHRDIR ?= $(PREFIX)/share
DOCDIR ?= $(PREFIX)/share/doc
MANDIR ?= $(PREFIX)/share/man
MANS = $(basename $(wildcard docs/*.txt))
all: man sbctl
man: $(MANS)
$(MANS):
docs/sbctl.%: docs/sbctl.%.txt docs/asciidoc.conf
a2x --no-xmllint --asciidoc-opts="-f docs/asciidoc.conf" -d manpage -f manpage -D docs $<
install: man
install -Dm755 sbctl -t $(DESTDIR)$(BINDIR)
for manfile in $(MANS); do \
install -Dm644 $$manfile -t $(DESTDIR)$(MANDIR)/man$${manfile##*.}; \
done;
install -Dm644 LICENSE -t $(DESTDIR)$(SHRDIR)/licenses/$(PROGNM)
clean:
rm -f $(MANS)

159
README.md Normal file
View File

@ -0,0 +1,159 @@
sbctl - Secure Boot Manager
===========================
The goal of the project is to have one consisten UI to manage secure boot keys.
# Features
* Manages secure boot keys
* Live enrollment of secure boot keys
* Signing database to help keep track of files to sign
* Verify ESP of files missing signatures
* EFI stub generation
# Roadmap
* Convert to use [goefi](https://github.com/Foxboron/goefi) instead of relying on `sbsigntoosl`
* Key rotation
* Customize keys
* Secure the keys
# Usage
```
$ sbctl
Secure Boot key manager
Usage:
sbctl [command]
Available Commands:
bundle Bundle the needed files for an EFI stub image
create-keys Create a set of secure boot signing keys
enroll-keys Enroll the current keys to EFI
generate-bundles Generate all EFI stub bundles
help Help about any command
list-bundles List stored bundles
list-files List enrolled files
remove-bundle Remove bundle from database
sign Sign a file with secure boot keys
sign-all Sign all enrolled files with secure boot keys
status Show current boot status
verify-esp Find and check if files in the ESP are signed or not
Flags:
-h, --help help for sbctl
Use "sbctl [command] --help" for more information about a command.
```
## Key creation and enrollment
```
$ sbctl status
==> WARNING: Setup Mode: Enabled
==> WARNING: Secure Boot: Disabled
$ sbctl create-keys
==> Creating secure boot keys...
-> Using UUID d6e9af79-c6b5-4b43-b893-dbb7e6570142...
==> Signing /usr/share/secureboot/keys/PK/PK.der.esl with /usr/share/secureboot/keys/PK/PK.key...
==> Signing /usr/share/secureboot/keys/KEK/KEK.der.esl with /usr/share/secureboot/keys/PK/PK.key...
==> Signing /usr/share/secureboot/keys/db/db.der.esl with /usr/share/secureboot/keys/KEK/KEK.key...
$ sbctl enroll-keys
==> Syncing /usr/share/secureboot/keys to EFI variables...
==> Synced keys!
$ sbctl status
==> Setup Mode: Disabled
==> WARNING: Secure Boot: Disabled
# Reboot!
$ sbctl status
==> Setup Mode: Disabled
==> Secure Boot: Enabled
```
## Signatures
```
$ sbctl verify
==> Verifying file database and EFI images in /efi...
-> WARNING: /boot/vmlinuz-linux is not signed
-> WARNING: /efi/EFI/BOOT/BOOTX64.EFI is not signed
-> WARNING: /efi/EFI/BOOT/KeyTool-signed.efi is not signed
-> WARNING: /efi/EFI/Linux/linux-linux.efi is not signed
-> WARNING: /efi/EFI/arch/fwupdx64.efi is not signed
-> WARNING: /efi/EFI/systemd/systemd-bootx64.efi is not signed
$ sbctl sign -s /efi/EFI/BOOT/BOOTX64.EFI
==> Signing /efi/EFI/BOOT/BOOTX64.EFI...
$ sbctl sign -s /efi/EFI/arch/fwupdx64.efi
==> Signing /efi/EFI/arch/fwupdx64.efi...
$ sbctl sign -s /efi/EFI/systemd/systemd-bootx64.efi
==> Signing /efi/EFI/systemd/systemd-bootx64.efi...
$ sbctl sign -s /usr/lib/fwupd/efi/fwupdx64.efi -o /usr/lib/fwupd/efi/fwupdx64.efi.signed
==> Signing /usr/lib/fwupd/efi/fwupdx64.efi...
$ sbctl verify
==> Verifying file database and EFI images in /efi...
-> /usr/lib/fwupd/efi/fwupdx64.efi.signed is signed
-> /efi/EFI/BOOT/BOOTX64.EFI is signed
-> /efi/EFI/arch/fwupdx64.efi is signed
-> /efi/EFI/systemd/systemd-bootx64.efi is signed
-> WARNING: /boot/vmlinuz-linux is not signed
-> WARNING: /efi/EFI/BOOT/KeyTool-signed.efi is not signed
-> WARNING: /efi/EFI/Linux/linux-linux.efi is not signed
$ sbctl list-files
==> File: /efi/EFI/BOOT/BOOTX64.EFI
-> Output: /efi/EFI/BOOT/BOOTX64.EFI
==> File: /efi/EFI/arch/fwupdx64.efi
-> Output: /efi/EFI/arch/fwupdx64.efi
==> File: /efi/EFI/systemd/systemd-bootx64.efi
-> Output: /efi/EFI/systemd/systemd-bootx64.efi
==> File: /efi/vmlinuz-linux
-> Output: /efi/vmlinuz-linux
==> File: /usr/lib/fwupd/efi/fwupdx64.efi
-> Output: /usr/lib/fwupd/efi/fwupdx64.efi.signed
```
## Generate EFI Stub
```
$ sbctl bundle -s -i /boot/intel-ucode.img \
-l /usr/share/systemd/bootctl/splash-arch.bmp \
-k /boot/vmlinuz-linux \
-f /boot/initramfs-linux.img \
/boot/EFI/Linux/linux-linux.efi
==> Wrote EFI bundle /boot/EFI/Linux/linux-linux.efi
==> Bundle: /boot/EFI/Linux/linux-linux.efi
-> Intel Microcode: /boot/intel-ucode.img
-> Kernel Image: /boot/vmlinuz-linux
-> Initramfs Image: /boot/initramfs-linux.img
-> Cmdline: /proc/cmdline
-> OS Relase: /usr/lib/os-release
-> EFI Stub Image: /usr/lib/systemd/boot/efi/linuxx64.efi.stub
-> ESP Location: /efi
-> Splash Image: /usr/share/systemd/bootctl/splash-arch.bmp
-> Output: /boot/EFI/Linux/linux-linux.efi
$ sbctl list-bundles
==> Bundle: /boot/EFI/Linux/linux-linux.efi
-> Intel Microcode: /boot/intel-ucode.img
-> Kernel Image: /boot/vmlinuz-linux
-> Initramfs Image: /boot/initramfs-linux.img
-> Cmdline: /proc/cmdline
-> OS Relase: /usr/lib/os-release
-> EFI Stub Image: /usr/lib/systemd/boot/efi/linuxx64.efi.stub
-> ESP Location: /efi
-> Splash Image: /usr/share/systemd/bootctl/splash-arch.bmp
-> Output: /boot/EFI/Linux/linux-linux.efi
$ sbctl generate-bundles
==> Generating EFI bundles....
==> Wrote EFI bundle /boot/EFI/Linux/linux-linux.efi
```

115
bundles.go Normal file
View File

@ -0,0 +1,115 @@
package sbctl
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
)
type Bundle struct {
Output string `json:"output"`
IntelMicrocode string `json:"intel_microcode"`
AMDMicrocode string `json:"amd_microcode"`
KernelImage string `json:"kernel_image"`
Initramfs string `json:"initramfs"`
Cmdline string `json:"cmdline"`
Splash string `json:"splash"`
OSRelease string `json:"os_release"`
EFIStub string `json:"efi_stub"`
ESP string `json:"esp"`
}
type Bundles map[string]*Bundle
var BundleDBPath = filepath.Join(DatabasePath, "bundles.db")
func ReadBundleDatabase(dbpath string) Bundles {
bundles := make(Bundles)
os.MkdirAll(DatabasePath, os.ModePerm)
if _, err := os.Stat(BundleDBPath); os.IsNotExist(err) {
file, err := os.Create(BundleDBPath)
if err != nil {
log.Fatal(err)
}
file.Close()
}
f, err := ioutil.ReadFile(dbpath)
if err != nil {
log.Fatal(err)
}
json.Unmarshal(f, &bundles)
return bundles
}
func WriteBundleDatabase(dbpath string, bundles Bundles) {
data, err := json.MarshalIndent(bundles, "", " ")
if err != nil {
log.Fatal(err)
}
err = ioutil.WriteFile(dbpath, data, 0644)
if err != nil {
log.Fatal(err)
}
}
func NewBundle() *Bundle {
esp := GetESP()
return &Bundle{
Output: "",
IntelMicrocode: "",
AMDMicrocode: "",
KernelImage: filepath.Join(esp, "vmlinuz-linux"),
Initramfs: filepath.Join(esp, "initramfs-linux.img"),
Cmdline: "/proc/cmdline",
Splash: "",
OSRelease: "/usr/lib/os-release",
EFIStub: "/usr/lib/systemd/boot/efi/linuxx64.efi.stub",
ESP: esp,
}
}
func GenerateBundle(bundle *Bundle) bool {
args := ""
args += fmt.Sprintf("--add-section .osrel=%s --change-section-vma .osrel=0x20000 ", bundle.OSRelease)
args += fmt.Sprintf("--add-section .cmdline=%s --change-section-vma .cmdline=0x30000 ", bundle.Cmdline)
if bundle.Splash != "" {
args += fmt.Sprintf("--add-section .splash=%s --change-section-vma .splash=0x40000 ", bundle.Splash)
}
args += fmt.Sprintf("--add-section .linux=%s --change-section-vma .linux=0x2000000 ", bundle.KernelImage)
args += fmt.Sprintf("--add-section .initrd=%s --change-section-vma .initrd=0x3000000 ", bundle.Initramfs)
args += fmt.Sprintf("%s %s", bundle.EFIStub, bundle.Output)
cmd := exec.Command("objcopy", strings.Split(args, " ")...)
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
return exitError.ExitCode() == 0
}
}
msg.Printf("Wrote EFI bundle %s", bundle.Output)
return true
}
func FormatBundle(name string, bundle *Bundle) {
msg.Printf("Bundle: %s", name)
if bundle.AMDMicrocode != "" {
msg2.Printf("AMD Microcode: %s", 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 Relase: %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)
}
msg2.Printf("Output: %s", bundle.Output)
}

239
cmd/main.go Normal file
View File

@ -0,0 +1,239 @@
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/foxboron/sbctl"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "sbctl",
Short: "Secure Boot key manager",
}
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()
},
}
}
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()
},
}
}
func signCmd() *cobra.Command {
var save bool
var output string
cmd := &cobra.Command{
Use: "sign",
Short: "Sign a file with secure boot keys",
Run: func(cmd *cobra.Command, args []string) {
if len(args) < 1 {
log.Fatalf("Requires a file to sign...\n")
}
sbctl.Sign(args[0], output, save)
},
}
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 {
return &cobra.Command{
Use: "sign-all",
Short: "Sign all enrolled files with secure boot keys",
Run: func(cmd *cobra.Command, args []string) {
files := sbctl.ReadFileDatabase(sbctl.DBPath)
for _, entry := range files {
sbctl.SignFile(sbctl.DBKey, sbctl.DBCert, entry.File, entry.OutputFile)
}
},
}
}
func removeFileCmd() *cobra.Command {
return &cobra.Command{
Use: "remove-file",
Short: "Remove file from database",
Run: func(cmd *cobra.Command, args []string) {
if len(args) < 1 {
log.Fatal("Need to specify file")
}
files := sbctl.ReadFileDatabase(sbctl.DBPath)
delete(files, args[0])
sbctl.WriteFileDatabase(sbctl.DBPath, files)
},
}
}
func statusCmd() *cobra.Command {
return &cobra.Command{
Use: "status",
Short: "Show current boot status",
Run: func(cmd *cobra.Command, args []string) {
sbctl.CheckStatus()
},
}
}
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) {
sbctl.VerifyESP()
},
}
}
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
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",
Run: func(cmd *cobra.Command, args []string) {
if len(args) < 1 {
log.Fatalf("Requires a file to sign...\n")
}
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) {
log.Fatalf("%s does not exist!", path)
os.Exit(1)
}
}
bundle := sbctl.NewBundle()
bundle.Output = args[0]
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
sbctl.CreateBundle(*bundle)
if save {
bundles := sbctl.ReadBundleDatabase(sbctl.BundleDBPath)
bundles[bundle.Output] = bundle
sbctl.WriteBundleDatabase(sbctl.BundleDBPath, bundles)
sbctl.FormatBundle(bundle.Output, bundle)
}
},
}
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", filepath.Join(esp, "vmlinuz-linux"), "Kernel image location")
f.StringVarP(&cmdline, "cmdline", "c", "/proc/cmdline", "Cmdline location")
f.StringVarP(&initramfs, "initramfs", "f", filepath.Join(esp, "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",
Run: func(cmd *cobra.Command, args []string) {
sbctl.GenerateAllBundles()
},
}
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",
Run: func(cmd *cobra.Command, args []string) {
sbctl.ListBundles()
},
}
}
func removeBundleCmd() *cobra.Command {
return &cobra.Command{
Use: "remove-bundle",
Short: "Remove bundle from database",
Run: func(cmd *cobra.Command, args []string) {
if len(args) < 1 {
log.Fatal("Need to specify file")
}
bundles := sbctl.ReadBundleDatabase(sbctl.BundleDBPath)
delete(bundles, args[0])
sbctl.WriteBundleDatabase(sbctl.BundleDBPath, bundles)
},
}
}
func main() {
rootCmd.PersistentPreRun = func(_ *cobra.Command, args []string) {
if os.Geteuid() != 0 {
fmt.Println("Needs to be executed as root")
os.Exit(1)
}
}
rootCmd.AddCommand(createKeysCmd())
rootCmd.AddCommand(enrollKeysCmd())
rootCmd.AddCommand(signCmd())
rootCmd.AddCommand(signAllCmd())
rootCmd.AddCommand(statusCmd())
rootCmd.AddCommand(verifyCmd())
rootCmd.AddCommand(listFilesCmd())
rootCmd.AddCommand(bundleCmd())
rootCmd.AddCommand(generateBundlesCmd())
rootCmd.AddCommand(removeBundleCmd())
rootCmd.AddCommand(listBundlesCmd())
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}

1
cmds.go Normal file
View File

@ -0,0 +1 @@
package sbctl

View File

@ -0,0 +1,12 @@
[Trigger]
Type = File
Operation = Install
Operation = Upgrade
Operation = Remove
Target = boot/*
Target = usr/lib/modules/*/vmlinuz
[Action]
Description = Generating EFI Stub bundles...
When = PostTransaction
Exec = /usr/bin/sbctl generate-bundles

View File

@ -0,0 +1,13 @@
[Trigger]
Type = File
Operation = Install
Operation = Upgrade
Operation = Remove
Target = boot/*
Target = boot/EFI/*
Target = usr/lib/modules/*/vmlinuz
[Action]
Description = Signing EFI binaries...
When = PostTransaction
Exec = /usr/bin/sbctl sign-all

47
database.go Normal file
View File

@ -0,0 +1,47 @@
package sbctl
import (
"encoding/json"
"io/ioutil"
"log"
"os"
"path/filepath"
)
type SigningEntry struct {
File string `json:"file"`
OutputFile string `json:"output_file"`
}
type SigningEntries map[string]*SigningEntry
var DBPath = filepath.Join(DatabasePath, "files.db")
func ReadFileDatabase(dbpath string) SigningEntries {
files := make(SigningEntries)
os.MkdirAll(DatabasePath, os.ModePerm)
if _, err := os.Stat(DBPath); os.IsNotExist(err) {
file, err := os.Create(DBPath)
if err != nil {
log.Fatal(err)
}
file.Close()
}
f, err := ioutil.ReadFile(dbpath)
if err != nil {
log.Fatal(err)
}
json.Unmarshal(f, &files)
return files
}
func WriteFileDatabase(dbpath string, files SigningEntries) {
data, err := json.MarshalIndent(files, "", " ")
if err != nil {
log.Fatal(err)
}
err = ioutil.WriteFile(dbpath, data, 0644)
if err != nil {
log.Fatal(err)
}
}

37
docs/asciidoc.conf Normal file
View File

@ -0,0 +1,37 @@
## linkman: macro
# Inspired by/borrowed from the GIT source tree at Documentation/asciidoc.conf
#
# Usage: linkman:command[manpage-section]
#
# Note, {0} is the manpage section, while {target} is the command.
#
# Show man link as: <command>(<section>); if section is defined, else just show
# the command.
[macros]
(?su)[\\]?(?P<name>linkman):(?P<target>\S*?)\[(?P<attrlist>.*?)\]=
[attributes]
asterisk=&#42;
plus=&#43;
caret=&#94;
startsb=&#91;
endsb=&#93;
backslash=&#92;
tilde=&#126;
apostrophe=&#39;
backtick=&#96;
litdd=&#45;&#45;
ifdef::backend-docbook[]
[linkman-inlinemacro]
{0%{target}}
{0#<citerefentry>}
{0#<refentrytitle>{target}</refentrytitle><manvolnum>{0}</manvolnum>}
{0#</citerefentry>}
endif::backend-docbook[]
ifdef::backend-xhtml11[]
[linkman-inlinemacro]
<a href="{target}.{0}.html">{target}{0?({0})}</a>
endif::backend-xhtml11[]

113
docs/sbctl.8.txt Normal file
View File

@ -0,0 +1,113 @@
sbctl(8)
========
Name
----
sbctl - Secure Boot manager
Synopsis
--------
'sbctl' <command>
Description
-----------
'sbctl' aims to provide a full integrated secure boot experience.
EFI signing commands
--------------------
**status**::
Shows the current secure boot status of the system. It checks if you are
currently booted in UEFI with Secure Boot, and wheter or not Setup Mode
has been enabled.
**create-keys**::
Creates a set of signing keys used to sign EFI binaries. Currently it
will create the following keys:
* Platform Key
* Key Exchange key
* Signature Database Key
**enroll-keys**::
It will first attempt to use `sbkeysync` to live enroll the
required keys. This requires Setup Mode to be active.
**sign** <FILE>...::
Signs a EFI binary with the created key. The file will be checked for
valid signatures to avoid duplicates.
**sign-all**::
Signs all enrolled EFI binaries.
**remove-file** <FILE>::
Removes the file from the signing database.
**verify**::
Looks for EFI binaries with the mime type application/x-dosexec and
checks if they have been signed with the Signature Database Key.
**help** <FILE>...::
Displays a help message.
EFI binary commands
------------------
**bundle** [FLAG] <NAME> <VMLINUZ PATH> <INITRAMFS PATH>::
Creates a bundle that should produce EFI binaries. This is usefull if
you want to sign your initramfs along with your kernel.
* -i|--intel <PATH> - Include Intel microcode
* -a|--amd <PATH> - Include AMD microcode
**remove-bundle** <NAME>::
Removes a bundle from the list.
**generate-bundles**::
This command generates all bundles and puts them into
**$ESP/Linux/linux-$bundlename.efi**. These are not signed and it's
expected that you enroll them yourself.
**list-bundles**::
List all registed bundles to generate.
Environment Variables
---------------------
**ESP**::
Overrides the ESP location used for enrolling keys, and finding EFI
binaries to sign. Defaults to /boot
**EFI_ROLLER_ROOT**::
Override the default efi-roller location. Defaults to /var/lib/efi-roller
Files
----
**/var/lib/efi-roller**::
Default storage directory.
**/var/lib/efi-roller/GUID**::
Owner identification. This is a randomly generated UUID.
**/var/lib/efi-roller/files.db**::
Contains a list of EFI binaries to be signed by the generated key.
**/var/lib/efi-roller/bundles.db**::
Contains a list of EFI bundles to be generated.
**/var/lib/efi-roller/keys/db/DB.{auth,cer,crt,esl,key}**::
Contains the Signature Database key used for signing EFI binaries.
**/var/lib/efi-roller/keys/kek/KEK.{auth,cer,crt,esl,key}**::
Contains the Key Exchange Key.
**/var/lib/efi-roller/keys/pk/PK.{auth,cer,crt,esl,key}**::
Contains the Platform Key.
See Also
--------
linkman:sbsign[1]

8
go.mod Normal file
View File

@ -0,0 +1,8 @@
module github.com/foxboron/sbctl
go 1.14
require (
github.com/foxboron/goefi v0.0.0-20200425230843-adb2e3d38c9d
github.com/spf13/cobra v1.0.0
)

128
go.sum Normal file
View File

@ -0,0 +1,128 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
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/foxboron/goefi v0.0.0-20200425230843-adb2e3d38c9d h1:lCRX0K9pVoRx7uXogj+Hy6Of1qhueyVnorTVnTetQh0=
github.com/foxboron/goefi v0.0.0-20200425230843-adb2e3d38c9d/go.mod h1:JOXcrLp1yt4pjXscnK6Pd/gTgtRvQXsVS2aeok+bSQo=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
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/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=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
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/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=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

239
keys.go Normal file
View File

@ -0,0 +1,239 @@
package sbctl
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io/ioutil"
"log"
"math/big"
"os"
"os/exec"
"path/filepath"
"strings"
)
var RSAKeySize = 4096
var (
DatabasePath = "/usr/share/secureboot/"
KeysPath = filepath.Join(DatabasePath, "keys")
PKKey = filepath.Join(KeysPath, "PK", "PK.key")
PKCert = filepath.Join(KeysPath, "PK", "PK.pem")
DBKey = filepath.Join(KeysPath, "db", "db.key")
DBCert = filepath.Join(KeysPath, "db", "db.pem")
)
func CreateKey(path, name string) []byte {
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)
}
c := x509.Certificate{
SerialNumber: serialNumber,
PublicKeyAlgorithm: x509.RSA,
SignatureAlgorithm: x509.SHA256WithRSA,
Subject: pkix.Name{
Country: []string{name},
},
}
priv, err := rsa.GenerateKey(rand.Reader, RSAKeySize)
if err != nil {
log.Fatal(err)
}
derBytes, err := x509.CreateCertificate(rand.Reader, &c, &c, &priv.PublicKey, priv)
if err != nil {
log.Fatalf("Failed to create certificate: %v", 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)
}
privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
if err != nil {
log.Fatalf("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)
}
if err := keyOut.Close(); err != nil {
log.Fatalf("Error closing key.pem: %v", err)
}
return derBytes
}
func SaveKey(k []byte, path string) {
err := ioutil.WriteFile(fmt.Sprintf("%s.der", path), k, 0644)
if err != nil {
log.Fatal(err)
}
certOut, err := os.Create(fmt.Sprintf("%s.pem", path))
if err != nil {
log.Fatalf("Failed to open cert.pem for writing: %v", 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)
}
if err := certOut.Close(); err != nil {
log.Fatalf("Error closing cert.pem: %v", err)
}
}
func CreateUUID() []byte {
out, err := exec.Command("uuidgen").Output()
if err != nil {
log.Fatal(err)
}
return bytes.TrimSuffix(out, []byte("\n"))
}
func KeyToSiglist(UUID []byte, input string) []byte {
msg.Printf("Create EFI signature list %s.esl...", input)
args := fmt.Sprintf("--owner %s --type x509 --output %s.esl %s", UUID, input, input)
fmt.Println(args)
out, err := exec.Command("/usr/bin/sbsiglist", strings.Split(args, " ")...).Output()
if err != nil {
log.Fatalf("Failed creating signature list: %s", err)
}
return out
}
func SignEFIVariable(key, cert, varname, vardatafile, output string) []byte {
msg.Printf("Signing %s with %s...", vardatafile, key)
args := fmt.Sprintf("--key %s --cert %s --output %s %s %s", key, cert, output, varname, vardatafile)
fmt.Println(args)
out, err := exec.Command("/usr/bin/sbvarsign", strings.Split(args, " ")...).Output()
if err != nil {
log.Fatalf("Failed signing EFI variable: %s", err)
}
return out
}
func SBKeySync(dir string) bool {
msg.Printf("Syncing %s to EFI variables...", dir)
args := fmt.Sprintf("--pk --verbose --keystore %s", dir)
cmd := exec.Command("sbkeysync", strings.Split(args, " ")...)
var out bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &out
if err := cmd.Run(); err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
return exitError.ExitCode() == 0
}
}
stdout := string(out.Bytes())
for _, line := range strings.Split(stdout, "\n") {
if strings.Contains(line, "Operation not permitted") {
fmt.Println(stdout)
return false
}
}
return true
}
func VerifyFile(cert, file string) bool {
args := fmt.Sprintf("--cert %s %s", cert, file)
cmd := exec.Command("sbverify", strings.Split(args, " ")...)
if err := cmd.Run(); err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
return exitError.ExitCode() == 0
}
}
return true
}
func SignFile(key, cert, file, output string) []byte {
// Lets check if we have signed it already...
if VerifyFile(cert, file) {
msg.Printf("%s has been signed...", file)
return []byte{}
}
msg.Printf("Signing %s...", file)
args := fmt.Sprintf("--key %s --cert %s --output %s %s", key, cert, output, file)
out, err := exec.Command("sbsign", strings.Split(args, " ")...).Output()
if err != nil {
log.Fatalf("Failed signing file: %s", err)
}
return out
}
var SecureBootKeys = []struct {
Key string
Description string
// Path to the key we sign it with
SignedWith string
}{
{
Key: "PK",
Description: "Platform Key",
SignedWith: "PK",
},
{
Key: "KEK",
Description: "Key Exchange Key",
SignedWith: "PK",
},
{
Key: "db",
Description: "Database Key",
SignedWith: "KEK",
},
// Haven't used this yet so WIP
// {
// Key: "dbx",
// Description: "Forbidden Database Key",
// SignedWith: "KEK",
// },
}
func CheckIfKeysInitialized(output string) bool {
for _, key := range SecureBootKeys {
path := filepath.Join(output, key.Key)
if _, err := os.Stat(path); os.IsNotExist(err) {
return false
}
}
return true
}
func InitializeSecureBootKeys(output string) {
os.MkdirAll(output, os.ModePerm)
var uuid []byte
guidPath := filepath.Join(output, "GUID")
if _, err := os.Stat(guidPath); os.IsNotExist(err) {
uuid = CreateUUID()
msg2.Printf("Created UUID %s...", uuid)
err := ioutil.WriteFile(guidPath, uuid, 0600)
if err != nil {
log.Fatal(err)
}
} else {
uuid, err = ioutil.ReadFile(guidPath)
if err != nil {
log.Fatal(err)
}
msg2.Printf("Using UUID %s...", uuid)
}
// Create the directories we need and keys
for _, key := range SecureBootKeys {
path := filepath.Join(output, "keys", key.Key)
os.MkdirAll(path, os.ModePerm)
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
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))
}
}

59
log.go Normal file
View File

@ -0,0 +1,59 @@
package sbctl
import (
"bytes"
"fmt"
"log"
"os"
"os/exec"
"strings"
)
var (
plain *log.Logger
msg *log.Logger
msg2 *log.Logger
warning *log.Logger
warning2 *log.Logger
err *log.Logger
err2 *log.Logger
)
func GetColor(args string) string {
out, _ := exec.Command("tput", strings.Split(args, " ")...).Output()
return string(bytes.TrimSuffix(out, []byte("\n")))
}
func init() {
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 = fmt.Sprintf("%s", off)
)
plainfmt := fmt.Sprintf("%s%s ", prefix, bold)
plain = log.New(os.Stdout, plainfmt, 0)
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)
err = 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)
}

201
sbctl.go Normal file
View File

@ -0,0 +1,201 @@
package sbctl
import (
"bytes"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/foxboron/goefi/efi/attributes"
)
// Functions that doesn't fit anywhere else
// Veryvery simple check
func GetESP() string {
if _, err := os.Stat("/efi"); !os.IsNotExist(err) {
return "/efi"
}
out, err := exec.Command("lsblk", "-o", "PARTTYPE,MOUNTPOINT").Output()
if err != nil {
log.Fatal(err)
}
data := string(out)
for _, lines := range strings.Split(data, "\n") {
if len(lines) < 1 {
continue
}
l := strings.Split(lines, " ")
if len(l) != 2 {
continue
}
if l[0] == "c12a7328-f81f-11d2-ba4b-00a0c93ec93b" {
return l[1]
}
}
return ""
}
func VerifyESP() {
espPath := GetESP()
files := ReadFileDatabase(DBPath)
msg.Printf("Verifying file database and EFI images in %s...", espPath)
for _, file := range files {
if VerifyFile(DBCert, file.OutputFile) {
msg2.Printf("%s is signed\n", file.OutputFile)
} else {
warning2.Printf("%s is not signed\n", file.OutputFile)
}
}
err := filepath.Walk(espPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
out, err := exec.Command("file", "-b", "--mime-type", path).Output()
if err != nil {
log.Fatal(err)
}
out = bytes.TrimSuffix(out, []byte("\n"))
if string(out) != "application/x-dosexec" {
return nil
}
if _, ok := files[path]; ok {
return nil
}
if VerifyFile(DBCert, path) {
msg2.Printf("%s is signed\n", path)
} else {
warning2.Printf("%s is not signed\n", path)
}
return nil
})
if err != nil {
log.Println(err)
}
}
func Sign(file, output string, enroll bool) {
file, err := filepath.Abs(file)
if err != nil {
log.Fatal(err)
}
if output == "" {
output = file
}
files := ReadFileDatabase(DBPath)
if entry, ok := files[file]; ok {
SignFile(DBKey, DBCert, entry.File, entry.OutputFile)
} else {
SignFile(DBKey, DBCert, file, output)
}
if enroll {
files[file] = &SigningEntry{File: file, OutputFile: output}
WriteFileDatabase(DBPath, files)
}
}
func ListFiles() {
files := ReadFileDatabase(DBPath)
for path, s := range files {
msg.Printf("File: %s", path)
msg2.Printf("Output: %s", s.OutputFile)
}
}
func CheckStatus() {
if _, err := os.Stat("/sys/firmware/efi/efivars"); os.IsNotExist(err) {
warning.Println("System is not booted with UEFI!")
os.Exit(1)
}
if sm, err := attributes.ReadEfivars("SetupMode"); err == nil {
if sm.Data[0] == 1 {
warning.Println("Setup Mode: Enabled")
} else {
msg.Println("Setup Mode: Disabled")
}
}
if sb, err := attributes.ReadEfivars("SecureBoot"); err == nil {
if sb.Data[0] == 1 {
msg.Println("Secure Boot: Enabled")
} else {
warning.Println("Secure Boot: Disabled")
}
}
}
func CreateKeys() {
if !CheckIfKeysInitialized(KeysPath) {
msg.Printf("Creating secure boot keys...")
InitializeSecureBootKeys(DatabasePath)
} else {
msg.Printf("Secure boot keys has been created")
}
}
func SyncKeys() {
synced := SBKeySync(KeysPath)
if !synced {
err.Println("Couldn't sync keys")
os.Exit(1)
} else {
msg.Println("Synced keys!")
}
}
func CombineFiles(microcode, initramfs string) *os.File {
tmpFile, e := ioutil.TempFile("/var/tmp", "initramfs-")
if e != nil {
err.Println("Cannot create temporary file", e)
}
one, _ := os.Open(microcode)
defer one.Close()
two, _ := os.Open(initramfs)
defer two.Close()
_, e = io.Copy(tmpFile, one)
if e != nil {
log.Fatalln("failed to append microcode file to output:", err)
}
_, e = io.Copy(tmpFile, two)
if e != nil {
log.Fatalln("failed to append initramfs file to output:", err)
}
return tmpFile
}
func CreateBundle(bundle Bundle) {
if bundle.IntelMicrocode != "" {
tmpFile := CombineFiles(bundle.IntelMicrocode, bundle.Initramfs)
defer os.Remove(tmpFile.Name())
bundle.Initramfs = tmpFile.Name()
}
if bundle.AMDMicrocode != "" {
tmpFile := CombineFiles(bundle.AMDMicrocode, bundle.Initramfs)
defer os.Remove(tmpFile.Name())
bundle.Initramfs = tmpFile.Name()
}
GenerateBundle(&bundle)
}
func GenerateAllBundles() {
msg.Println("Generating EFI bundles....")
bundles := ReadBundleDatabase(BundleDBPath)
for _, bundle := range bundles {
CreateBundle(*bundle)
}
}
func ListBundles() {
bundles := ReadBundleDatabase(BundleDBPath)
for key, bundle := range bundles {
FormatBundle(key, bundle)
}
}