Compare commits

...

149 Commits

Author SHA1 Message Date
Morten Linderud bedb8e8c83
main: fixed typo
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-09-05 15:33:17 +02:00
Morten Linderud 73be015150
list-*: Fixup linting
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-09-05 15:33:06 +02:00
Morten Linderud 9ff60964d5
list-*: Ensure we list all entries on error
Return some formatted string and return nil instead of aborting the
entire listing when we encounter an error.

Fixes: https://github.com/Foxboron/sbctl/issues/88

Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-09-05 14:54:45 +02:00
Morten Linderud 947b6ba8e8
Formating: Added some misc formatting
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-09-05 14:42:31 +02:00
Morten Linderud 69e52d3efb
Makefile: Add completions
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-09-05 14:41:59 +02:00
Morten Linderud 66d1482dd9
go.sum: Forgot a /x/text dependency?
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-07-22 21:12:29 +02:00
Morten Linderud 05b4f88539
go.mod: Update go-uefi to latest master
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-07-22 21:11:19 +02:00
Morten Linderud 858e129c2d
Merge commit 'refs/pull/77/head' of github.com:Foxboron/sbctl 2021-07-22 21:10:42 +02:00
Morten Linderud dc88c5c93d
Merge commit 'refs/pull/81/head' of github.com:Foxboron/sbctl 2021-07-22 21:10:30 +02:00
Morten Linderud 868b0a9547
Merge commit 'refs/pull/83/head' of github.com:Foxboron/sbctl 2021-07-22 21:09:32 +02:00
Oskar Kohout ea325ca46f fix: restrict key file permissions 2021-07-16 18:32:47 +02:00
Hugo Osvaldo Barrera 1aca349c90 Attempt to trigger automounts to find the ESP
The ESP may be an automount partition, so try touching a file in each
candidate location so as to trigger an automounts.

This is the same way systemd attempts to find it:
https://github.com/systemd/systemd/blob/f565b86/src/shared/bootspec.c#L1014-L1018

I've also changed the function to return an error if no ESP is found.
The previous behaviour (an empty string) just results in a crash later
on.

When no ESP is found, the `bundle` command will have no default for the
`esp` flag. Passing an empty string to it as a default results in no
value being show in the output of `--help`.

This seemed like the most reasonable compromise instead of panicking.

Fixes #78
2021-06-28 20:37:56 +02:00
Morten Linderud 78cdabfa6d
sbctl/sign: the -o option would fail on non-existing output file
A few options where mixes around during the refactor. We also need to
capture the off case of failing signature verification on the output
file when it doesn't exist.

Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-28 12:34:19 +02:00
Morten Linderud 1614cfbd2e
logging: Rename to SBCTL_UNICODE, add manpage
Copypaste error from POC code landed in the released version. Also added
a quick note to the man page about the switch.

Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-19 22:58:57 +02:00
Morten Linderud 62fc79175f
Merge commit 'refs/pull/76/head' of github.com:Foxboron/sbctl 2021-06-19 22:57:04 +02:00
Haochen Tong a88a5b7e42
logging: use simple unicode symbols 2021-06-19 14:44:53 +08:00
Érico Nogueira 9e0ea82854 sbctl/verify: remove accidental newline
Was leading to double newlines in the "sbctl verify" output.

Signed-off-by: Érico Nogueira <erico.erc@gmail.com>
2021-06-17 00:30:17 -03:00
Morten Linderud 2b6b3f92a1
Forgot srcinfo
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-14 00:30:08 +02:00
Morten Linderud 2261630dbd
Updated sbctl-git
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-14 00:29:19 +02:00
Morten Linderud a43373ce3f
sbctl: Error looks better then Fatal
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-14 00:24:43 +02:00
Morten Linderud faf366f5e7
Merge commit 'refs/pull/75/head' of github.com:Foxboron/sbctl 2021-06-13 19:42:36 +02:00
Morten Linderud d8af3d80a1
sbctl/status: refactor to cleaner code
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-13 19:41:03 +02:00
Morten Linderud 0a0dc36030
Updated readme
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-13 19:01:08 +02:00
Morten Linderud 4ba10b9393
sbctl/remove-*: Added remove logging to the commands 2021-06-13 00:01:44 +02:00
Morten Linderud 64cbd26bb3
sbctl/sign: Added proper messages
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-12 21:38:13 +02:00
Morten Linderud 4de27722ee
Replace fmt.Println with internal logging
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-12 21:37:55 +02:00
Morten Linderud 070702abda
Removed extra print logging
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-12 21:22:09 +02:00
Hugo Osvaldo Barrera 95458395f1 Update some references to latest go-uefi
Current `master` of both branches cannot build together otherwise.
2021-06-12 01:45:54 +02:00
Morten Linderud 7c23cf35da
sign-all: don't exit if -g is used and we can't create bundles
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-05 14:36:02 +02:00
Morten Linderud 7e01fec4fa
generate-bundles: Return better error messages
Current errors doesn't really give you any details what is failing if
there is a failure. This rearranges it a little bit

Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-05 14:34:54 +02:00
Morten Linderud fb79c38977
bundle: Ensure files exist before combining
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-05 14:33:28 +02:00
Morten Linderud 32ac267834
Merge branch 'morten/go-uefi' 2021-06-05 14:08:34 +02:00
Morten Linderud 5d528ff82a
Merge branch 'morten/cli' 2021-06-05 14:08:11 +02:00
Morten Linderud 240786b7d8
Idk go modules
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-02 21:56:03 +02:00
Morten Linderud 7192e52fa1
keys: Move from sbsigntools to go-uefi
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-02 21:56:02 +02:00
Morten Linderud 2031e3a210
keys: Removed unused functions
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-02 21:56:02 +02:00
Morten Linderud 03f9ab94d9
Removed erronous println
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-02 21:56:02 +02:00
Morten Linderud ec7dba937e
Silence ci
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-02 21:56:02 +02:00
Morten Linderud d108dbc9e0
Fixup
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-02 21:56:02 +02:00
Morten Linderud 683afec1d7
tests: Add secureboot enabled test
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-02 21:56:02 +02:00
Morten Linderud 95170e5117
tests: Fixup secureboot enrollment test
Also split out Exec, we need it in more tests

Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-02 21:56:02 +02:00
Morten Linderud 9587644626
sbctl/enroll-keys: Implement support for enrolling keys with go-uefi
This enrolls the keys using go-uefi. Essentially it reworks the
sbkeysync into a set of enroll commands taken from the go-uefi test
suite.

Preferably this should be more flexible e.g for key rotation.

Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-02 21:56:02 +02:00
Morten Linderud 2d65668632
sbctl: Implement check for sbctl installation
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-02 21:56:00 +02:00
Morten Linderud d04e117cbc
sbctl/create-keys: Rework output and keypath handling
In the future we might want to initialize a new set of keys. It makes
sense to pass the output directory so we can create new keys directly in
an alternative path and overwrite, e.g for key rotation.

Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-02 21:55:37 +02:00
Morten Linderud 1bf2810228
keys: Rework key creation
With go-uefi we don't need anything else then a certificate and a
keyfile. This simplifies the key creation to only care about these two
byte slices and saving them.

