mirror of https://github.com/ericonr/sbctl.git
Compare commits
149 Commits
fe1f0365f9
...
bedb8e8c83
Author | SHA1 | Date |
---|---|---|
Morten Linderud | bedb8e8c83 | |
Morten Linderud | 73be015150 | |
Morten Linderud | 9ff60964d5 | |
Morten Linderud | 947b6ba8e8 | |
Morten Linderud | 69e52d3efb | |
Morten Linderud | 66d1482dd9 | |
Morten Linderud | 05b4f88539 | |
Morten Linderud | 858e129c2d | |
Morten Linderud | dc88c5c93d | |
Morten Linderud | 868b0a9547 | |
Oskar Kohout | ea325ca46f | |
Hugo Osvaldo Barrera | 1aca349c90 | |
Morten Linderud | 78cdabfa6d | |
Morten Linderud | 1614cfbd2e | |
Morten Linderud | 62fc79175f | |
Haochen Tong | a88a5b7e42 | |
Érico Nogueira | 9e0ea82854 | |
Morten Linderud | 2b6b3f92a1 | |
Morten Linderud | 2261630dbd | |
Morten Linderud | a43373ce3f | |
Morten Linderud | faf366f5e7 | |
Morten Linderud | d8af3d80a1 | |
Morten Linderud | 0a0dc36030 | |
Morten Linderud | 4ba10b9393 | |
Morten Linderud | 64cbd26bb3 | |
Morten Linderud | 4de27722ee | |
Morten Linderud | 070702abda | |
Hugo Osvaldo Barrera | 95458395f1 | |
Morten Linderud | 7c23cf35da | |
Morten Linderud | 7e01fec4fa | |
Morten Linderud | fb79c38977 | |
Morten Linderud | 32ac267834 | |
Morten Linderud | 5d528ff82a | |
Morten Linderud | 240786b7d8 | |
Morten Linderud | 7192e52fa1 | |
Morten Linderud | 2031e3a210 | |
Morten Linderud | 03f9ab94d9 | |
Morten Linderud | ec7dba937e | |
Morten Linderud | d108dbc9e0 | |
Morten Linderud | 683afec1d7 | |
Morten Linderud | 95170e5117 | |
Morten Linderud | 9587644626 | |
Morten Linderud | 2d65668632 | |
Morten Linderud | d04e117cbc | |
Morten Linderud | 1bf2810228 | |
Morten Linderud | ac8723dacd | |
Morten Linderud | 13bbb90c03 | |
Morten Linderud | 28eee4464d | |
Morten Linderud | 0781f6bb98 | |
Morten Linderud | 091b831f0b | |
Morten Linderud | 3300e790a9 | |
Morten Linderud | a738c8c2e3 | |
Morten Linderud | 635be0683f | |
Morten Linderud | ae1aec15fb | |
Morten Linderud | 550b4e6365 | |
Morten Linderud | ba0cee8115 | |
Morten Linderud | 57a1c93eb9 | |
Morten Linderud | 6b0242c953 | |
Morten Linderud | fe514e1af7 | |
Morten Linderud | 0d121672ca | |
Morten Linderud | 8b4fc40724 | |
Morten Linderud | b49ebbb8bf | |
Morten Linderud | 6dfc186d43 | |
Morten Linderud | 3f05d1df52 | |
Morten Linderud | a318695f44 | |
Morten Linderud | 3454841a75 | |
Morten Linderud | 97435cc48e | |
Morten Linderud | f01453a978 | |
Morten Linderud | 235238c987 | |
Morten Linderud | 342ba34a17 | |
Morten Linderud | a5e0551e56 | |
Morten Linderud | 3505f1b571 | |
Morten Linderud | adadb52e73 | |
Morten Linderud | 877ab49ae6 | |
Morten Linderud | d0022cb3b2 | |
Morten Linderud | 70b00f3184 | |
Morten Linderud | 30e16f5bd7 | |
Morten Linderud | fb9b3c7b33 | |
Morten Linderud | 3d7f094988 | |
Morten Linderud | 23381e0111 | |
Morten Linderud | 1508b290d6 | |
Morten Linderud | 3568e9d34b | |
Morten Linderud | b82e17e2ed | |
Morten Linderud | bb78cf9c01 | |
Morten Linderud | a05e6c8fb8 | |
Morten Linderud | 2a53d5200c | |
Morten Linderud | 62d653d0f8 | |
Morten Linderud | 431363f285 | |
Morten Linderud | 7a4defc0c1 | |
Morten Linderud | 0d249b25df | |
Morten Linderud | 955c547743 | |
Morten Linderud | 1b7f188e0f | |
Morten Linderud | 10ff8d2a65 | |
Morten Linderud | ba6dfc183e | |
Morten Linderud | a4f56b7127 | |
igo95862 | db8bbe2826 | |
igo95862 | cca2a12a7c | |
Morten Linderud | d7e4f3b603 | |
Morten Linderud | 35e68e55cc | |
Morten Linderud | b9eafb6a88 | |
Morten Linderud | bca692f1e1 | |
Hugo Osvaldo Barrera | 0ba4f65dac | |
Hugo Barrera | 9efc268827 | |
Hugo Barrera | 7839c5f47b | |
Hugo Osvaldo Barrera | 7b654ac28c | |
Hugo Osvaldo Barrera | fd444c444c | |
Hugo Osvaldo Barrera | 74b581384f | |
Hugo Barrera | 09181324a0 | |
igo95862 | d3feae2791 | |
Morten Linderud | f53632ebd2 | |
Morten Linderud | 35ebd7bba9 | |
Morten Linderud | e63eb3d6b1 | |
Morten Linderud | 44d597c74e | |
igo95862 | e55ef14bf4 | |
igo95862 | 02885c41db | |
igo95862 | 7d6d2c76ab | |
Morten Linderud | 9060461b35 | |
Morten Linderud | 3fcd3b0e84 | |
Morten Linderud | a33d0b40d8 | |
Érico Nogueira | 63876f01d5 | |
Morten Linderud | 59ec7a813f | |
Morten Linderud | a6445c4a2b | |
Morten Linderud | 4df69d6935 | |
Morten Linderud | ad1dc957cb | |
Morten Linderud | 8405a2a407 | |
Morten Linderud | 9100e231ea | |
Morten Linderud | c50750ee7a | |
Morten Linderud | 36a1849942 | |
Morten Linderud | d69b59bb01 | |
Morten Linderud | 99efd2a5e3 | |
Morten Linderud | 2572b38a8e | |
Morten Linderud | a426eeb0c6 | |
Morten Linderud | 17fc0e5ff4 | |
Morten Linderud | 7cdab1f018 | |
Morten Linderud | e40683e344 | |
Morten Linderud | 611f2818c6 | |
Morten Linderud | a0cd90a0b0 | |
Morten Linderud | 79d986f987 | |
Érico Rolim | f94f185652 | |
Érico Rolim | cc55d6e443 | |
Érico Rolim | 92bb91172d | |
Érico Rolim | 0a7c6e8bc5 | |
Érico Rolim | 017b0c1dea | |
Morten Linderud | f92102ae2a | |
Morten Linderud | 9e9c3a1620 | |
physkets | af64bdc245 | |
Morten Linderud | dfa6fb1baa | |
Morten Linderud | b09199482f | |
Érico Rolim | 464b3c2f71 |
|
@ -10,7 +10,7 @@ jobs:
|
|||
- uses: actions/checkout@v1
|
||||
- run: make
|
||||
- run: make test
|
||||
- run: make deps lint
|
||||
- run: GOBIN=/usr/bin make deps lint
|
||||
void:
|
||||
runs-on: ubuntu-latest
|
||||
container: voidlinux/voidlinux-musl
|
||||
|
@ -21,4 +21,4 @@ jobs:
|
|||
- uses: actions/checkout@v1
|
||||
- run: make
|
||||
- run: make test
|
||||
- run: make deps lint
|
||||
- run: GOBIN=/usr/bin make deps lint
|
||||
|
|
|
@ -3,3 +3,5 @@ releases/*
|
|||
sbctl
|
||||
!sbctl/
|
||||
docs/*.8
|
||||
rootfs*
|
||||
bzImage
|
||||
|
|
19
Makefile
19
Makefile
|
@ -10,6 +10,8 @@ GOFLAGS ?= -buildmode=pie -trimpath
|
|||
|
||||
SOURCES = $(shell go list -f '{{range .GoFiles}}{{$$.Dir}}/{{.}} {{end}}' ./...)
|
||||
|
||||
TAG = $(shell git describe --abbrev=0 --tags)
|
||||
|
||||
all: man build
|
||||
build: sbctl
|
||||
man: $(MANS)
|
||||
|
@ -21,18 +23,19 @@ docs/sbctl.%: docs/sbctl.%.txt docs/asciidoc.conf
|
|||
sbctl: $(SOURCES)
|
||||
go build ./cmd/$@
|
||||
|
||||
install: man
|
||||
.PHONY: completion
|
||||
completion:
|
||||
./sbctl completion bash | install -Dm644 /dev/stdin "$(DESTDIR)$(SHRDIR)/bash-completion/completions/sbctl"
|
||||
./sbctl completion zsh | install -Dm644 /dev/stdin "$(DESTDIR)$(SHRDIR)/usr/share/zsh/site-functions/_sbctl"
|
||||
./sbctl completion fish | install -Dm644 /dev/stdin "$(DESTDIR)$(SHRDIR)/usr/share/fish/vendor_completions.d/sbctl.fish"
|
||||
|
||||
install: man completion
|
||||
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)
|
||||
|
||||
.PHONY: tag
|
||||
tag:
|
||||
git describe --exact-match >/dev/null 2>&1 || git tag -s $(shell date +%Y%m%d)
|
||||
git push --tags
|
||||
|
||||
.PHONY: release
|
||||
release:
|
||||
mkdir -p releases
|
||||
|
@ -60,3 +63,7 @@ lint:
|
|||
.PHONY: test
|
||||
test:
|
||||
go test -v ./...
|
||||
|
||||
.PHONY: integration
|
||||
integration:
|
||||
go test -v tests/integration_test.go
|
||||
|
|
178
README.md
178
README.md
|
@ -1,27 +1,48 @@
|
|||
# sbctl - Secure Boot Manager
|
||||
[![Build Status](https://github.com/Foxboron/sbctl/workflows/build/badge.svg)](https://github.com/Foxboron/sbctl/actions)
|
||||
[![Build Status](https://github.com/Foxboron/sbctl/workflows/CI/badge.svg)](https://github.com/Foxboron/sbctl/actions)
|
||||
|
||||
The goal of the project is to have one consistent UI to manage secure boot keys.
|
||||
sbctl intends to be a user-friendly secure boot key manager capable of setting
|
||||
up secure boot, offer key management capabilities, and keep track of files that
|
||||
needs to be signed in the boot chain.
|
||||
|
||||
# Features
|
||||
It is written top-to-bottom in [Golang](https://golang.org/) using
|
||||
[go-uefi](https://github.com/Foxboron/go-uefi) for the API layer and doesn't
|
||||
rely on existing secure boot tooling. It also tries to sport some integration
|
||||
testing towards towards [tianocore](https://www.tianocore.org/) utilizing
|
||||
[vmtest](https://github.com/anatol/vmtest).
|
||||
|
||||
![](https://pkgbuild.com/~foxboron/sbctl_demo.gif)
|
||||
|
||||
## Features
|
||||
* User-friendly
|
||||
* Manages secure boot keys
|
||||
* Live enrollment of secure boot keys
|
||||
* Live enrollment of keys
|
||||
* Signing database to help keep track of files to sign
|
||||
* Verify ESP of files missing signatures
|
||||
* EFI stub generation
|
||||
* JSON Output
|
||||
|
||||
# Roadmap
|
||||
|
||||
* Convert to use [goefi](https://github.com/Foxboron/goefi) instead of relying on `sbsigntools`
|
||||
## Roadmap to 1.0
|
||||
* Key rotation
|
||||
* Customize keys
|
||||
* Secure the keys
|
||||
* TPM Support
|
||||
* Hardware Token support
|
||||
* Configuration Files
|
||||
* Automatic boot chain signing using the [Boot Loader Interface](https://systemd.io/BOOT_LOADER_INTERFACE/)
|
||||
|
||||
## Dependencies
|
||||
* util-linux (using `lsblk`)
|
||||
* binutils (using `objcopy`)
|
||||
* Go >= 1.16
|
||||
|
||||
# Support and development channel
|
||||
|
||||
Development discussions and support happens in `#sbctl` on the [libera.chat](https://kiwiirc.com/nextclient/irc.libera.chat/#sbctl) IRC network.
|
||||
|
||||
# Usage
|
||||
|
||||
```
|
||||
$ sbctl
|
||||
Secure Boot key manager
|
||||
Secure Boot Key Manager
|
||||
|
||||
Usage:
|
||||
sbctl [command]
|
||||
|
@ -43,6 +64,7 @@ Available Commands:
|
|||
|
||||
Flags:
|
||||
-h, --help help for sbctl
|
||||
--json Output as json
|
||||
|
||||
Use "sbctl [command] --help" for more information about a command.
|
||||
```
|
||||
|
@ -51,71 +73,89 @@ Use "sbctl [command] --help" for more information about a command.
|
|||
|
||||
```
|
||||
# sbctl status
|
||||
==> WARNING: Setup Mode: Enabled
|
||||
==> WARNING: Secure Boot: Disabled
|
||||
Installed: ✘ Sbctl is not installed
|
||||
Setup Mode: ✘ Enabled
|
||||
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...
|
||||
Created Owner UUID a9fbbdb7-a05f-48d5-b63a-08c5df45ee70
|
||||
Creating secure boot keys...✔
|
||||
Secure boot keys created!
|
||||
|
||||
# sbctl enroll-keys
|
||||
==> Syncing /usr/share/secureboot/keys to EFI variables...
|
||||
==> Synced keys!
|
||||
Enrolling keys to EFI variables...✔
|
||||
Enrolled keys to the EFI variables!
|
||||
|
||||
# sbctl status
|
||||
==> Setup Mode: Disabled
|
||||
==> WARNING: Secure Boot: Disabled
|
||||
Installed: ✔ Sbctl is installed
|
||||
Owner GUID: a9fbbdb7-a05f-48d5-b63a-08c5df45ee70
|
||||
Setup Mode: ✔ Disabled
|
||||
Secure Boot: ✘ Disabled
|
||||
|
||||
// Reboot!
|
||||
# sbctl status
|
||||
==> Setup Mode: Disabled
|
||||
==> Secure Boot: Enabled
|
||||
Installed: ✔ Sbctl is installed
|
||||
Owner GUID: a9fbbdb7-a05f-48d5-b63a-08c5df45ee70
|
||||
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
|
||||
Verifying file database and EFI images in /efi...
|
||||
✘ /boot/vmlinuz-linux is not signed
|
||||
✘ /efi/EFI/BOOT/BOOTX64.EFI is not signed
|
||||
✘ /efi/EFI/BOOT/KeyTool-signed.efi is not signed
|
||||
✘ /efi/EFI/Linux/linux-linux.efi is not signed
|
||||
✘ /efi/EFI/arch/fwupdx64.efi is not signed
|
||||
✘ /efi/EFI/systemd/systemd-bootx64.efi is not signed
|
||||
|
||||
# sbctl sign -s /efi/EFI/BOOT/BOOTX64.EFI
|
||||
==> Signing /efi/EFI/BOOT/BOOTX64.EFI...
|
||||
✔ Signed /efi/EFI/BOOT/BOOTX64.EFI...
|
||||
|
||||
# sbctl sign -s /efi/EFI/arch/fwupdx64.efi
|
||||
==> Signing /efi/EFI/arch/fwupdx64.efi...
|
||||
✔ Signed /efi/EFI/arch/fwupdx64.efi...
|
||||
|
||||
# sbctl sign -s /efi/EFI/systemd/systemd-bootx64.efi
|
||||
==> Signing /efi/EFI/systemd/systemd-bootx64.efi...
|
||||
✔ Signed /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...
|
||||
✔ Signed /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
|
||||
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
|
||||
✘ /boot/vmlinuz-linux is not signed
|
||||
✘ /efi/EFI/BOOT/KeyTool-signed.efi is not signed
|
||||
✘ /efi/EFI/Linux/linux-linux.efi is not signed
|
||||
|
||||
# sbctl list-files
|
||||
==> File: /efi/EFI/BOOT/BOOTX64.EFI
|
||||
==> File: /efi/EFI/arch/fwupdx64.efi
|
||||
==> File: /efi/EFI/systemd/systemd-bootx64.efi
|
||||
==> File: /efi/vmlinuz-linux
|
||||
==> File: /usr/lib/fwupd/efi/fwupdx64.efi
|
||||
-> Output: /usr/lib/fwupd/efi/fwupdx64.efi.signed
|
||||
/boot/vmlinuz-linux
|
||||
Signed: ✘ Not Signed
|
||||
|
||||
/efi/EFI/BOOT/KeyTool-signed.efi
|
||||
Signed: ✘ Not Signed
|
||||
|
||||
/efi/EFI/Linux/linux-linux.efi
|
||||
Signed: ✘ Not Signed
|
||||
|
||||
/efi/EFI/arch/fwupdx64.efi
|
||||
Signed: ✔ Signed
|
||||
|
||||
/efi/EFI/BOOT/BOOTX64.EFI
|
||||
Signed: ✔ Signed
|
||||
|
||||
/usr/lib/fwupd/efi/fwupdx64.efi
|
||||
Signed: ✔ Signed
|
||||
Output File: /usr/lib/fwupd/efi/fwupdx64.efi.signed
|
||||
|
||||
/efi/EFI/systemd/systemd-bootx64.efi
|
||||
Signed: ✔ Signed
|
||||
```
|
||||
|
||||
## Generate EFI Stub
|
||||
|
@ -124,32 +164,26 @@ Use "sbctl [command] --help" for more information about a command.
|
|||
-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 Release: /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
|
||||
/efi/EFI/Linux/linux-linux.efi
|
||||
Wrote EFI bundle /efi/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 Release: /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
|
||||
Enrolled bundles:
|
||||
|
||||
/efi/EFI/Linux/linux-linux.efi
|
||||
Signed: ✔ Signed
|
||||
ESP Location: /efi
|
||||
Output: └─/EFI/Linux/linux-linux.efi
|
||||
EFI Stub Image: └─/usr/lib/systemd/boot/efi/linuxx64.efi.stub
|
||||
Splash Image: ├─/usr/share/systemd/bootctl/splash-arch.bmp
|
||||
Cmdline: ├─/etc/kernel/cmdline
|
||||
OS Release: ├─/usr/lib/os-release
|
||||
Kernel Image: ├─/boot/vmlinuz-linux
|
||||
Initramfs Image: └─/boot/initramfs-linux.img
|
||||
Intel Microcode: └─/boot/intel-ucode.img
|
||||
|
||||
|
||||
# sbctl generate-bundles
|
||||
==> Generating EFI bundles....
|
||||
==> Wrote EFI bundle /boot/EFI/Linux/linux-linux.efi
|
||||
Generating EFI bundles....
|
||||
Wrote EFI bundle /efi/EFI/Linux/linux-linux.efi
|
||||
```
|
||||
|
|
113
bundles.go
113
bundles.go
|
@ -2,13 +2,11 @@ package sbctl
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Bundle struct {
|
||||
|
@ -28,33 +26,39 @@ 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)
|
||||
func ReadBundleDatabase(dbpath string) (Bundles, error) {
|
||||
f, err := ReadOrCreateFile(dbpath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return nil, err
|
||||
}
|
||||
bundles := make(Bundles)
|
||||
json.Unmarshal(f, &bundles)
|
||||
return bundles
|
||||
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 = ioutil.WriteFile(dbpath, data, 0644)
|
||||
err = os.WriteFile(dbpath, data, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -70,65 +74,56 @@ func GetEfistub() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func NewBundle() *Bundle {
|
||||
esp := GetESP()
|
||||
func NewBundle() (bundle *Bundle, err error) {
|
||||
esp, err := GetESP()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
stub := GetEfistub()
|
||||
if stub == "" {
|
||||
panic("No EFISTUB file found. Please install systemd-boot or gummiboot!")
|
||||
}
|
||||
|
||||
return &Bundle{
|
||||
bundle = &Bundle{
|
||||
Output: "",
|
||||
IntelMicrocode: "",
|
||||
AMDMicrocode: "",
|
||||
KernelImage: filepath.Join(esp, "vmlinuz-linux"),
|
||||
Initramfs: filepath.Join(esp, "initramfs-linux.img"),
|
||||
Cmdline: "/proc/cmdline",
|
||||
KernelImage: "/boot/vmlinuz-linux",
|
||||
Initramfs: "/boot/initramfs-linux.img",
|
||||
Cmdline: "/etc/kernel/cmdline",
|
||||
Splash: "",
|
||||
OSRelease: "/usr/lib/os-release",
|
||||
EFIStub: stub,
|
||||
ESP: esp,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
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",
|
||||
"--add-section", fmt.Sprintf(".linux=%s", bundle.KernelImage), "--change-section-vma", ".linux=0x2000000",
|
||||
"--add-section", fmt.Sprintf(".initrd=%s", bundle.Initramfs), "--change-section-vma", ".initrd=0x3000000",
|
||||
}
|
||||
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, " ")...)
|
||||
|
||||
if bundle.Splash != "" {
|
||||
args = append(args, "--add-section", fmt.Sprintf(".splash=%s", bundle.Splash), "--change-section-vma", ".splash=0x40000")
|
||||
}
|
||||
|
||||
args = append(args, bundle.EFIStub, bundle.Output)
|
||||
cmd := exec.Command("objcopy", args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
if errors.Is(err, exec.ErrNotFound) {
|
||||
return false, err
|
||||
}
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
return exitError.ExitCode() == 0
|
||||
return exitError.ExitCode() == 0, nil
|
||||
}
|
||||
}
|
||||
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 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)
|
||||
}
|
||||
msg2.Printf("Output: %s", bundle.Output)
|
||||
return true, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package sbctl
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// from /usr/include/linux/fs.h
|
||||
FS_SECRM_FL = 0x00000001 /* Secure deletion */
|
||||
FS_UNRM_FL = 0x00000002 /* Undelete */
|
||||
FS_COMPR_FL = 0x00000004 /* Compress file */
|
||||
FS_SYNC_FL = 0x00000008 /* Synchronous updates */
|
||||
FS_IMMUTABLE_FL = 0x00000010 /* Immutable file */
|
||||
FS_APPEND_FL = 0x00000020 /* writes to file may only append */
|
||||
FS_NODUMP_FL = 0x00000040 /* do not dump file */
|
||||
FS_NOATIME_FL = 0x00000080 /* do not update atime */
|
||||
FS_DIRTY_FL = 0x00000100
|
||||
FS_COMPRBLK_FL = 0x00000200 /* One or more compressed clusters */
|
||||
FS_NOCOMP_FL = 0x00000400 /* Don't compress */
|
||||
FS_ECOMPR_FL = 0x00000800 /* Compression error */
|
||||
FS_BTREE_FL = 0x00001000 /* btree format dir */
|
||||
FS_INDEX_FL = 0x00001000 /* hash-indexed directory */
|
||||
FS_IMAGIC_FL = 0x00002000 /* AFS directory */
|
||||
FS_JOURNAL_DATA_FL = 0x00004000 /* Reserved for ext3 */
|
||||
FS_NOTAIL_FL = 0x00008000 /* file tail should not be merged */
|
||||
FS_DIRSYNC_FL = 0x00010000 /* dirsync behaviour (directories only) */
|
||||
FS_TOPDIR_FL = 0x00020000 /* Top of directory hierarchies*/
|
||||
FS_EXTENT_FL = 0x00080000 /* Extents */
|
||||
FS_DIRECTIO_FL = 0x00100000 /* Use direct i/o */
|
||||
FS_NOCOW_FL = 0x00800000 /* Do not cow file */
|
||||
FS_PROJINHERIT_FL = 0x20000000 /* Create with parents projid */
|
||||
FS_RESERVED_FL = 0x80000000 /* reserved for ext2 lib */
|
||||
)
|
||||
|
||||
/* The code below won't work correctly if this tool is built
|
||||
* for a 64-bit big endian platform.
|
||||
* See https://github.com/golang/go/issues/45585 for context. */
|
||||
|
||||
// GetAttr retrieves the attributes of a file on a linux filesystem
|
||||
func GetAttr(f *os.File) (int32, error) {
|
||||
attr_int, err := unix.IoctlGetInt(int(f.Fd()), unix.FS_IOC_GETFLAGS)
|
||||
return int32(attr_int), err
|
||||
}
|
||||
|
||||
// SetAttr sets the attributes of a file on a linux filesystem to the given value
|
||||
func SetAttr(f *os.File, attr int32) error {
|
||||
return unix.IoctlSetPointerInt(int(f.Fd()), unix.FS_IOC_SETFLAGS, int(attr))
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
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, err := sbctl.NewBundle()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
logging.Print("Wrote EFI bundle %s\n", bundle.Output)
|
||||
if saveBundle {
|
||||
bundles[bundle.Output] = bundle
|
||||
err := sbctl.WriteBundleDatabase(sbctl.BundleDBPath, bundles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func bundleCmdFlags(cmd *cobra.Command) {
|
||||
esp, err := sbctl.GetESP()
|
||||
if err != nil {
|
||||
logging.Warn("Failed to find ESP: %s", err)
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
|
@ -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,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/foxboron/sbctl"
|
||||
"github.com/foxboron/sbctl/logging"
|
||||
"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 {
|
||||
if err := sbctl.CreateDirectory(sbctl.KeysPath); err != nil {
|
||||
return err
|
||||
}
|
||||
uuid, err := sbctl.CreateGUID(sbctl.DatabasePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logging.Print("Created Owner UUID %s\n", uuid)
|
||||
if !sbctl.CheckIfKeysInitialized(sbctl.KeysPath) {
|
||||
logging.Print("Creating secure boot keys...")
|
||||
err := sbctl.InitializeSecureBootKeys(sbctl.KeysPath)
|
||||
if err != nil {
|
||||
logging.NotOk("")
|
||||
return fmt.Errorf("couldn't initialize secure boot: %w", err)
|
||||
}
|
||||
logging.Ok("\nSecure boot keys created!")
|
||||
} else {
|
||||
logging.Ok("Secure boot keys has already been created!")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
CliCommands = append(CliCommands, cliCommand{
|
||||
Cmd: createKeysCmd,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/foxboron/go-uefi/efi/util"
|
||||
"github.com/foxboron/sbctl"
|
||||
"github.com/foxboron/sbctl/logging"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func CheckImmutable() error {
|
||||
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
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var enrollKeysCmd = &cobra.Command{
|
||||
Use: "enroll-keys",
|
||||
Short: "Enroll the current keys to EFI",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := CheckImmutable(); err != nil {
|
||||
return err
|
||||
}
|
||||
uuid, err := sbctl.GetGUID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
guid := util.StringToGUID(uuid.String())
|
||||
logging.Print("Enrolling keys to EFI variables...")
|
||||
if err := sbctl.KeySync(*guid, sbctl.KeysPath); err != nil {
|
||||
logging.NotOk("")
|
||||
return fmt.Errorf("couldn't sync keys: %w", err)
|
||||
}
|
||||
logging.Ok("\nEnrolled keys to the EFI variables!")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
CliCommands = append(CliCommands, cliCommand{
|
||||
Cmd: enrollKeysCmd,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/foxboron/sbctl"
|
||||
"github.com/foxboron/sbctl/logging"
|
||||
"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 {
|
||||
logging.Println("Generating EFI bundles....")
|
||||
out_create := true
|
||||
out_sign := true
|
||||
var out_err error
|
||||
err := sbctl.BundleIter(func(bundle *sbctl.Bundle) error {
|
||||
err := sbctl.CreateBundle(*bundle)
|
||||
if err != nil {
|
||||
out_create = false
|
||||
out_err = fmt.Errorf("failed creating bundle %s: %w", bundle.Output, err)
|
||||
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
|
||||
out_err = fmt.Errorf("failed signing bundle %s: %w", bundle.Output, err)
|
||||
} else {
|
||||
logging.Ok("Signed %s", file)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if !out_create || !out_sign {
|
||||
return out_err
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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 {
|
||||
logging.Error(fmt.Errorf("%s: %w", s.Output, err))
|
||||
logging.Error(fmt.Errorf(""))
|
||||
return nil
|
||||
}
|
||||
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, err := sbctl.GetESP()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/foxboron/sbctl"
|
||||
"github.com/foxboron/sbctl/logging"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var listFilesCmd = &cobra.Command{
|
||||
Use: "list-files",
|
||||
Short: "List enrolled files",
|
||||
RunE: RunList,
|
||||
}
|
||||
|
||||
type JsonFile struct {
|
||||
sbctl.SigningEntry
|
||||
IsSigned bool `json:"is_signed"`
|
||||
}
|
||||
|
||||
func RunList(_ *cobra.Command, args []string) error {
|
||||
files := []JsonFile{}
|
||||
var isSigned bool
|
||||
err := sbctl.SigningEntryIter(
|
||||
func(s *sbctl.SigningEntry) error {
|
||||
ok, err := sbctl.VerifyFile(sbctl.DBCert, s.OutputFile)
|
||||
if err != nil {
|
||||
logging.Error(fmt.Errorf("%s: %w", s.OutputFile, err))
|
||||
logging.Error(fmt.Errorf(""))
|
||||
return nil
|
||||
}
|
||||
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)
|
||||
}
|
||||
logging.Println("")
|
||||
files = append(files, JsonFile{*s, isSigned})
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cmdOptions.JsonOutput {
|
||||
JsonOut(files)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
CliCommands = append(CliCommands, cliCommand{
|
||||
Cmd: listFilesCmd,
|
||||
})
|
||||
}
|
|
@ -1,343 +1,81 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"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
|
||||
}
|
||||
|
||||
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()
|
||||
},
|
||||
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.PersistentFlags()
|
||||
flags.BoolVar(&cmdOptions.JsonOutput, "json", false, "Output as json")
|
||||
|
||||
cmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
|
||||
if cmdOptions.JsonOutput {
|
||||
logging.PrintOff()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 JsonOut(v interface{}) error {
|
||||
b, err := json.MarshalIndent(v, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not marshal json: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
// Ensure we have absolute paths
|
||||
file, err := filepath.Abs(args[0])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if output == "" {
|
||||
output = file
|
||||
} else {
|
||||
output, err = filepath.Abs(output)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := sbctl.Sign(file, output, save); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
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",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var outBundle error
|
||||
outSign := false
|
||||
|
||||
if generate {
|
||||
outBundle = sbctl.GenerateAllBundles(true)
|
||||
}
|
||||
|
||||
files := sbctl.ReadFileDatabase(sbctl.DBPath)
|
||||
for _, entry := range files {
|
||||
|
||||
if sbctl.SignFile(sbctl.DBKey, sbctl.DBCert, entry.File, entry.OutputFile, entry.Checksum) != nil {
|
||||
outSign = true
|
||||
continue
|
||||
}
|
||||
|
||||
// Update checksum after we signed it
|
||||
checksum := sbctl.ChecksumFile(entry.File)
|
||||
entry.Checksum = checksum
|
||||
files[entry.File] = entry
|
||||
sbctl.WriteFileDatabase(sbctl.DBPath, files)
|
||||
|
||||
}
|
||||
|
||||
if outBundle != nil || outSign {
|
||||
log.Fatalln("Errors were encountered, see above")
|
||||
}
|
||||
},
|
||||
}
|
||||
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",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) < 1 {
|
||||
log.Fatal("Need to specify file")
|
||||
}
|
||||
files := sbctl.ReadFileDatabase(sbctl.DBPath)
|
||||
if _, ok := files[args[0]]; !ok {
|
||||
log.Printf("File %s doesn't exist in database!\n", args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
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()
|
||||
output, err := filepath.Abs(args[0])
|
||||
if err != nil {
|
||||
log.Fatal(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
|
||||
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(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",
|
||||
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)
|
||||
|
||||
if _, ok := bundles[args[0]]; !ok {
|
||||
log.Printf("Bundle %s doesn't exist in database!\n", args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
delete(bundles, args[0])
|
||||
sbctl.WriteBundleDatabase(sbctl.BundleDBPath, bundles)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
fmt.Fprint(os.Stdout, string(b))
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
rootCmd.PersistentPreRun = func(c *cobra.Command, args []string) {
|
||||
if strings.Contains(c.CommandPath(), "completion zsh") ||
|
||||
strings.Contains(c.CommandPath(), "completion bash") ||
|
||||
strings.Contains(c.CommandPath(), "completion fish") ||
|
||||
strings.Contains(c.CommandPath(), "__complete") {
|
||||
return
|
||||
}
|
||||
if os.Geteuid() != 0 {
|
||||
fmt.Println("Needs to be executed as root")
|
||||
os.Exit(1)
|
||||
}
|
||||
for _, cmd := range CliCommands {
|
||||
rootCmd.AddCommand(cmd.Cmd)
|
||||
}
|
||||
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())
|
||||
rootCmd.AddCommand(removeFileCmd())
|
||||
|
||||
completionCmd := &cobra.Command{Use: "completion"}
|
||||
completionCmd.AddCommand(completionBashCmd())
|
||||
completionCmd.AddCommand(completionZshCmd())
|
||||
completionCmd.AddCommand(completionFishCmd())
|
||||
rootCmd.AddCommand(completionCmd)
|
||||
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)
|
||||
cmd.Println(cmd.UsageString())
|
||||
return ErrSilent
|
||||
})
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
if strings.HasPrefix(err.Error(), "unknown command") {
|
||||
logging.Println(err.Error())
|
||||
} else if errors.Is(err, os.ErrPermission) {
|
||||
logging.Error(fmt.Errorf("sbctl requires root to run: %w", err))
|
||||
} else if errors.Is(err, sbctl.ErrImmutable) {
|
||||
logging.Println("You need to chattr -i files in efivarfs")
|
||||
} else if !errors.Is(err, ErrSilent) {
|
||||
logging.Error(err)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
sbctl.ColorsOff()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
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])
|
||||
err = sbctl.WriteBundleDatabase(sbctl.BundleDBPath, bundles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logging.Print("Removed %s from the database.\n", args[0])
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
CliCommands = append(CliCommands, cliCommand{
|
||||
Cmd: removeBundleCmd,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
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])
|
||||
if err := sbctl.WriteFileDatabase(sbctl.DBPath, files); err != nil {
|
||||
return err
|
||||
}
|
||||
logging.Print("Removed %s from the database.\n", args[0])
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
CliCommands = append(CliCommands, cliCommand{
|
||||
Cmd: removeFileCmd,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"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 {
|
||||
sign = true
|
||||
if err := generateBundlesCmd.RunE(cmd, args); err != nil {
|
||||
logging.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
files, err := sbctl.ReadFileDatabase(sbctl.DBPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, entry := range files {
|
||||
|
||||
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 fmt.Errorf("failed signing %s: %w", entry.File, err)
|
||||
} else {
|
||||
logging.Ok("Signed %s", entry.OutputFile)
|
||||
}
|
||||
|
||||
// Update checksum after we signed it
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
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,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
err = sbctl.Sign(file, output, save)
|
||||
if errors.Is(err, sbctl.ErrAlreadySigned) {
|
||||
logging.Print("File have already been signed %s\n", output)
|
||||
} else if err != nil {
|
||||
return err
|
||||
} else {
|
||||
logging.Ok("Signed %s", output)
|
||||
}
|
||||
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,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/foxboron/go-uefi/efi"
|
||||
"github.com/foxboron/sbctl"
|
||||
"github.com/foxboron/sbctl/logging"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var statusCmd = &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Show current boot status",
|
||||
RunE: RunStatus,
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
Installed bool `json:"installed"`
|
||||
GUID string `json:"guid"`
|
||||
SetupMode bool `json:"setup_mode"`
|
||||
SecureBoot bool `json:"secure_boot"`
|
||||
}
|
||||
|
||||
func NewStatus() *Status {
|
||||
return &Status{
|
||||
Installed: false,
|
||||
GUID: "",
|
||||
SetupMode: false,
|
||||
SecureBoot: false,
|
||||
}
|
||||
}
|
||||
|
||||
func PrintStatus(s *Status) {
|
||||
logging.Print("Installed:\t")
|
||||
if s.Installed {
|
||||
logging.Ok("Sbctl is installed")
|
||||
logging.Print("Owner GUID:\t")
|
||||
logging.Println(s.GUID)
|
||||
} else {
|
||||
logging.NotOk("Sbctl is not installed")
|
||||
}
|
||||
logging.Print("Setup Mode:\t")
|
||||
if s.SetupMode {
|
||||
logging.NotOk("Enabled")
|
||||
} else {
|
||||
logging.Ok("Disabled")
|
||||
}
|
||||
logging.Print("Secure Boot:\t")
|
||||
if s.SecureBoot {
|
||||
logging.Ok("Enabled")
|
||||
} else {
|
||||
logging.NotOk("Disabled")
|
||||
}
|
||||
}
|
||||
|
||||
func RunStatus(cmd *cobra.Command, args []string) error {
|
||||
stat := NewStatus()
|
||||
if _, err := os.Stat("/sys/firmware/efi/efivars"); os.IsNotExist(err) {
|
||||
return fmt.Errorf("system is not booted with UEFI")
|
||||
}
|
||||
if sbctl.CheckSbctlInstallation(sbctl.DatabasePath) {
|
||||
stat.Installed = true
|
||||
u, err := sbctl.GetGUID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stat.GUID = u.String()
|
||||
}
|
||||
if efi.GetSetupMode() {
|
||||
stat.SetupMode = true
|
||||
}
|
||||
if efi.GetSecureBoot() {
|
||||
stat.SecureBoot = true
|
||||
}
|
||||
if cmdOptions.JsonOutput {
|
||||
JsonOut(stat)
|
||||
} else {
|
||||
PrintStatus(stat)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
CliCommands = append(CliCommands, cliCommand{
|
||||
Cmd: statusCmd,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/foxboron/sbctl"
|
||||
"github.com/foxboron/sbctl/logging"
|
||||
"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 {
|
||||
// Exit early if we can't verify files
|
||||
if err := sbctl.CanVerifyFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
espPath, err := sbctl.GetESP()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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", path)
|
||||
} else {
|
||||
logging.NotOk("%s is not signed", path)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
CliCommands = append(CliCommands, cliCommand{
|
||||
Cmd: verifyCmd,
|
||||
})
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
pkgbase = sbctl-git
|
||||
pkgdesc = Secure Boot key manager
|
||||
pkgver = r37.g833e3e4
|
||||
pkgver = r201.ga43373c
|
||||
pkgrel = 1
|
||||
url = https://github.com/Foxboron/sbctl
|
||||
arch = x86_64
|
||||
|
@ -8,10 +8,8 @@ pkgbase = sbctl-git
|
|||
makedepends = go
|
||||
makedepends = git
|
||||
makedepends = asciidoc
|
||||
depends = sbsigntools
|
||||
source = git+https://github.com/Foxboron/sbctl.git?signed
|
||||
validpgpkeys = C100346676634E80C940FB9E9C02FF419FECBE16
|
||||
sha256sums = SKIP
|
||||
|
||||
pkgname = sbctl-git
|
||||
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
# Maintainer: Morten Linderud <foxboron@archlinux.org>
|
||||
|
||||
pkgname=sbctl-git
|
||||
pkgver=r37.g833e3e4
|
||||
pkgver=r201.ga43373c
|
||||
pkgrel=1
|
||||
pkgdesc="Secure Boot key manager"
|
||||
arch=("x86_64")
|
||||
url="https://github.com/Foxboron/sbctl"
|
||||
license=("MIT")
|
||||
depends=("sbsigntools")
|
||||
makedepends=("go" "git" "asciidoc")
|
||||
source=("git+https://github.com/Foxboron/sbctl.git?signed")
|
||||
validpgpkeys=("C100346676634E80C940FB9E9C02FF419FECBE16")
|
||||
|
@ -34,5 +33,5 @@ package(){
|
|||
./sbctl completion bash | install -Dm644 /dev/stdin "$pkgdir/usr/share/bash-completion/completions/sbctl"
|
||||
./sbctl completion zsh | install -Dm644 /dev/stdin "$pkgdir/usr/share/zsh/site-functions/_sbctl"
|
||||
./sbctl completion fish | install -Dm644 /dev/stdin "$pkgdir/usr/share/fish/vendor_completions.d/sbctl.fish"
|
||||
install -Dm644 ./contrib/pacman/99-sbctl.hook "${pkgdir}/usr/share/libalpm/hooks/99-sbctl.hook"
|
||||
install -Dm644 ./contrib/pacman/ZZ-sbctl.hook "${pkgdir}/usr/share/libalpm/hooks/99-sbctl.hook"
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ Operation = Remove
|
|||
Target = boot/*
|
||||
Target = efi/*
|
||||
Target = usr/lib/modules/*/vmlinuz
|
||||
Target = usr/lib/initcpio/*
|
||||
Target = usr/lib/**/efi/*.efi*
|
||||
|
||||
[Action]
|
||||
Description = Signing EFI binaries...
|
48
database.go
48
database.go
|
@ -2,10 +2,8 @@ package sbctl
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type SigningEntry struct {
|
||||
|
@ -16,33 +14,39 @@ type SigningEntry struct {
|
|||
|
||||
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)
|
||||
func ReadFileDatabase(dbpath string) (SigningEntries, error) {
|
||||
f, err := ReadOrCreateFile(dbpath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files := make(SigningEntries)
|
||||
json.Unmarshal(f, &files)
|
||||
return files
|
||||
|
||||
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 = ioutil.WriteFile(dbpath, data, 0644)
|
||||
err = os.WriteFile(dbpath, data, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -48,6 +48,9 @@ EFI signing commands
|
|||
**sign-all**::
|
||||
Signs all enrolled EFI binaries.
|
||||
|
||||
*-g*, *--generate*;;
|
||||
Generate all bundles before signing.
|
||||
|
||||
**list-files**::
|
||||
Lists all enrolled EFI binaries.
|
||||
|
||||
|
@ -67,14 +70,14 @@ EFI binary commands
|
|||
------------------
|
||||
|
||||
**bundle** [FLAGS] <NAME>::
|
||||
Creates a bundle that should produce EFI binaries. This is useful if
|
||||
you want to sign your initramfs along with your kernel.
|
||||
Creates a bundle that should produce EFI binaries. See **BUNDLES**
|
||||
below for more details.
|
||||
|
||||
*-a* 'PATH', *--amducode* 'PATH';;
|
||||
AMD microcode location.
|
||||
|
||||
*-c* 'PATH', *--cmdline* 'PATH';;
|
||||
Cmdline location. (default "/proc/cmdline")
|
||||
Cmdline location. (default "/etc/kernel/cmdline")
|
||||
|
||||
*-e* 'PATH', *--efi-stub* 'PATH';;
|
||||
EFI Stub location. (default "/usr/lib/systemd/boot/efi/linuxx64.efi.stub")
|
||||
|
@ -110,18 +113,51 @@ EFI binary commands
|
|||
Sign all the generated bundles.
|
||||
|
||||
**remove-bundle** <NAME>::
|
||||
Removes a bundle from the list.
|
||||
Removes a bundle from the list. This does not delete the bundle itself.
|
||||
|
||||
**list-bundles**::
|
||||
List all registered bundles to generate.
|
||||
|
||||
|
||||
Bundles
|
||||
-------
|
||||
|
||||
Normally, only the kernel is signed with your secure boot keys. This means the
|
||||
kernel command line and initramfs can be changed without possibility of verification.
|
||||
|
||||
Bundles are EFI executables which pack all three (initramfs, kernel and
|
||||
cmdline) into a single file which is easy to sign. Avoiding any unsigned
|
||||
files during boot makes the whole process more tamper-proof.
|
||||
|
||||
When a bundle is generated, its configuration is stored into the bundle
|
||||
database (see **FILES**). Subsequent executions of *sbctl generate-bundles*
|
||||
will rebuild these bundles, so you don't need to re-specify all parameters
|
||||
after each system update.
|
||||
|
||||
Hint: systemd-boot will automatically show entries for any bundles found in
|
||||
*esp/EFI/Linux/+++*+++.efi*.
|
||||
|
||||
|
||||
Notes
|
||||
-----
|
||||
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.
|
||||
|
||||
**SBCTL_UNICODE**::
|
||||
If this value is "0" sbctl will replace the unicode symbols to equivalent
|
||||
ascii ones. The default value is assumed to be 1.
|
||||
|
||||
|
||||
Files
|
||||
----
|
||||
**/usr/share/secureboot**::
|
||||
|
@ -149,6 +185,7 @@ Files
|
|||
See Also
|
||||
--------
|
||||
linkman:sbsign[1]
|
||||
linkman:bootctl[1]
|
||||
|
||||
|
||||
Authors
|
||||
|
|
13
go.mod
13
go.mod
|
@ -1,9 +1,14 @@
|
|||
module github.com/foxboron/sbctl
|
||||
|
||||
go 1.14
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/foxboron/goefi v0.0.0-20200514154950-f75d7eb492b7
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/anatol/vmtest v0.0.0-20210225191124-26540db15d49
|
||||
github.com/fatih/color v1.12.0
|
||||
github.com/foxboron/go-uefi v0.0.0-20210707123620-3a44878e0db9
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||
github.com/spf13/cobra v1.1.3
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
|
||||
golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b
|
||||
)
|
||||
|
|
228
go.sum
228
go.sum
|
@ -1,26 +1,55 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
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/anatol/vmtest v0.0.0-20201215024419-d0326c4b7734/go.mod h1:EWbYrKMDMxiKbQjI7z6GO7yABGxqRkU3+slxy/avES8=
|
||||
github.com/anatol/vmtest v0.0.0-20210225191124-26540db15d49 h1:WI65BVwIWBZqogBRSDRY3HqhJPkzKV1B8fbOV16LSCA=
|
||||
github.com/anatol/vmtest v0.0.0-20210225191124-26540db15d49/go.mod h1:EWbYrKMDMxiKbQjI7z6GO7yABGxqRkU3+slxy/avES8=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
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/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-semver v0.3.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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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-20200514154950-f75d7eb492b7 h1:kgR7He9X3djcpeajbwg5RilafBVNTuHneugjFXq+7jc=
|
||||
github.com/foxboron/goefi v0.0.0-20200514154950-f75d7eb492b7/go.mod h1:cCrkwDedAKDCmYAhWzlcOqhFx5LtqKTEjqQbJpe6g4U=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
|
||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/foxboron/go-uefi v0.0.0-20210602193603-8589bbab9380 h1:D8hRHRCC/jFjOg0alhvQo2unG/HU/qZFbhLvRJPo21I=
|
||||
github.com/foxboron/go-uefi v0.0.0-20210602193603-8589bbab9380/go.mod h1:bLcrn48nYQOkijhTK2iQw1MjXbBqJTG0k8RP6ww+CGQ=
|
||||
github.com/foxboron/go-uefi v0.0.0-20210611230104-7a6a29e36155 h1:9RnTC3NVUwcFpHGGzDYd2LqED59D929P9rl+bq8JL2c=
|
||||
github.com/foxboron/go-uefi v0.0.0-20210611230104-7a6a29e36155/go.mod h1:bLcrn48nYQOkijhTK2iQw1MjXbBqJTG0k8RP6ww+CGQ=
|
||||
github.com/foxboron/go-uefi v0.0.0-20210707123620-3a44878e0db9 h1:P5EmEozjxzYjCvhu5N37V31IAgyqr7Nka14ZUWKSvXo=
|
||||
github.com/foxboron/go-uefi v0.0.0-20210707123620-3a44878e0db9/go.mod h1:sZB64FhbhnEuJW8NFrcx4uuhbs4Hm8GeaH0iXz5bNqo=
|
||||
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-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
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=
|
||||
|
@ -30,20 +59,54 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV
|
|||
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/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
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/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
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/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
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/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
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/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
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=
|
||||
|
@ -52,15 +115,36 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
|
|||
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/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
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.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
|
||||
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
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/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
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.8.1/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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
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=
|
||||
|
@ -71,62 +155,170 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R
|
|||
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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
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/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
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/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
|
||||
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
|
||||
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/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
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/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
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.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
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-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/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-20181201002055-351d144fa1fc/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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/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/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
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/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
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-20181026203630-95b1ffbd15a5/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/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b h1:qh4f65QIVFjq9eBURLEYWqaEXmOyqdUyiBSgaXWccWk=
|
||||
golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
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-20180917221912-90fa682c2a6e/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-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
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=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package sbctl
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func CreateUUID() []byte {
|
||||
id, _ := uuid.NewRandom()
|
||||
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()
|
||||
err := ioutil.WriteFile(guidPath, uuid, 0600)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
uuid, err = ioutil.ReadFile(guidPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
321
keys.go
321
keys.go
|
@ -7,16 +7,19 @@ import (
|
|||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/big"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/foxboron/go-uefi/efi"
|
||||
"github.com/foxboron/go-uefi/efi/pecoff"
|
||||
"github.com/foxboron/go-uefi/efi/pkcs7"
|
||||
"github.com/foxboron/go-uefi/efi/signature"
|
||||
"github.com/foxboron/go-uefi/efi/util"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var RSAKeySize = 4096
|
||||
|
@ -30,161 +33,225 @@ var (
|
|||
KEKCert = filepath.Join(KeysPath, "KEK", "KEK.pem")
|
||||
DBKey = filepath.Join(KeysPath, "db", "db.key")
|
||||
DBCert = filepath.Join(KeysPath, "db", "db.pem")
|
||||
|
||||
DBPath = filepath.Join(DatabasePath, "files.db")
|
||||
|
||||
GUIDPath = filepath.Join(DatabasePath, "GUID")
|
||||
)
|
||||
|
||||
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)
|
||||
// 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(name string) ([]byte, []byte, error) {
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit)
|
||||
c := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
PublicKeyAlgorithm: x509.RSA,
|
||||
SignatureAlgorithm: x509.SHA256WithRSA,
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(5, 0, 0),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
Subject: pkix.Name{
|
||||
Country: []string{name},
|
||||
Country: []string{name},
|
||||
CommonName: name,
|
||||
},
|
||||
}
|
||||
priv, err := rsa.GenerateKey(rand.Reader, RSAKeySize)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return nil, nil, err
|
||||
}
|
||||
privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(priv)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to marshal private key: %v", err)
|
||||
}
|
||||
keyOut := new(bytes.Buffer)
|
||||
if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privateKeyBytes}); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to write data to key: %v", 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, 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)
|
||||
certOut := new(bytes.Buffer)
|
||||
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to write data to certificate: %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
|
||||
return keyOut.Bytes(), certOut.Bytes(), nil
|
||||
}
|
||||
|
||||
func SaveKey(k []byte, path string) {
|
||||
err := ioutil.WriteFile(fmt.Sprintf("%s.der", path), k, 0644)
|
||||
func SaveKey(k []byte, file string) error {
|
||||
os.MkdirAll(filepath.Dir(file), os.ModePerm)
|
||||
err := os.WriteFile(file, k, 0400)
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
out, err := exec.Command("/usr/bin/sbsiglist", strings.Split(args, " ")...).Output()
|
||||
func Enroll(uuid util.EFIGUID, cert, signerKey, signerPem []byte, efivar string) error {
|
||||
c := signature.NewSignatureList(signature.CERT_X509_GUID)
|
||||
c.AppendBytes(uuid, cert)
|
||||
buf := new(bytes.Buffer)
|
||||
signature.WriteSignatureList(buf, *c)
|
||||
key, err := util.ReadKey(signerKey)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed creating signature list: %s", err)
|
||||
return nil
|
||||
}
|
||||
return out
|
||||
crt, err := util.ReadCert(signerPem)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
signedBuf, err := efi.SignEFIVariable(key, crt, efivar, buf.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return efi.WriteEFIVariable(efivar, signedBuf)
|
||||
}
|
||||
|
||||
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)
|
||||
out, err := exec.Command("/usr/bin/sbvarsign", strings.Split(args, " ")...).Output()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed signing EFI variable: %s", err)
|
||||
func KeySync(guid util.EFIGUID, keydir string) error {
|
||||
PKKey, _ := os.ReadFile(filepath.Join(keydir, "PK", "PK.key"))
|
||||
PKPem, _ := os.ReadFile(filepath.Join(keydir, "PK", "PK.pem"))
|
||||
KEKKey, _ := os.ReadFile(filepath.Join(keydir, "KEK", "KEK.key"))
|
||||
KEKPem, _ := os.ReadFile(filepath.Join(keydir, "KEK", "KEK.pem"))
|
||||
dbPem, _ := os.ReadFile(filepath.Join(keydir, "db", "db.pem"))
|
||||
if err := Enroll(guid, dbPem, KEKKey, KEKPem, "db"); err != nil {
|
||||
return err
|
||||
}
|
||||
return out
|
||||
if err := Enroll(guid, KEKPem, PKKey, PKPem, "KEK"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Enroll(guid, PKPem, PKKey, PKPem, "PK"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
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)
|
||||
}
|
||||
|
||||
peFile, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
x509Cert, err := util.ReadCertFromFile(cert)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
sigs, err := pecoff.GetSignatures(peFile)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("%s: %w", file, err)
|
||||
}
|
||||
if len(sigs) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
for _, signature := range sigs {
|
||||
ok, err := pkcs7.VerifySignature(x509Cert, signature.Certificate)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if ok {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
stdout := out.String()
|
||||
for _, line := range strings.Split(stdout, "\n") {
|
||||
if strings.Contains(line, "Operation not permitted") {
|
||||
fmt.Println(stdout)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
// If we come this far we haven't found a signature that matches the cert
|
||||
return false, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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) {
|
||||
return PrintGenerateError(err2, "%s does not exist!", file)
|
||||
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
|
||||
if VerifyFile(cert, output) && ChecksumFile(file) == checksum {
|
||||
msg.Printf("%s has been signed...", file)
|
||||
return nil
|
||||
ok, err := VerifyFile(cert, output)
|
||||
if errors.Is(err, os.ErrNotExist) && (file != output) {
|
||||
// if the file does not exist and file is not the same as output
|
||||
// then we just catch the error and continue. This is expected
|
||||
// behaviour
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg2.Printf("Signing %s...", file)
|
||||
args := fmt.Sprintf("--key %s --cert %s --output %s %s", key, cert, output, file)
|
||||
_, err := exec.Command("sbsign", strings.Split(args, " ")...).Output()
|
||||
chk, err := ChecksumFile(file)
|
||||
if err != nil {
|
||||
return PrintGenerateError(err2, "Failed signing file: %s", err)
|
||||
return err
|
||||
}
|
||||
if ok && chk == checksum {
|
||||
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: %w", key, err)
|
||||
}
|
||||
|
||||
// We want to write the file back with correct permissions
|
||||
si, err := os.Stat(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed signing file: %w", err)
|
||||
}
|
||||
|
||||
peFile, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Cert, err := util.ReadCertFromFile(cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
Key, err := util.ReadKeyFromFile(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := pecoff.PECOFFChecksum(peFile)
|
||||
|
||||
sig, err := pecoff.CreateSignature(ctx, Cert, Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := pecoff.AppendToBinary(ctx, sig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = os.WriteFile(output, b, si.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Map up our default keys in a struct
|
||||
var SecureBootKeys = []struct {
|
||||
Key string
|
||||
Description string
|
||||
// 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
|
||||
// {
|
||||
|
@ -194,60 +261,34 @@ var SecureBootKeys = []struct {
|
|||
// },
|
||||
}
|
||||
|
||||
// Check if we have already intialized keys in the given output directory
|
||||
func CheckIfKeysInitialized(output string) bool {
|
||||
for _, key := range SecureBootKeys {
|
||||
path := filepath.Join(output, key.Key)
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func CreateUUID() []byte {
|
||||
id, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
// Initialize the secure boot keys needed to setup secure boot.
|
||||
// It creates the following keys:
|
||||
// * Platform Key (PK)
|
||||
// * Key Exchange Key (KEK)
|
||||
// * db (database)
|
||||
func InitializeSecureBootKeys(output string) error {
|
||||
if CheckIfKeysInitialized(output) {
|
||||
return nil
|
||||
}
|
||||
return []byte(id.String())
|
||||
}
|
||||
|
||||
func CreateGUID(output string) []byte {
|
||||
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)
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
func InitializeSecureBootKeys(output string) {
|
||||
os.MkdirAll(output, os.ModePerm)
|
||||
uuid := CreateGUID(output)
|
||||
// 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))
|
||||
keyfile, cert, err := CreateKey(key.Description)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := filepath.Join(output, key.Key)
|
||||
SaveKey(keyfile, filepath.Join(path, fmt.Sprintf("%s.key", key.Key)))
|
||||
SaveKey(cert, filepath.Join(path, fmt.Sprintf("%s.pem", key.Key)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
61
log.go
61
log.go
|
@ -1,61 +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
|
||||
err *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
|
||||
)
|
||||
|
||||
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)
|
||||
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)
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
var (
|
||||
OkSym = "✓"
|
||||
NotOkSym = "✗"
|
||||
WarnSym = "‼"
|
||||
UnkwnSym = "⁇"
|
||||
)
|
||||
var (
|
||||
OkSymText = "[+]"
|
||||
NotOkSymText = "[-]"
|
||||
WarnSymText = "[!]"
|
||||
UnkwnSymText = "[?]"
|
||||
)
|
||||
|
||||
var (
|
||||
ok string
|
||||
notok 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...)
|
||||
}
|
||||
|
||||
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(Okf(m, a...))
|
||||
}
|
||||
|
||||
func NotOkf(m string, a ...interface{}) string {
|
||||
return fmt.Sprintf("%s %s\n", notok, fmt.Sprintf(m, a...))
|
||||
}
|
||||
|
||||
// Print ok string to stdout
|
||||
func NotOk(m string, a ...interface{}) {
|
||||
Print(NotOkf(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(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(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) {
|
||||
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("SBCTL_UNICODE"); ok == "0" {
|
||||
OkSym = OkSymText
|
||||
NotOkSym = NotOkSymText
|
||||
WarnSym = WarnSymText
|
||||
UnkwnSym = UnkwnSymText
|
||||
}
|
||||
|
||||
ok = color.New(color.FgGreen, color.Bold).Sprintf(OkSym)
|
||||
notok = color.New(color.FgRed, color.Bold).Sprintf(NotOkSym)
|
||||
warn = color.New(color.FgYellow, color.Bold).Sprintf(WarnSym)
|
||||
unkwn = color.New(color.FgRed, color.Bold).Sprintf(UnkwnSym)
|
||||
PrintOn()
|
||||
}
|
316
sbctl.go
316
sbctl.go
|
@ -1,110 +1,108 @@
|
|||
package sbctl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"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 ""
|
||||
type LsblkEntry struct {
|
||||
Parttype string `json:"parttype"`
|
||||
Mountpoint string `json:"mountpoint"`
|
||||
Pttype string `json:"pttype"`
|
||||
Fstype string `json:"fstype"`
|
||||
}
|
||||
|
||||
func VerifyESP() {
|
||||
// Cache files we have looked at.
|
||||
checked := make(map[string]bool)
|
||||
type LsblkRoot struct {
|
||||
Blockdevices []LsblkEntry `json:"blockdevices"`
|
||||
}
|
||||
|
||||
espPath := GetESP()
|
||||
files := ReadFileDatabase(DBPath)
|
||||
msg.Printf("Verifying file database and EFI images in %s...", espPath)
|
||||
var espLocations = []string{
|
||||
"/boot",
|
||||
"/boot/efi",
|
||||
"/efi",
|
||||
}
|
||||
var ErrNoESP = errors.New("failed to find EFI system partition")
|
||||
|
||||
for _, file := range files {
|
||||
normalized := strings.Join(strings.Split(file.OutputFile, "/")[2:], "/")
|
||||
checked[normalized] = true
|
||||
// Slightly more advanced check
|
||||
func GetESP() (string, error) {
|
||||
|
||||
// Check output file exists before checking if it's signed
|
||||
if _, err := os.Stat(file.OutputFile); os.IsNotExist(err) {
|
||||
warning2.Printf("%s does not exist\n", file.OutputFile)
|
||||
} else if VerifyFile(DBCert, file.OutputFile) {
|
||||
msg2.Printf("%s is signed\n", file.OutputFile)
|
||||
} else {
|
||||
warning2.Printf("%s is not signed\n", file.OutputFile)
|
||||
for _, env := range []string{"SYSTEMD_ESP_PATH", "ESP_PATH"} {
|
||||
envEspPath, found := os.LookupEnv(env)
|
||||
if found {
|
||||
return envEspPath, nil
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
for _, location := range espLocations {
|
||||
// "Touch" a file inside all candiadate locations to trigger an
|
||||
// automount if there's an automount partition.
|
||||
os.Stat(fmt.Sprintf("%s/does-not-exist", location))
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
if VerifyFile(DBCert, path) {
|
||||
msg2.Printf("%s is signed\n", path)
|
||||
} else {
|
||||
warning2.Printf("%s is not signed\n", path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
out, err := exec.Command(
|
||||
"lsblk",
|
||||
"--json",
|
||||
"--output", "PARTTYPE,MOUNTPOINT,PTTYPE,FSTYPE").Output()
|
||||
if err != nil {
|
||||
log.Println(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
|
||||
}
|
||||
}
|
||||
|
||||
for _, entryToCheck := range []*LsblkEntry{pathEfiEntry, pathBootEntry, pathBootEfiEntry} {
|
||||
if entryToCheck == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if entryToCheck.Pttype != "gpt" {
|
||||
continue
|
||||
}
|
||||
|
||||
if entryToCheck.Fstype != "vfat" {
|
||||
continue
|
||||
}
|
||||
|
||||
if entryToCheck.Parttype != "c12a7328-f81f-11d2-ba4b-00a0c93ec93b" {
|
||||
continue
|
||||
}
|
||||
|
||||
return entryToCheck.Mountpoint, nil
|
||||
}
|
||||
|
||||
return "", ErrNoESP
|
||||
}
|
||||
|
||||
func Sign(file, output string, enroll bool) error {
|
||||
file, err := filepath.Abs(file)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if output == "" {
|
||||
|
@ -112,23 +110,31 @@ func Sign(file, output string, enroll bool) error {
|
|||
} else {
|
||||
output, err = filepath.Abs(output)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = nil
|
||||
|
||||
files := ReadFileDatabase(DBPath)
|
||||
files, err := ReadFileDatabase(DBPath)
|
||||
if err != nil {
|
||||
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)
|
||||
// return early if signing fails
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
checksum := ChecksumFile(file)
|
||||
checksum, err := ChecksumFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
|
@ -138,68 +144,29 @@ 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}
|
||||
WriteFileDatabase(DBPath, files)
|
||||
if err := WriteFileDatabase(DBPath, files); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func ListFiles() {
|
||||
files := ReadFileDatabase(DBPath)
|
||||
for path, s := range files {
|
||||
msg.Printf("File: %s", path)
|
||||
if path != s.OutputFile {
|
||||
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, error) {
|
||||
tmpFile, e := ioutil.TempFile("/var/tmp", "initramfs-")
|
||||
if e != nil {
|
||||
err.Println("Cannot create temporary file", e)
|
||||
for _, file := range []string{microcode, initramfs} {
|
||||
if _, err := os.Stat(file); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", file, errors.Unwrap(err))
|
||||
}
|
||||
}
|
||||
|
||||
tmpFile, err := os.CreateTemp("/var/tmp", "initramfs-")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
one, _ := os.Open(microcode)
|
||||
|
@ -208,14 +175,14 @@ func CombineFiles(microcode, initramfs string) (*os.File, error) {
|
|||
two, _ := os.Open(initramfs)
|
||||
defer two.Close()
|
||||
|
||||
_, e = io.Copy(tmpFile, one)
|
||||
if e != nil {
|
||||
return nil, PrintGenerateError(err2, "failed to append microcode file to output:", err)
|
||||
_, err = io.Copy(tmpFile, one)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to append microcode file to output: %w", err)
|
||||
}
|
||||
|
||||
_, e = io.Copy(tmpFile, two)
|
||||
if e != nil {
|
||||
return nil, PrintGenerateError(err2, "failed to append initramfs file to output:", err)
|
||||
_, err = io.Copy(tmpFile, two)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to append initramfs file to output: %w", err)
|
||||
}
|
||||
|
||||
return tmpFile, nil
|
||||
|
@ -223,63 +190,40 @@ func CombineFiles(microcode, initramfs string) (*os.File, error) {
|
|||
|
||||
func CreateBundle(bundle Bundle) error {
|
||||
var microcode string
|
||||
make_bundle := false
|
||||
|
||||
if bundle.IntelMicrocode != "" {
|
||||
microcode = bundle.IntelMicrocode
|
||||
make_bundle = true
|
||||
} else if bundle.AMDMicrocode != "" {
|
||||
microcode = bundle.AMDMicrocode
|
||||
make_bundle = true
|
||||
}
|
||||
|
||||
tmpFile, err := CombineFiles(microcode, bundle.Initramfs)
|
||||
if make_bundle {
|
||||
tmpFile, err := CombineFiles(microcode, bundle.Initramfs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tmpFile.Name())
|
||||
bundle.Initramfs = tmpFile.Name()
|
||||
}
|
||||
|
||||
out, err := GenerateBundle(&bundle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tmpFile.Name())
|
||||
bundle.Initramfs = tmpFile.Name()
|
||||
|
||||
out := GenerateBundle(&bundle)
|
||||
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....")
|
||||
bundles := ReadBundleDatabase(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 PrintGenerateError(err, "Error generating EFI bundles")
|
||||
}
|
||||
|
||||
if !out_sign {
|
||||
return PrintGenerateError(err, "Error signing EFI bundles")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ListBundles() {
|
||||
bundles := ReadBundleDatabase(BundleDBPath)
|
||||
for key, bundle := range bundles {
|
||||
FormatBundle(key, bundle)
|
||||
// Checks if sbctl is setup on this computer
|
||||
func CheckSbctlInstallation(path string) bool {
|
||||
if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
Integration tests
|
||||
=================
|
||||
|
||||
Follow https://github.com/anatol/vmtest/blob/master/docs/prepare_image.md
|
||||
Expects `/usr/share/edk2-ovmf/x64/OVMF_CODE.secboot.fd`
|
|
@ -0,0 +1,37 @@
|
|||
// +build integration
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/foxboron/sbctl/tests/utils"
|
||||
)
|
||||
|
||||
func TestKeyEnrollment(t *testing.T) {
|
||||
conf := utils.NewConfig()
|
||||
conf.AddFile("sbctl")
|
||||
|
||||
utils.WithVM(conf,
|
||||
func(vm *utils.TestVM) {
|
||||
t.Run("Enroll Keys", vm.RunTest("./integrations/enroll_keys_test.go"))
|
||||
})
|
||||
|
||||
utils.WithVM(conf,
|
||||
func(vm *utils.TestVM) {
|
||||
t.Run("Check SecureBoot enabled", vm.RunTest("./integrations/secure_boot_enabled_test.go"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
cmd := exec.Command("go", "build", "../cmd/sbctl")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// +build integrations
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/foxboron/go-uefi/efi"
|
||||
"github.com/foxboron/sbctl/tests/utils"
|
||||
)
|
||||
|
||||
func TestEnrollKeys(t *testing.T) {
|
||||
|
||||
if efi.GetSecureBoot() {
|
||||
t.Fatal("in secure boot mode")
|
||||
}
|
||||
|
||||
if !efi.GetSetupMode() {
|
||||
t.Fatal("not in setup mode")
|
||||
}
|
||||
|
||||
utils.Exec("rm -rf /usr/share/secureboot")
|
||||
utils.Exec("/mnt/sbctl status")
|
||||
utils.Exec("/mnt/sbctl create-keys")
|
||||
utils.Exec("/mnt/sbctl enroll-keys")
|
||||
|
||||
if efi.GetSetupMode() {
|
||||
t.Fatal("in setup mode")
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// +build integrations
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/foxboron/go-uefi/efi"
|
||||
"github.com/foxboron/sbctl/tests/utils"
|
||||
)
|
||||
|
||||
func TestSecureBootEnabled(t *testing.T) {
|
||||
|
||||
utils.Exec("/mnt/sbctl status")
|
||||
|
||||
if !efi.GetSecureBoot() {
|
||||
t.Fatal("not in secure boot mode")
|
||||
}
|
||||
|
||||
if efi.GetSetupMode() {
|
||||
t.Fatal("in setup mode")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
#!/bin/bash
|
||||
dd if=/dev/zero of=rootfs.raw bs=1G count=1
|
||||
mkfs.ext4 rootfs.raw
|
||||
sudo losetup -fP rootfs.raw
|
||||
mkdir rootfs
|
||||
sudo mount /dev/loop0 rootfs
|
||||
sudo pacstrap rootfs base openssh sbsigntools terminus-font
|
||||
|
||||
echo "[Match]
|
||||
Name=enp0s3
|
||||
|
||||
[Network]
|
||||
DHCP=yes" | sudo tee rootfs/etc/systemd/network/20-wired.network
|
||||
|
||||
sudo sed -i '/^root/ { s/:x:/::/ }' rootfs/etc/passwd
|
||||
sudo sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' rootfs/etc/ssh/sshd_config
|
||||
sudo sed -i 's/#PermitEmptyPasswords no/PermitEmptyPasswords yes/' rootfs/etc/ssh/sshd_config
|
||||
echo "shared /mnt 9p rw,sync,dirsync,access=client,trans=virtio 0 0" | sudo tee rootfs/etc/fstab
|
||||
echo "FONT=ter-132n" | sudo tee rootfs/etc/vconsole.conf
|
||||
|
||||
sudo arch-chroot rootfs systemctl enable sshd systemd-networkd
|
||||
sudo rm rootfs/var/cache/pacman/pkg/*
|
||||
sudo umount rootfs
|
||||
sudo losetup -d /dev/loop0
|
||||
rm -r rootfs
|
||||
qemu-img create -o backing_file=rootfs.raw,backing_fmt=raw -f qcow2 rootfs.cow
|
|
@ -0,0 +1,24 @@
|
|||
#!/bin/bash
|
||||
dd if=/dev/zero of=rootfs.raw bs=1G count=1
|
||||
mkfs.ext4 rootfs.raw
|
||||
sudo losetup -fP rootfs.raw
|
||||
mkdir rootfs
|
||||
sudo mount /dev/loop0 rootfs
|
||||
sudo pacstrap rootfs base openssh
|
||||
|
||||
echo "[Match]
|
||||
Name=enp0s3
|
||||
|
||||
[Network]
|
||||
DHCP=yes" | sudo tee rootfs/etc/systemd/network/20-wired.network
|
||||
|
||||
sudo sed -i '/^root/ { s/:x:/::/ }' rootfs/etc/passwd
|
||||
sudo sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' rootfs/etc/ssh/sshd_config
|
||||
sudo sed -i 's/#PermitEmptyPasswords no/PermitEmptyPasswords yes/' rootfs/etc/ssh/sshd_config
|
||||
|
||||
sudo arch-chroot rootfs systemctl enable sshd systemd-networkd
|
||||
sudo rm rootfs/var/cache/pacman/pkg/*
|
||||
sudo umount rootfs
|
||||
sudo losetup -d /dev/loop0
|
||||
rm -r rootfs
|
||||
qemu-img create -o backing_file=rootfs.raw,backing_fmt=raw -f qcow2 rootfs.cow
|
Binary file not shown.
|
@ -0,0 +1,54 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"log"
|
||||
"math/big"
|
||||
"time"
|
||||
)
|
||||
|
||||
func CreateKey() ([]byte, []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,
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(5, 0, 0),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
Subject: pkix.Name{
|
||||
Country: []string{"Test Suite Key"},
|
||||
CommonName: "Test Suite",
|
||||
},
|
||||
}
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 4098)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(priv)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to marshal private key: %v", err)
|
||||
}
|
||||
keyOut := new(bytes.Buffer)
|
||||
if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privateKeyBytes}); err != nil {
|
||||
log.Fatalf("Failed to write data to key.pem: %v", err)
|
||||
}
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &c, &c, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create certificate: %v", err)
|
||||
}
|
||||
certOut := new(bytes.Buffer)
|
||||
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
|
||||
log.Fatalf("Failed to write data to cert.pem: %v", err)
|
||||
}
|
||||
return keyOut.Bytes(), certOut.Bytes()
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type TestConfig struct {
|
||||
Shared string
|
||||
Ovmf string
|
||||
Secboot string
|
||||
Files []string
|
||||
}
|
||||
|
||||
func NewConfig() *TestConfig {
|
||||
dir, _ := os.MkdirTemp("", "go-uefi-test")
|
||||
ret := &TestConfig{
|
||||
Shared: dir,
|
||||
Ovmf: path.Join(dir, "OVMF_VARS.fd"),
|
||||
Secboot: path.Join(dir, "OVMF_CODE.secboot.fd"),
|
||||
Files: []string{},
|
||||
}
|
||||
CopyFile("/usr/share/edk2-ovmf/x64/OVMF_VARS.fd", ret.Ovmf)
|
||||
CopyFile("/usr/share/edk2-ovmf/x64/OVMF_CODE.secboot.fd", ret.Secboot)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (tc *TestConfig) AddFile(file string) {
|
||||
dst := path.Join(tc.Shared, filepath.Base(file))
|
||||
tc.Files = append(tc.Files, dst)
|
||||
CopyFile(file, dst)
|
||||
}
|
||||
|
||||
func (tc *TestConfig) AddBytes(b []byte, name string) {
|
||||
dst := path.Join(tc.Shared, name)
|
||||
tc.Files = append(tc.Files, dst)
|
||||
os.WriteFile(dst, b, 0644)
|
||||
}
|
||||
|
||||
func (tc *TestConfig) Remove() {
|
||||
os.RemoveAll(tc.Shared)
|
||||
}
|
||||
|
||||
func CopyFile(src, dst string) bool {
|
||||
source, err := os.Open(src)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer source.Close()
|
||||
|
||||
f, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
io.Copy(f, source)
|
||||
si, err := os.Stat(src)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = os.Chmod(dst, si.Mode())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/anatol/vmtest"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func StartOVMF(conf TestConfig) *vmtest.Qemu {
|
||||
params := []string{
|
||||
"-machine", "type=q35,smm=on,accel=kvm",
|
||||
"-boot", "order=c,menu=on,strict=on",
|
||||
"-net", "none",
|
||||
"-global", "driver=cfi.pflash01,property=secure,value=on",
|
||||
"-global", "ICH9-LPC.disable_s3=1",
|
||||
"-drive", "if=pflash,format=raw,unit=0,file=/usr/share/edk2-ovmf/x64/OVMF_CODE.secboot.fd,readonly",
|
||||
"-drive", "if=pflash,format=raw,unit=1,file=ovmf/OVMF_VARS.fd",
|
||||
}
|
||||
if conf.Shared != "" {
|
||||
params = append(params, "-drive", fmt.Sprintf("file=fat:rw:%s", conf.Shared))
|
||||
}
|
||||
opts := vmtest.QemuOptions{
|
||||
Params: params,
|
||||
Verbose: false, //testing.Verbose(),
|
||||
Timeout: 50 * time.Second,
|
||||
}
|
||||
// Run QEMU instance
|
||||
ovmf, err := vmtest.NewQemu(&opts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ovmf.ConsoleExpect("Shell>")
|
||||
return ovmf
|
||||
}
|
||||
|
||||
type TestVM struct {
|
||||
qemu *vmtest.Qemu
|
||||
conn *ssh.Client
|
||||
}
|
||||
|
||||
func WithVM(conf *TestConfig, fn func(vm *TestVM)) {
|
||||
vm := StartVM(conf)
|
||||
defer vm.Close()
|
||||
fn(vm)
|
||||
}
|
||||
|
||||
// TODO: Wire this up with 9p instead of ssh
|
||||
func StartVM(conf *TestConfig) *TestVM {
|
||||
params := []string{
|
||||
"-machine", "type=q35,smm=on,accel=kvm",
|
||||
"-debugcon", "file:debug.log", "-global", "isa-debugcon.iobase=0x402",
|
||||
"-netdev", "user,id=net0,hostfwd=tcp::10022-:22",
|
||||
"-device", "virtio-net-pci,netdev=net0",
|
||||
"-nic", "user,model=virtio-net-pci",
|
||||
"-fsdev", fmt.Sprintf("local,id=test_dev,path=%s,security_model=none", conf.Shared),
|
||||
"-device", "virtio-9p-pci,fsdev=test_dev,mount_tag=shared",
|
||||
"-global", "driver=cfi.pflash01,property=secure,value=on",
|
||||
"-global", "ICH9-LPC.disable_s3=1",
|
||||
"-drive", fmt.Sprintf("if=pflash,format=raw,unit=0,file=%s,readonly", conf.Secboot),
|
||||
"-drive", fmt.Sprintf("if=pflash,format=raw,unit=1,file=%s", conf.Ovmf),
|
||||
"-m", "8G", "-smp", "2", "-enable-kvm", "-cpu", "host",
|
||||
}
|
||||
opts := vmtest.QemuOptions{
|
||||
OperatingSystem: vmtest.OS_LINUX,
|
||||
Kernel: "kernel/bzImage",
|
||||
Params: params,
|
||||
Disks: []vmtest.QemuDisk{{Path: "kernel/rootfs.cow", Format: "qcow2"}},
|
||||
Append: []string{"root=/dev/sda", "quiet", "rw"},
|
||||
Verbose: false, //testing.Verbose()
|
||||
Timeout: 50 * time.Second,
|
||||
}
|
||||
// Run QEMU instance
|
||||
qemu, err := vmtest.NewQemu(&opts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
qemu.ConsoleExpect("login:")
|
||||
|
||||
config := &ssh.ClientConfig{
|
||||
User: "root",
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
conn, err := ssh.Dial("tcp", "localhost:10022", config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &TestVM{qemu, conn}
|
||||
}
|
||||
|
||||
func (t *TestVM) Run(command string) (ret string, err error) {
|
||||
sess, err := t.conn.NewSession()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
output, err := sess.CombinedOutput(command)
|
||||
return string(output), err
|
||||
}
|
||||
|
||||
func (t *TestVM) Close() {
|
||||
t.conn.Close()
|
||||
t.qemu.Shutdown()
|
||||
}
|
||||
|
||||
func (t *TestVM) CopyFile(path string) {
|
||||
cmd := exec.Command("scp", "-P10022", path, "root@localhost:/")
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (tvm *TestVM) RunTest(path string) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
testName := fmt.Sprintf("%s%s", filepath.Base(path), ".test")
|
||||
cmd := exec.Command("go", "test", "-o", testName, "-c", path)
|
||||
if testing.Verbose() {
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
}
|
||||
if err := cmd.Run(); err != nil {
|
||||
tvm.Close()
|
||||
t.Error(err)
|
||||
}
|
||||
tvm.CopyFile(testName)
|
||||
os.Remove(testName)
|
||||
|
||||
ret, err := tvm.Run(fmt.Sprintf("/%s -test.v", testName))
|
||||
t.Logf("\n%s", ret)
|
||||
if err != nil {
|
||||
tvm.Close()
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Exec(c string) error {
|
||||
args := strings.Split(c, " ")
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
127
util.go
127
util.go
|
@ -1,27 +1,128 @@
|
|||
package sbctl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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, error) {
|
||||
hasher := sha256.New()
|
||||
s, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
hasher.Write(s)
|
||||
|
||||
return hex.EncodeToString(hasher.Sum(nil)), nil
|
||||
}
|
||||
|
||||
func ChecksumFile(file string) string {
|
||||
hasher := sha256.New()
|
||||
s, err := ioutil.ReadFile(file)
|
||||
hasher.Write(s)
|
||||
func CreateDirectory(path string) error {
|
||||
if _, err := os.Stat(path); errors.Is(err, os.ErrExist) {
|
||||
return nil
|
||||
} else if errors.Is(err, os.ErrNotExist) {
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(path, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadOrCreateFile(filePath string) ([]byte, error) {
|
||||
// Try to access or create the file itself
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
// Errors will mainly happen due to permissions or non-existing file
|
||||
if os.IsNotExist(err) {
|
||||
// First, guarantee the directory's existence
|
||||
// os.MkdirAll simply returns nil if the directory already exists
|
||||
fileDir := filepath.Dir(filePath)
|
||||
if err = os.MkdirAll(fileDir, os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
file.Close()
|
||||
|
||||
// Create zero-length f, which is equivalent to what would be read from empty file
|
||||
f = make([]byte, 0)
|
||||
} else {
|
||||
if os.IsPermission(err) {
|
||||
return nil, err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return hex.EncodeToString(hasher.Sum(nil))
|
||||
return f, 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")
|
||||
var ErrNotImmutable = errors.New("file is not immutable")
|
||||
|
||||
func IsImmutable(file string) error {
|
||||
f, err := os.Open(file)
|
||||
// Files in efivarfs might not exist. Ignore them
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
attr, err := GetAttr(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (attr & FS_IMMUTABLE_FL) != 0 {
|
||||
return ErrImmutable
|
||||
}
|
||||
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]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue