Add Secure Boot Support (#12692)

- Why I did it
Add Secure Boot support to SONiC OS.
Secure Boot (SB) is a verification mechanism for ensuring that code launched by a computer's UEFI firmware is trusted. It is designed to protect a system against malicious code being loaded and executed early in the boot process before the operating system has been loaded.

- How I did it
Added a signing process to sign the following components:
shim, grub, Linux kernel, and kernel modules when doing the build, and when feature is enabled in build time according to the HLD explanations (the feature is disabled by default).

- How to verify it
There are self-verifications of each boot component when building the image, in addition, there is an existing end-to-end test in sonic-mgmt repo that checks that the boot succeeds when loading a secure system (details below).

How to build a sonic image with secure boot feature: (more description in HLD)

Required to use the following build flags from rules/config:
SECURE_UPGRADE_MODE="dev"
SECURE_UPGRADE_DEV_SIGNING_KEY="/path/to/private/key.pem"
SECURE_UPGRADE_DEV_SIGNING_CERT="/path/to/cert/key.pem"
After setting those flags should build the sonic-buildimage.
Before installing the image, should prepared the setup (switch device) with the follow:
check that the device support UEFI
stored pub keys in UEFI DB

enabled Secure Boot flag in UEFI
How to run a test that verify the Secure Boot flow:
The existing test "test_upgrade_path" under "sonic-mgmt/tests/upgrade_path/test_upgrade_path", is enough to validate proper boot
You need to specify the following arguments:
Base_image_list your_secure_image
Taget_image_list your_second_secure_image
Upgrade_type cold
And run the test, basically the test will install the base image given in the parameter and then upgrade to target image by doing cold reboot and validates all the services are up and working correctly
This commit is contained in:
davidpil2002
2023-03-14 14:55:22 +02:00
committed by GitHub
parent 1cd67444e4
commit 8098bc4bf5
11 changed files with 618 additions and 6 deletions

63
scripts/efi-sign.sh Executable file
View File

@@ -0,0 +1,63 @@
#!/bin/sh
set -e
#
# Sign efi file with secret key and certificate
# - shim
# - grub
# - vmlinuz
#
print_usage() {
cat <<EOF
$0: Usage
$0 -p <PRIVATE_KEY_PEM> -c <CERT_PEM> -e <EFI_FILE> -s <EFI_FILE_SIGNED>
Usage example: efi-sign.sh -p priv-key.pem -c pub-key.pem -e shimx64.efi -s shimx64-signed.efi
EOF
}
while getopts 'p:c:e:s:hv' flag; do
case "${flag}" in
p) PRIVATE_KEY_PEM="${OPTARG}" ;;
c) CERT_PEM="${OPTARG}" ;;
e) EFI_FILE="${OPTARG}" ;;
s) EFI_FILE_SIGNED="${OPTARG}" ;;
v) VERBOSE='true' ;;
h) print_usage
exit 1 ;;
esac
done
if [ $OPTIND -eq 1 ]; then echo "no options were pass"; print_usage; exit 1 ;fi
[ -f "$PRIVATE_KEY_PEM" ] || {
echo "Error: PRIVATE_KEY_PEM file does not exist: $PRIVATE_KEY_PEM"
print_usage
exit 1
}
[ -f "$CERT_PEM" ] || {
echo "Error: CERT_PEM file does not exist: $CERT_PEM"
print_usage
exit 1
}
[ -f "$EFI_FILE" ] || {
echo "Error: File for signing does not exist: $EFI_FILE"
print_usage
exit 1
}
if [ -z ${EFI_FILE_SIGNED} ]; then
echo "ERROR: no arg named <EFI_FILE_SIGNED> supplied"
print_usage
exit 1
fi
echo "$0 signing $EFI_FILE with ${PRIVATE_KEY_PEM}, ${CERT_PEM} to create $EFI_FILE_SIGNED"
sbsign --key ${PRIVATE_KEY_PEM} --cert ${CERT_PEM} \
--output ${EFI_FILE_SIGNED} ${EFI_FILE} || {
echo "EFI sign error"
exit 1
}

View File