No signing is done here.

Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-02 21:55:37 +02:00
Morten Linderud ac8723dacd
util: Ensure we ignore when files do not exist in efivarfs
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-02 21:55:37 +02:00
Morten Linderud 13bbb90c03
sbctl/enroll-keys: Refactor immutable checking
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-02 21:55:37 +02:00
Morten Linderud 28eee4464d
sbctl/status: Do not fail if sbctl is not initialized
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-02 21:55:37 +02:00
Morten Linderud 0781f6bb98
sbctl/create-keys: Move up the GUID logic
We also make a helper for creating directories in a proper way

Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-02 21:55:37 +02:00
Morten Linderud 091b831f0b
tests: Added buildtag
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-02 21:55:36 +02:00
Morten Linderud 3300e790a9
tests/utils: Add keys to struct
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-02 21:55:36 +02:00
Morten Linderud a738c8c2e3
Fixed gitignore and go modules
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-02 21:55:36 +02:00
Morten Linderud 635be0683f
Added test suite
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-06-02 21:55:36 +02:00
Morten Linderud ae1aec15fb
sbctl: Ensure all commands inherit stdout turning off
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 15:18:09 +02:00
Morten Linderud 550b4e6365
Move global flags to persistent
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:47:46 +02:00
Morten Linderud ba0cee8115
Make lint happy
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:47:46 +02:00
Morten Linderud 57a1c93eb9
Remove last of the log.* stuff
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:47:45 +02:00
Morten Linderud 6b0242c953
Added print layout for key syncing
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:46:38 +02:00
Morten Linderud fe514e1af7
Added errors to WriteFileDatabase
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:46:38 +02:00
Morten Linderud 0d121672ca
Move verify to top-level
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:46:37 +02:00
Morten Linderud 8b4fc40724
Added internal functions for checked paths, and CheckMSDos
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:46:08 +02:00
Morten Linderud b49ebbb8bf
Added CanVerifyFiles
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:46:08 +02:00
Morten Linderud 6dfc186d43
enroll changes
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:46:08 +02:00
Morten Linderud 3f05d1df52
Propegate errors better
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:46:08 +02:00
Morten Linderud a318695f44
Moved generate-bundles top-level
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:46:08 +02:00
Morten Linderud 3454841a75
Moved create-keys top-level
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:46:07 +02:00
Morten Linderud 97435cc48e
More internal restructuring
Move more logic top-level, move prints to top-level

Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:46:07 +02:00
Morten Linderud f01453a978
Change immutable error a little bit
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:46:07 +02:00
Morten Linderud 235238c987
Fixed lint issues
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:46:07 +02:00
Morten Linderud 342ba34a17
Fixup
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:46:07 +02:00
Morten Linderud a5e0551e56
GUID package
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:46:07 +02:00
Morten Linderud 3505f1b571
New structure
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:46:05 +02:00
Morten Linderud adadb52e73
Give status the ability to display owner GUID
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:43:05 +02:00
Morten Linderud 877ab49ae6
Implement GetGUID
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:43:05 +02:00
Morten Linderud d0022cb3b2
Added BundleIter
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:43:05 +02:00
Morten Linderud 70b00f3184
Added new error
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:43:05 +02:00
Morten Linderud 30e16f5bd7
Catch for unknown command
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:43:05 +02:00
Morten Linderud fb9b3c7b33
🤷
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:43:05 +02:00
Morten Linderud 3d7f094988
Added an iter function
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:43:05 +02:00
Morten Linderud 23381e0111
Added NotOK instead of "Error". Makes more sense semantically
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:43:05 +02:00
Morten Linderud 1508b290d6
Moved json out function
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:43:04 +02:00
Morten Linderud 3568e9d34b
sbctl: Buble up errors from the "library"
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:43:04 +02:00
Morten Linderud b82e17e2ed
Return errors when generating bundles
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:43:04 +02:00
Morten Linderud bb78cf9c01
Remove previous logging, improve error bubling
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:43:04 +02:00
Morten Linderud a05e6c8fb8
Fixed commands with colors off
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:43:04 +02:00
Morten Linderud 2a53d5200c
Added list-bundles setup
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:43:04 +02:00
Morten Linderud 62d653d0f8
Added status new format
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:43:04 +02:00
Morten Linderud 431363f285
Added list-files new WIP for commands
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:43:03 +02:00
Morten Linderud 7a4defc0c1
Added deps
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:43:03 +02:00
Morten Linderud 0d249b25df
Added bundle cli format
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:43:03 +02:00
Morten Linderud 955c547743
Added more fidelity to the logging methods
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:43:03 +02:00
Morten Linderud 1b7f188e0f
Added new print module
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:43:03 +02:00
Morten Linderud 10ff8d2a65
man: Mention environment variables for ESP location
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:34:21 +02:00
Morten Linderud ba6dfc183e
sbctl/bundle: Do not default to ESP for fetching kernel and initramfs
Most distros (I think) default to stuffing this into `/boot` so our ESP
selection is going to mess this up more often then not.

Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-30 14:15:02 +02:00
Morten Linderud a4f56b7127
Merge commit 'refs/pull/72/head' of github.com:Foxboron/sbctl 2021-05-30 14:12:25 +02:00
igo95862 db8bbe2826
Add SYSTEMD_ESP_PATH and ESP_PATH environment variables support 2021-05-30 15:05:50 +03:00
igo95862 cca2a12a7c
Improved GetEsp function.
Now checks /efi,/boot and /boot/efi for gpt partition table,
vfat filesystem and ESP partition GUID.
2021-05-30 14:38:22 +03:00
Morten Linderud d7e4f3b603
Merge commit 'refs/pull/66/head' of github.com:Foxboron/sbctl 2021-05-22 18:22:49 +02:00
Morten Linderud 35e68e55cc
Merge commit 'refs/pull/68/head' of github.com:Foxboron/sbctl 2021-05-22 18:22:39 +02:00
Morten Linderud b9eafb6a88
Merge commit 'refs/pull/70/head' of github.com:Foxboron/sbctl 2021-05-22 18:22:30 +02:00
Morten Linderud bca692f1e1
Updated readme for libera
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-21 15:32:45 +02:00
Hugo Osvaldo Barrera 0ba4f65dac Tweak unconvincing working 2021-05-21 10:20:13 +02:00
Hugo Barrera 9efc268827
Typo
Co-authored-by: Érico Nogueira Rolim <34201958+ericonr@users.noreply.github.com>
2021-05-21 10:18:58 +02:00
Hugo Barrera 7839c5f47b
Update docs/sbctl.8.txt
Co-authored-by: Érico Nogueira Rolim <34201958+ericonr@users.noreply.github.com>
2021-05-21 10:17:55 +02:00
Hugo Osvaldo Barrera 7b654ac28c Typos 2021-05-21 01:44:03 +02:00
Hugo Osvaldo Barrera fd444c444c Refine docs based on feedback 2021-05-21 01:20:42 +02:00
Hugo Osvaldo Barrera 74b581384f Extend the documentation a bit 2021-05-21 00:12:56 +02:00
Hugo Barrera 09181324a0
Update man entry for default cmdline
The default cmdline is `/etc/kernel/cmdline`, but the man page said `/proc/cmdline`.
2021-05-18 02:04:40 +00:00
igo95862 d3feae2791
Remove ioutil
Deprecated in GO 1.16
2021-05-16 21:14:42 +03:00
Morten Linderud f53632ebd2
util: Expand array in print generator
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-13 12:34:19 +02:00
Morten Linderud 35ebd7bba9
bundles: Handle command not found errors
Fixes #64

Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-05-13 12:33:58 +02:00
Morten Linderud e63eb3d6b1
Merge commit 'refs/pull/61/head' of github.com:Foxboron/sbctl 2021-05-09 16:49:18 +02:00
Morten Linderud 44d597c74e
Merge commit 'refs/pull/54/head' of github.com:Foxboron/sbctl 2021-05-09 16:43:50 +02:00
igo95862 e55ef14bf4
Redirect objcopy stderr to parent stderr 2021-05-05 11:15:50 +03:00
igo95862 02885c41db
Use argument list for objcopy instead of split by whitespace
The order of .splash argument has changed but I don't
believe it will have an effect on result.
2021-05-05 11:01:35 +03:00
igo95862 7d6d2c76ab
Directly pass arguments to subprocesses instead of args spliting 2021-05-04 22:16:57 +03:00
Morten Linderud 9060461b35
sbctl: Inverted bool broke key enrollment
Should get that integration tests.

Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-04-25 12:58:44 +02:00
Morten Linderud 3fcd3b0e84
Updated srcinfo
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-04-24 17:45:17 +02:00
Morten Linderud a33d0b40d8
Fixed sbctl hooks in PKGBUILD
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-04-24 17:41:15 +02:00
Érico Nogueira 63876f01d5 Use x/sys/unix for ioctl instead of rolling our own.
Keep FS_* constants which aren't listed in the package, and leave
SetAttr there, even though it's currently not used.

