Skip to content

Commit

Permalink
ShimUtils: Rename ShimToCert as ShimUtils; add new tools:
Browse files Browse the repository at this point in the history
 - shim-make.tool
 - sbat-info.tool
 - unsign-efi-sig-list.tool
and update shim-to-cert.tool
  • Loading branch information
mikebeaton committed Sep 9, 2023
1 parent 2b718a2 commit d1c6a01
Show file tree
Hide file tree
Showing 8 changed files with 704 additions and 65 deletions.
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ OpenCore Changelog
- Fixed hang while unloading NTFS driver
- Added UEFI quirk `ShimRetainProtocol`, allowing OpenCore chained from shim to verify Linux using shim's certificates
- Added `OpenLegacyBoot` driver for supporting legacy OS booting
- Added `shim-make.tool` to download and build rhboot/shim, for Linux SBAT and MOK integration

#### v0.9.4
- Fixed kext blocker `Exclude` strategy for prelinked on 32-bit versions of macOS
Expand Down
14 changes: 8 additions & 6 deletions Docs/Configuration.tex
Original file line number Diff line number Diff line change
Expand Up @@ -6814,9 +6814,11 @@ \subsection{OpenLinuxBoot}\label{uefilinux}
as distro boots successfully.
\end{itemize}

If using OpenLinuxBoot with Secure Boot, users may wish to use the \texttt{shim-to-cert.tool} included in OpenCore
utilities, which can be used to extract the public key needed to boot a distro's kernels directly, as done when using OpenCore
with OpenLinuxBoot, rather than via GRUB shim. For non-GRUB distros, the required public key must be found by user research.
If using OpenLinuxBoot with Secure Boot, users may wish to install
an own-signed, own-build Shim bootloader giving SBAT and MOK integration, as explained in
\href{https://github.com/acidanthera/OpenCorePkg/tree/master/Utilities/ShimUtils}{OpenCore ShimUtils}.
For non-GRUB distros, the required public keys for this process cannot be extracted from
\texttt{shimx64.efi} and so must be found by user research.

\subsubsection{Configuration}

Expand Down Expand Up @@ -9272,15 +9274,15 @@ \subsection{UEFI Secure Boot}\label{uefisecureboot}
the same private key.
\item Sign all third-party operating system (not made by Microsoft or Apple)
bootloaders if needed. For Linux there is an option to install
Microsoft-signed Shim bootloader as explained on e.g.
\href{https://wiki.debian.org/SecureBoot}{Debian Wiki}.
an own-signed, own-build Shim bootloader giving SBAT and MOK integration, as explained in
\href{https://github.com/acidanthera/OpenCorePkg/tree/master/Utilities/ShimUtils}{OpenCore ShimUtils}.
\item Enable UEFI Secure Boot in firmware preferences and install the
certificate with a private key. Details on how to generate a certificate
can be found in various articles, such as \href{https://habr.com/en/post/273497}{this one},
and are out of the scope of this document. If Windows is needed one
will also need to add the
\href{http://go.microsoft.com/fwlink/?LinkID=321192}{Microsoft Windows Production CA 2011}.
To launch option ROMs or to use signed Linux drivers,
To launch option ROMs or to use signed Linux drivers if not using an own-build of shim,
\href{http://go.microsoft.com/fwlink/?LinkId=321194}{Microsoft UEFI Driver Signing CA} will also be needed.
\item Password-protect changing firmware settings to ensure that UEFI Secure Boot
cannot be disabled without the user's knowledge.
Expand Down
101 changes: 101 additions & 0 deletions Utilities/ShimUtils/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
## OpenCore + OpenLinuxBoot + Secure Boot

If you want to use OpenCore + OpenLinuxBoot + Secure Boot it is possible to self-sign
everything, including any new Linux kernels after updates. This is possible since most
standard distros leave at least the previous kernel bootable (and OpenLinuxBoot exposes
this, via the Auxiliary menu), so you can boot into the old kernel, then own-sign the
new kernel.

More convenient may be to trust the signing keys of the specific distros which you
want to boot, which are bundled into the `shimx64.efi` file installed with each distro.
You can extract these with `shim-to-cert.tool` distributed with OpenCore, then install
them in your system Secure Boot `db` variable. Best practice would be to install the deny
list (`vendor.dbx`) from `shimx64.efi`, if any, into your system `dbx` variable, as well.
(Otherwise you are ignoring any recovations which the vendor has made.)

Recently, Shim has added SBAT support, as a more efficient way to revoke unsafe
binaries. Unfortunately, the SBAT enforcement code is part of Shim, it is not
something you can extract and add to your system Secure Boot database.

To work round this, the new recommended way to boot OpenCore + OpenLinuxBoot +
Secure Boot is to make an own-build of Shim. The vendor certificates
and revocation lists extracted from the distro `shimx64.efi` files are combined
and signed by you, into your own build of Shim; in this approach, these vendor
certificates should NOT also be included in the system Secure Boot database,
and should be removed if you added them previously. Including them in both places
will still boot under Secure Boot, but will effectively disable SBAT revocation. (If you are
own-signing everything, including Linux kernels after updates, this will still
work as before and the below is not needed. Equally, if you are not using
Secure Boot the below is not needed.)

The advantages of using an own-build of Shim are:
- No need to sign every kernel after updates (same as previous method)
- Linux SBAT integration (new)
- Linux MOK integration (new)
- No need to include the Windows intermediate CA - you are trusting whichever distro
keys you choose to include in your own Shim, directly (new)

Disadvantages are:
- Need to update when distro keys or distro recovation lists within Shim are updated
(same as previous method)
- Need to udpate when Shim SBAT level is updated (new)

### Method
`Utilities/ShimUtils` includes a script `shim-make.tool` which will download the
current Shim source and build it for you, on macOS (using Ubuntu multipass) or on
Linux (Ubuntu and Fedora supported, others may work).

- Extract `vendor.db` and `vendor.dbx` files from the `shimx64.efi` file of each distro
which you want to load (using `shim-to-cert.tool`)
- Concatentate these (e.g. `cat fedora/vendor.db ubuntu/vendor.db > combined/vendor.db`
and `cat fedora/vendor.dbx ubuntu/vendor.dbx > combined/vendor.dbx`)
- Do not concatenate `.der` files directly, it will not work
- If you have a single distro with a single `.der` file, you can use `VENDOR_CERT_FILE`
instead of `VENDOR_DB_FILE` in the `make` options below; otherwise, you will need to use
`cert-to-efi-sig-list` from `efitools` to convert the `.der` file to a sig list - this
is done automatically by `shim-to-cert.tool` when `efitools` are available (in
Linux; or from within Ubuntu multipass on macOS, e.g. `multipass shell oc-shim`)
- Build a version of Shim which includes these concatenated signature lists (and
launches OpenCore.efi directly):
- `./shim-make.tool setup`
- `./shim-make.tool clean` (only needed if remaking after the initial make)
- `./shim-make.tool make VENDOR_DB_FILE=combined/vendor.db VENDOR_DBX_FILE=combined/vendor.dbx`
- Copy the relevant files (`shimx64.efi` and `mmx64.efi` as well as `BOOTX64.CSV`) to your mounted ESP volume, e.g.:
- `./shim-make.tool install /Volumes/EFI` (macOS)
- `sudo ./shim-make.tool install /boot/efi` (Linux)
- Sign the newly built `shimx64.efi` and `mmx64.efi` with your own ISK (see e.g.
https://habr.com/en/articles/273497/ - Google translate is your friend)
- If you do not copy and sign `mmx64.efi` as well as `shimx64.efi`, your system will hang if any MOK operations are attempted
- `BOOTX64.CSV` is not required and is for information only

As before you need to sign `OpenCore.efi` and any drivers it loads with your ISK.
You now also need to add an empty SBAT section to `OpenCore.efi` before signing it.

> An empty SBAT section means: 'I'm not part of the system which allocates SBAT names
and signs them into boot files, and I don't want this boot file to be revoked by any
future SBAT revocations'. Of course, you can still revoke your own-signed boot files
by rotating your own signing keys.

As noted [here](https://github.com/fwupd/fwupd/issues/2910) and
[here](https://github.com/rhboot/shim/issues/376),
the [documented](https://github.com/rhboot/shim/blob/main/SBAT.md) method for adding an
SBAT section to an already-linked `.efi` file does not work correctly (GNU `objcopy`
corrupts the executable). This
[third party python script](https://github.com/rhboot/shim/issues/376#issuecomment-1628004034)
does work. A suitable command is:

`pe-add-sections.py -s .sbat <(echo -n) -z .sbat -i OpenCore.efi -o OpenCore_empty_sbat.efi`

This file then needs to be signed and copied back into place, e.g.:

`sbsign --key {path-to}/ISK.key --cert {path-to}/ISK.pem OpenCore_empty_sbat.efi --output OpenCore.efi`

Finally, in order for OpenCore integration with Shim to work correctly
`UEFI/Quirks/ShimRetainProtocol` must be enabled in `config.plist`, and
`LauncherPath` should be set to `\EFI\OC\shimx64.efi`.

> Using Ubuntu multipass, it is now possible to operate entirely within macOS for signing,
key generation, etc. Note that the `~/shim_root` directory is already shared between
macOS and the `oc-shim` multipass VM (under its macOS path, e.g. `/Users/username/shim_root`),
and other macOS folders and volumes can be mounted if you wish, e.g.
`multipass mount /Volumes/EFI oc-shim:/Volumes/EFI`.
130 changes: 130 additions & 0 deletions Utilities/ShimUtils/sbat-info.tool
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#!/bin/bash

# sbat-info.tool - Dump some useful information about own-build or standard shimx64.efi.
#
# Copyright (c) 2023, Michael Beaton. All rights reserved.<BR>
# SPDX-License-Identifier: BSD-3-Clause
#

LIGHT_GREEN='\033[1;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color

# Note: binutils can be placed last on path in macOS, to provide objcopy but avoid overwriting native tools.
command -v objcopy >/dev/null 2>&1 || { echo >&2 "objcopy not found - please install binutils package."; exit 1; }

usage () {
echo "Usage: $(basename "$0") <efifile>"
echo " Display SBAT information, if present, for any .efi executable."
echo " If the file is a version of Shim, also dislay Shim version info and SBAT enforcement level."
}

has_section () {
objdump -h "$1" | sed -nr 's/^ *[0-9]+ ([^ ]+).*/\1/p' | grep "$2" 1>/dev/null
}

if [ -z "$1" ]; then
usage
exit 1
fi

# https://unix.stackexchange.com/a/84980/340732
op_dir=$(mktemp -d 2>/dev/null || mktemp -d -t 'shim_info') || exit 1

# objcopy and objdump do not like writing to stdout when part of a pipe, so just write to temp files
# Note: these get created as empty files even if the section does not exist
objcopy -O binary -j ".sbat" "$1" "${op_dir}/sbat" || { rm -rf "${op_dir}"; exit 1; }
objcopy -O binary -j ".sbatlevel" "$1" "${op_dir}/sbatlevel" || { rm -rf "${op_dir}"; exit 1; }
objcopy -O binary -j ".data.ident" "$1" "${op_dir}/data.ident" || { rm -rf "${op_dir}"; exit 1; }

if ! has_section "$1" ".data.ident" ; then
IS_SHIM=0
echo -e "${YELLOW}Shim version info not present (probably not Shim)${NC}"
else
# Treat this section's presence as enough for the warning colours below
IS_SHIM=1
FILE_HEADER="$(head -n 1 "${op_dir}/data.ident")"
SHIM_HEADER="UEFI SHIM"
if [ ! "$FILE_HEADER" = "$SHIM_HEADER" ] ; then
echo -e "${RED}!!!Version header found '${FILE_HEADER}' expected '${SHIM_HEADER}'${NC}"
else
echo -e "${YELLOW}Found ${SHIM_HEADER} version header${NC}"
fi

# shellcheck disable=SC2016
VERSION="$(grep -a 'Version' "${op_dir}/data.ident" | sed -n 's/^\$Version: *\([^[:space:]]*\) *\$$/\1/p')"
if [ "$VERSION" = "" ] ; then
echo -e "${RED}Expected version number not present${NC}"
else
echo -e "Version: ${LIGHT_GREEN}${VERSION}${NC} (${VERSION}+, if own build)"
fi

# shellcheck disable=SC2016
COMMIT="$(grep -a 'Commit' "${op_dir}/data.ident" | sed -n 's/^\$Commit: *\([^[:space:]]*\) *\$$/\1/p')"
if [ "$COMMIT" = "" ] ; then
echo -e "${RED}Expected commit id not present${NC}"
else
echo "Commit: ${COMMIT}"
fi
fi

echo
echo -e "${YELLOW}SBAT (when tested by other files or by self):${NC}"

if ! has_section "$1" ".sbat" ; then
echo
echo -e "${RED}No .sbat section${NC}"
else
echo
echo -n -e "$LIGHT_GREEN"
cat "${op_dir}/sbat"
echo -n -e "$NC"
if ! cat "${op_dir}/sbat" | grep -E "[a-z]|[A-Z]" 1>/dev/null ; then
# Red for shim; yellow for other file
if [ "$IS_SHIM" -eq 1 ] ; then
echo -n -e "$RED"
else
echo -n -e "$LIGHT_GREEN"
fi
echo -e "Present but empty .sbat section"
fi
fi

echo
echo -e "${YELLOW}SBAT Level (when testing other files):${NC}"

if ! has_section "$1" ".sbatlevel" ; then
echo
# Yellow for shim (not present in earlier shims with .sbat, ~15.6); green for other file
if [ "$IS_SHIM" -eq 1 ] ; then
echo -n -e "$YELLOW"
else
echo -n -e "$LIGHT_GREEN"
fi
echo -n "No .sbatlevel section"
echo -e "$NC"
else
sbat_level_version=$(dd if="${op_dir}/sbatlevel" ibs=1 skip=0 count=4 2>/dev/null | od -t u4 -An | xargs) || { rm -rf "${op_dir}"; exit 1; }
sbat_level_previous=$(dd if="${op_dir}/sbatlevel" ibs=1 skip=4 count=4 2>/dev/null | od -t u4 -An | xargs) || { rm -rf "${op_dir}"; exit 1; }
sbat_level_latest=$(dd if="${op_dir}/sbatlevel" ibs=1 skip=8 count=4 2>/dev/null | od -t u4 -An | xargs) || { rm -rf "${op_dir}"; exit 1; }

if [ "$sbat_level_version" -ne 0 ] ; then
echo -e "${RED}!!! Unexpected .sbatlevel version $sbat_level_version != 0 !!!${NC}"
fi

echo
echo -e "${YELLOW}Previous (more permissive boot, default):${NC}"
echo -n -e "$LIGHT_GREEN"
dd if="${op_dir}/sbatlevel" ibs=1 skip="$(($sbat_level_previous + 4))" count="$(($sbat_level_latest - $sbat_level_previous))" 2>/dev/null
echo -e "$NC"

echo -e "${YELLOW}Latest (stricter boot, optional):${NC}"
echo -n -e "$LIGHT_GREEN"
dd if="${op_dir}/sbatlevel" ibs=1 skip="$(($sbat_level_latest + 4))" 2>/dev/null
echo -e "$NC"
fi

rm -rf "${op_dir}"

exit 0
Loading

0 comments on commit d1c6a01

Please sign in to comment.