@@ -0,0 +1,96 @@
#!/bin/bash
# This Script is verifying the efi file signature by using sbverify.
# In addition, is verifying that kernel modules a directory contained a signature.
# Note: Kernel Module verification is not checking that the signature is correct, but its checking that the Kernel Modules have one.
EFI_FILE=''
KERNEL_MODULES_DIR=''
CERT_PEM=''
VERBOSE='false'
print_usage() {
cat <<EOF
$0: Usage
$0 -e <EFI_FILE/EFI_DIR> -c <CERT_PEM> -k <KERNEL_MODULES_DIR>
Run Example: secure_boot_signature_verification.sh -e shimx64.efi -c pub-key.pem -k fsroot-mellanox
Run Example: secure_boot_signature_verification.sh -e /boot/efi_dir -c pub-key.pem -k fsroot-mellanox
EOF
}
verify_efi(){
cert_pem=$1
efi_file=$2
echo "sbverify --cert $cert_pem $efi_file"
sbverify --cert $cert_pem $efi_file || {
echo "sbverify error with $efi_file"
exit 1
}
echo "$efi_file signed OK."
}
while getopts 'e:k:c:hv' flag; do
case "${flag}" in
e) EFI_FILE="${OPTARG}" ;;
k) KERNEL_MODULES_DIR="${OPTARG}" ;;
c) CERT_PEM="${OPTARG}" ;;
v) VERBOSE='true' ;;
h) print_usage
exit 1 ;;
esac
done
if [ $OPTIND -eq 1 ]; then echo "no options were pass"; print_usage; exit 1 ;fi
if [ -d "$EFI_FILE" ];then
[ -f "$CERT_PEM" ] || {
echo "Error: option '-c' incorrect, file: certificate=$CERT_PEM does not exist"
print_usage
exit 1
}
# find all efi files.
efi_file_list=$(sudo find ${EFI_FILE} -name "*.efi")
for efi_file in $efi_file_list
do
echo "verifying efi_file named: ${efi_file} .."
verify_efi $CERT_PEM ${efi_file}
done
echo "$0: All EFI files SIGNED OK."
fi
if [ -f "$EFI_FILE" ]; then
[ -f "$CERT_PEM" ] || {
echo "Error: option '-c' incorrect, file: certificate=$CERT_PEM does not exist"
print_usage
exit 1
}
verify_efi $CERT_PEM $EFI_FILE
fi
if [ -d "$KERNEL_MODULES_DIR" ]; then
# Condition checking that all the kernel modules in the KERNEL_MODULES_DIR contain a signature.
# find all the kernel modules.
modules_list=$(sudo find ${KERNEL_MODULES_DIR} -name "*.ko")
# Do sign for each found module
kernel_modules_cnt=0
for mod in $modules_list
do
# check Kernel module is signed.
if ! grep -q "~Module signature appended~" "${mod}"; then
echo "Error: Kernel module=${mod} have no signature appened."
exit 1
fi
if [ $VERBOSE = 'true' ]; then
echo "kernel module named=${mod} have signature appended."
fi
kernel_modules_cnt=$((kernel_modules_cnt+1))
done
echo "Num of kernel modules signed: kernel_modules_cnt=$kernel_modules_cnt"
echo "$0: All Kernel Modules SIGNED OK."
fi

123
scripts/signing_kernel_modules.sh Executable file
View File