Leave a comment about this implementation not working on 64-bit big
endian systems. Chances of this software being run on such a platform
are very low, since at the moment, to my knowledge, Secure Boot on
64-bit big endian can only happen with aarch64_be, which is quite rare.
2021-04-18 00:38:49 -03:00
Morten Linderud 59ec7a813f
sbctl: IsImmutable should return false if the file does not exist
Weird efivarfs quirk is that sometimes empty vars have no file. This
means they are not immutable and we can write to them.

Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-04-18 03:29:29 +02:00
Morten Linderud a6445c4a2b
sbctl: Create valid x509 certs for the kernel
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-04-16 22:13:55 +02:00
Morten Linderud 4df69d6935
Merge branch 'morten/fix-hook' 2021-04-14 21:51:41 +02:00
Morten Linderud ad1dc957cb
Merge branch 'morten/cmdline' 2021-04-14 21:51:33 +02:00
Morten Linderud 8405a2a407
Merge branch 'morten/fix-permission' 2021-04-14 21:51:23 +02:00
Morten Linderud 9100e231ea
Merge branch 'morten/immutable' 2021-04-14 21:51:10 +02:00
Morten Linderud c50750ee7a
sbctl.hook: Renamed to be ordered last, added more paths
This should cover systemd and fwupd alike

Fixes #51

Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-04-14 21:38:08 +02:00
Morten Linderud 36a1849942
sbctl/bundle: Change default cmdline to /etc/kernel/cmdline
Should probably try include some documentation to this, but this changes
the default from /proc/cmdline to /etc/kernel/cmdline.

This is partially a standard and a bit more flexible for everyday use
for most people.

https://www.freedesktop.org/software/systemd/man/kernel-install.html

Fixes #39

Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-04-05 16:24:19 +02:00
Morten Linderud d69b59bb01
sbctl: Check for persmission denied. Use errors package
We can always stat files, but it's enough to figure out if we can
actually check the signature. Instead we try to open the file.

This patch also moves us to the new errors package

    $ sbctl verify
    ==> Verifying file database and EFI images in /efi...
      -> WARNING: /boot/EFI/BOOT/BOOTX64.EFI is not signed
      -> WARNING: /boot/EFI/arch/fwupdx64.efi is not signed
      -> WARNING: /boot/EFI/systemd/systemd-bootx64.efi is not signed
      -> ERROR: /tmp/vmlinuz-linux does not exist
      -> ERROR: /tmp/vmlinuz-linuz-test permission denied. Can't read file

Fixes #46

Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-04-03 13:09:08 +02:00
Morten Linderud 99efd2a5e3
keys: sbkeysync can have "Permissiond denide" errors
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-04-03 12:30:52 +02:00
Morten Linderud 2572b38a8e
sbctl: Check for immutable files before sbkeysync
This allows us to give a sensible error for `enroll-keys` if the files
are set as immutable.

    $ sbctl enroll-keys
    ==> ERROR: File is immutable: /sys/firmware/efi/efivars/PK-8be4df61-93ca-11d2-aa0d-00e098032b8c
    ==> ERROR: File is immutable: /sys/firmware/efi/efivars/KEK-8be4df61-93ca-11d2-aa0d-00e098032b8c
    ==> ERROR: File is immutable: /sys/firmware/efi/efivars/db-d719b2cb-3d3a-4596-a3bc-dad00e67656f
    ==> ERROR: You need to chattr -i files in efivarfs

Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-04-03 12:27:44 +02:00
Morten Linderud a426eeb0c6
cmd/sbctl: Typo in err
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-03-12 23:01:15 +01:00
Morten Linderud 17fc0e5ff4
cmd/sbctl: proper exit if we fail creating bundle
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-03-06 19:53:13 +01:00
Morten Linderud 7cdab1f018
sbctl: Microcode won't always be passed
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-03-06 19:49:51 +01:00
Morten Linderud e40683e344
sbctl: Added missing format argument
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-03-06 19:49:35 +01:00
Morten Linderud 611f2818c6
Makefile: Fixup make before release
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-02-28 14:08:36 +01:00
Morten Linderud a0cd90a0b0
sbctl: Fixed up some missing error handling
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-02-28 14:06:58 +01:00
Morten Linderud 79d986f987
Merge branch 'pr-31' 2021-02-16 19:54:43 +01:00
Érico Rolim f94f185652 Use ReadOrCreateFile in ReadBundleDatabase.
Using the function also removed code that had hardcoded globals for the
location of some files instead of using the dbpath parameter.

Add error checking around the function where appropriate.

Also fail early when creating a new bundle if it isn't possible to
access the bundle database.

Signed-off-by: Érico Rolim <erico.erc@gmail.com>
2021-01-11 00:51:32 -03:00
Érico Rolim cc55d6e443 Add ReadOrCreateFile utility function.
This function will try to read a file into a byte buffer, and, if the
file doesn't exist, create its containing directory and the file itself.
If any of those actions fail due to permissions, the function will print
a warning about running the tool as root.

Reading from the file and bundle databases works like this, so the error
checking should be implemented in a single place.

Also, use the new function in ReadFileDatabase().

Signed-off-by: Érico Rolim <erico.erc@gmail.com>
2021-01-11 00:49:45 -03:00
Érico Rolim 92bb91172d Improve error propagation and permission checking.
- Introduces dependency on sys/unix for unix.Access. This is necessary
only in keys.go, since we run 'sbsign' as a command and can't check if
it failed due to permissions.

- Allows removing special casing in main.go for commands that don't
require root permissions.