@@ -0,0 +1,123 @@
#!/bin/bash
# This script is signing kernel modules by using sign-file tool
usage() {
cat <<EOF
$0: # Display Help
$0 -l <LINUX_KERNEL_VERSION> -c <PEM_CERT> -p <PEM_PRIVATE_KEY> -s <LOCAL_SIGN_FILE> -e <LOCAL_EXTRACT_CERT> -k <KERNEL_MODULES_DIR>
Sign kernel modules in <KERNEL_MODULES_DIR> using private & public keys.
Parameters description:
LINUX_KERNEL_VERSION
PEM_CERT public key (pem format)
PEM_PRIVATE_KEY private key (pem format)
LOCAL_SIGN_FILE path of the sign-file tool for signing Kernel Modules, if the value is empty it will used the sign-file installed in /usr/lib/linux-kbuild-<version>/scripts
LOCAL_EXTRACT_CERT path of the extract-cert tool for Extract X.509 certificate, if the value is empty it will used the extract-cert installed in /usr/lib/linux-kbuild-<version>/scripts
KERNEL_MODULES_DIR root directory of all the kernel modules to be sign by the script, if the value empty it will use the call script location as root.
Runs examples:
1. ./scripts/signing_kernel_modules.sh -l 5.10.0-8-2 -c cert.pem -p priv-key.pem
2. ./scripts/signing_kernel_modules.sh -l 5.10.0-8-2 -c cert.pem -p priv-key.pem -k fsroot-mellanox -e /usr/lib/linux-kbuild-5.10/scripts/extract-cert -s /usr/lib/linux-kbuild-5.10/scripts/sign-file
EOF
}
while getopts 'l:c:p:k:s:e:hv' flag; do
case "${flag}" in
l) LINUX_KERNEL_VERSION="${OPTARG}" ;;
c) PEM_CERT="${OPTARG}" ;;
p) PEM_PRIVATE_KEY="${OPTARG}" ;;
k) KERNEL_MODULES_DIR="${OPTARG}" ;;
s) LOCAL_SIGN_FILE="${OPTARG}" ;;
e) LOCAL_EXTRACT_CERT="${OPTARG}" ;;
v) VERBOSE='true' ;;
h) usage
exit 1 ;;
esac
done
if [ $OPTIND -eq 1 ]; then echo "no options were pass"; usage; exit 1 ;fi
if [ -z ${LINUX_KERNEL_VERSION} ]; then
echo "ERROR: LINUX_KERNEL_VERSION arg1 is empty"
usage
exit 1
fi
if [ ! -f ${PEM_CERT} ]; then
echo "ERROR: arg2 PEM_CERT=${PEM_CERT} file does not exist"
usage
exit 1
fi
if [ ! -f ${PEM_PRIVATE_KEY} ]; then
echo "ERROR: arg3 PEM_PRIVATE_KEY=${PEM_PRIVATE_KEY} file does not exist"
usage
exit 1
fi
kbuild_ver_major="$(cut -d '.' -f 1 <<< "$LINUX_KERNEL_VERSION")"."$(cut -d '.' -f 2 <<< "$LINUX_KERNEL_VERSION")"
if [ -z ${LOCAL_SIGN_FILE} ]; then
LOCAL_SIGN_FILE="/usr/lib/linux-kbuild-${kbuild_ver_major}/scripts/sign-file"
fi
if [ ! -f ${LOCAL_SIGN_FILE} ]; then
echo "ERROR: LOCAL_SIGN_FILE=${LOCAL_SIGN_FILE} file does not exist"
usage
exit 1
fi
if [ -z ${LOCAL_EXTRACT_CERT} ]; then
LOCAL_EXTRACT_CERT="/usr/lib/linux-kbuild-${kbuild_ver_major}/scripts/extract-cert"
fi
if [ ! -f ${LOCAL_EXTRACT_CERT} ]; then
echo "ERROR: LOCAL_EXTRACT_CERT=${LOCAL_EXTRACT_CERT} file does not exist"
usage
exit 1
fi
if [ ! -d "$KERNEL_MODULES_DIR" ]; then
# If the user do not provide a KERNEL_MODULES_DIR, the script is going to search in the script call path for Kernel modules.
KERNEL_MODULES_DIR="./"
echo "KERNEL_MODULES_DIR set to default path: $KERNEL_MODULES_DIR"
fi
# find all the kernel modules.
modules_list=$(find ${KERNEL_MODULES_DIR} -name "*.ko")
dev_certs_tmp_folder="/tmp/dev_kmod_sign"
# clean env
if [ -d ${dev_certs_tmp_folder} ]; then
rm -r ${dev_certs_tmp_folder}
fi
mkdir -p ${dev_certs_tmp_folder}
local_sign_key="${dev_certs_tmp_folder}/$(basename $PEM_PRIVATE_KEY)"
local_sign_cert="${dev_certs_tmp_folder}/$(basename $PEM_CERT)"
# Combine cert for module signing
echo "keys concat: cat ${PEM_PRIVATE_KEY} ${PEM_CERT} > ${local_sign_key}"
cat ${PEM_PRIVATE_KEY} ${PEM_CERT} > ${local_sign_key}
# Extract x509 cert in corect format
echo "create x509 cert: ${LOCAL_EXTRACT_CERT} ${local_sign_key} ${local_sign_cert}"
${LOCAL_EXTRACT_CERT} ${local_sign_key} ${local_sign_cert}
# Do sign for each found module
kernel_modules_cnt=0
for mod in $modules_list
do
echo "signing module named: ${mod} .."
echo "${LOCAL_SIGN_FILE} sha512 ${local_sign_key} ${local_sign_cert} ${mod}"
kernel_modules_cnt=$((kernel_modules_cnt+1))
${LOCAL_SIGN_FILE} sha512 ${local_sign_key} ${local_sign_cert} ${mod}
# check Kernel module is signed.
if ! grep -q "~Module signature appended~" "${mod}"; then
echo "Error: Kernel module=${mod} have no signature appened."
exit 1
fi
done
echo "Num of kernel modules signed: kernel_modules_cnt=$kernel_modules_cnt"
echo "$0: All Kernel Modules SIGNED OK."

View File