- ReadFileDatabase() can now return errors due to the multiple ways in
which it can fail; it also warns the user about possibly requiring root.

- ReadFileDatabase() was using the global DBPath instead of its dbpath
parameter in multiple places. This has been fixed.

- VerifyESP() can now run without root.

- SignFile() checks if it can read the DB key before running sbsign.

Signed-off-by: Érico Rolim <erico.erc@gmail.com>
2021-01-11 00:49:45 -03:00
Érico Rolim 0a7c6e8bc5 Fix potential seg fault in ChecksumFile.
If ReadFile errors out, the error would only be checked after the
function attempts to read the buffer into the hasher. This commit fixes
that, checking the error as soon as possible.

Signed-off-by: Érico Rolim <erico.erc@gmail.com>
2021-01-11 00:43:21 -03:00
Érico Rolim 017b0c1dea Use err1 instead of err as a logger.
This allows err to be used anywhere as the error variable, instead of
having to use "e", for example. This commit also fixes a bug where the
PrintGenerateError() calls in CombineFiles() were using "err" as the
argument for error, when it should have been "e" - since "err" was the
logger and could be used in that way, the compiler didn't complain.

Signed-off-by: Érico Rolim <erico.erc@gmail.com>
2021-01-11 00:37:17 -03:00
Morten Linderud f92102ae2a
Moved from goefi to go-uefi
Signed-off-by: Morten Linderud <morten@linderud.pw>
2021-01-05 22:33:17 +01:00
Morten Linderud 9e9c3a1620
Add support channel
Signed-off-by: Morten Linderud <morten@linderud.pw>
2020-12-30 12:19:17 +01:00
physkets af64bdc245
Document the generate option of sign-all 2020-11-10 15:32:18 +00:00
Morten Linderud dfa6fb1baa
Merge branch 'pr-36' 2020-11-08 19:09:41 +01:00
Morten Linderud b09199482f
README: Fix build badge
Signed-off-by: Morten Linderud <morten@linderud.pw>
2020-11-08 19:09:12 +01:00
Érico Rolim 464b3c2f71 Fix lint step in CI.
Set GOBIN during `make deps` so staticcheck is installed directly in a
directory already in PATH.
2020-11-08 15:06:55 -03:00
45 changed files with 2446 additions and 911 deletions

View File

@ -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

2
.gitignore vendored
View File

@ -3,3 +3,5 @@ releases/*
sbctl
!sbctl/
docs/*.8
rootfs*
bzImage

View File

@ -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
View File

@ -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
```

View File

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

50
chattr.go Normal file
View File

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

110
cmd/sbctl/bundle.go Normal file
View File

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

51
cmd/sbctl/completions.go Normal file
View File

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

42
cmd/sbctl/create-keys.go Normal file
View File

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

58
cmd/sbctl/enroll-keys.go Normal file
View File

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

View File

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

79
cmd/sbctl/list-bundles.go Normal file
View File

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

63
cmd/sbctl/list-files.go Normal file
View File

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

372
cmd/sbctl/main.go Executable file → Normal file
View File

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

View File

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

40
cmd/sbctl/remove-files.go Normal file
View File

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

68
cmd/sbctl/sign-all.go Normal file
View File

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

64
cmd/sbctl/sign.go Normal file
View File

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

89
cmd/sbctl/status.go Normal file
View File

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

90
cmd/sbctl/verify.go Normal file
View File

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

View File

@ -1 +0,0 @@
package sbctl

View File

@ -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

View File

@ -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"
}

View File

@ -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...

View File

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

View File

@ -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
View File

@ -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
View File

@ -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=

44
guid.go Normal file
View File

@ -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
View File

@ -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
View File

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

118
logging/logging.go Normal file
View File

@ -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
View File

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

5
tests/README.md Normal file
View File

@ -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`

37
tests/integration_test.go Normal file
View File

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

View File

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

View File

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

26
tests/kernel/mkimage.sh Normal file
View File

@ -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

24
tests/make_image.sh Executable file
View File

@ -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

BIN
tests/ovmf/OVMF_VARS.fd Normal file

Binary file not shown.

0
tests/shared/test Normal file
View File

54
tests/utils/certs.go Normal file
View File

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

69
tests/utils/config.go Normal file
View File

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

155
tests/utils/utils.go Normal file
View File

@ -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
View File

@ -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]
}