@@ -0,0 +1,121 @@
#!/bin/bash
# This script is signing boot components: shim, mmx, grub, kernel and kernel modules in development env.
## Enable debug output for script & exit code when failing occurs
set -x -e
print_usage() {
cat <<EOF
$0: Usage
$0 -r <FS_ROOT> -l <LINUX_KERNEL_VERSION> -c <PEM_CERT> -p <PEM_PRIV_KEY>
EOF
}
clean_file() {
if [ -f $1 ]; then
echo "clean old file named: $1"
echo "rm -f $1"
rm -f $1
fi
}
while getopts 'a:r:l:c:p:hv' flag; do
case "${flag}" in
a) CONFIGURED_ARCH="${OPTARG}" ;;
r) FS_ROOT="${OPTARG}" ;;
l) LINUX_KERNEL_VERSION="${OPTARG}" ;;
c) PEM_CERT="${OPTARG}" ;;
p) PEM_PRIV_KEY="${OPTARG}" ;;
v) VERBOSE='true' ;;
h) print_usage
exit 1 ;;
esac
done
if [ $OPTIND -eq 1 ]; then echo "no options were pass"; print_usage; exit 1 ;fi
echo "$0 signing & verifying EFI files and Kernel Modules start ..."
if [ -z ${CONFIGURED_ARCH} ]; then
echo "ERROR: CONFIGURED_ARCH=${CONFIGURED_ARCH} is empty"
print_usage
exit 1
fi
if [ -z ${FS_ROOT} ]; then
echo "ERROR: FS_ROOT=${FS_ROOT} is empty"
print_usage
exit 1
fi
if [ -z ${LINUX_KERNEL_VERSION} ]; then
echo "ERROR: LINUX_KERNEL_VERSION=${LINUX_KERNEL_VERSION} is empty"
print_usage
exit 1
fi
if [ ! -f "${PEM_CERT}" ]; then
echo "ERROR: PEM_CERT=${PEM_CERT} file does not exist"
print_usage
exit 1
fi
if [ ! -f "${PEM_PRIV_KEY}" ]; then
echo "ERROR: PEM_PRIV_KEY=${PEM_PRIV_KEY} file does not exist"
print_usage
exit 1
fi
# efi-sign.sh is used to sign: shim, mmx, grub, and kernel (vmlinuz)
EFI_SIGNING=scripts/efi-sign.sh
# ######################################
# Signing EFI files: mm, shim, grub
# #####################################
efi_file_list=$(sudo find ${KERNEL_MODULES_DIR} -name "*.efi")
for efi in $efi_file_list
do
# grep filename from full path
efi_filename=$(echo $efi | grep -o '[^/]*$')
if echo $efi_filename | grep -e "shim" -e "grub" -e "mm"; then
clean_file ${efi}-signed
echo "signing efi file - full path: ${efi} filename: ${efi_filename}"
echo "sudo ${EFI_SIGNING} -p $PEM_PRIV_KEY -c $PEM_CERT -e ${efi} -s ${efi}-signed"
${EFI_SIGNING} -p $PEM_PRIV_KEY -c $PEM_CERT -e ${efi} -s ${efi}-signed
# cp shim & mmx signed files to boot directory in the fs.
cp ${efi}-signed $FS_ROOT/boot/${efi_filename}
# verifying signature of mm & shim efi files.
./scripts/secure_boot_signature_verification.sh -c $PEM_CERT -e $FS_ROOT/boot/${efi_filename}
fi
done
######################
## vmlinuz signing
######################
CURR_VMLINUZ=$FS_ROOT/boot/vmlinuz-${LINUX_KERNEL_VERSION}-${CONFIGURED_ARCH}
# clean old files
clean_file ${CURR_VMLINUZ}-signed
echo "signing ${CURR_VMLINUZ} .."
${EFI_SIGNING} -p $PEM_PRIV_KEY -c $PEM_CERT -e ${CURR_VMLINUZ} -s ${CURR_VMLINUZ}-signed
# rename signed vmlinuz with the name vmlinuz without signed suffix
mv ${CURR_VMLINUZ}-signed ${CURR_VMLINUZ}
./scripts/secure_boot_signature_verification.sh -c $PEM_CERT -e ${CURR_VMLINUZ}
#########################
# Kernel Modules signing
#########################
./scripts/signing_kernel_modules.sh -l $LINUX_KERNEL_VERSION -c ${PEM_CERT} -p ${PEM_PRIV_KEY} -k ${FS_ROOT}
echo "$0 signing & verifying EFI files and Kernel Modules DONE"