# Copyright 1999-2024 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 # @ECLASS: secureboot.eclass # @MAINTAINER: # Nowa Ammerlaan # @AUTHOR: # Author: Nowa Ammerlaan # @SUPPORTED_EAPIS: 7 8 # @BLURB: A small eclass to sign efi files for Secure Boot # @DESCRIPTION: # Eclass for packages that install .efi files. A use flag and two user # variables allow signing these .efi files for use on systems with Secure Boot # enabled. # # Signing the files during emerge ensures that any tooling that actually # installs the bootloaders and kernels to ESP always uses a signed version. # This prevents Secure Boot from accidentally breaking when upgrading the # kernel or the bootloader. # # Example use # @CODE # src_install() { # default # secureboot_sign_efi_file in.efi out.efi.signed # } # @CODE # # Or # @CODE # src_install() { # default # secureboot_auto_sign # } # @CODE # # Some tools will automatically detect and use EFI executables with the .signed # suffix. For tools that do not do this the --in-place argument for # secureboot_auto_sign can be used to ensure that the signed version is used. case ${EAPI} in 7|8) ;; *) die "${ECLASS}: EAPI ${EAPI:-0} not supported" ;; esac IUSE="secureboot" BDEPEND=" secureboot? ( app-crypt/sbsigntools dev-libs/openssl ) " # @ECLASS_VARIABLE: SECUREBOOT_SIGN_KEY # @USER_VARIABLE # @DEFAULT_UNSET # @DESCRIPTION: # Used with USE=secureboot. Should be set to the path of the private # key in PEM format to use, or a PKCS#11 URI. # @ECLASS_VARIABLE: SECUREBOOT_SIGN_CERT # @USER_VARIABLE # @DEFAULT_UNSET # @DESCRIPTION: # Used with USE=secureboot. Should be set to the path of the public # key certificate in PEM format to use. if [[ -z ${_SECUREBOOT_ECLASS} ]]; then _SECUREBOOT_ECLASS=1 # @FUNCTION: _secureboot_die_if_unset # @INTERNAL # @DESCRIPTION: # If USE=secureboot is enabled die if the required user variables are unset # and die if the keys can't be found. _secureboot_die_if_unset() { debug-print-function ${FUNCNAME} "$@" use secureboot || return if [[ -z ${SECUREBOOT_SIGN_KEY} || -z ${SECUREBOOT_SIGN_CERT} ]]; then die "USE=secureboot enabled but SECUREBOOT_SIGN_KEY and/or SECUREBOOT_SIGN_CERT not set." fi # Sanity check: fail early if key/cert in DER format or does not exist local openssl_args=( -inform PEM -in "${SECUREBOOT_SIGN_CERT}" -noout -nocert ) if [[ ${SECUREBOOT_SIGN_KEY} == pkcs11:* ]]; then openssl_args+=( -engine pkcs11 -keyform ENGINE -key "${SECUREBOOT_SIGN_KEY}" ) else openssl_args+=( -keyform PEM -key "${SECUREBOOT_SIGN_KEY}" ) fi openssl x509 "${openssl_args[@]}" || die "Secure Boot signing certificate or key not found or not PEM format." } # @FUNCTION: secureboot_pkg_setup # @DESCRIPTION: # Checks if required user variables are set before starting the build secureboot_pkg_setup() { debug-print-function ${FUNCNAME} "$@" use secureboot || return # If we are merging a binary then the files in this binary # are already signed, no need to check the variables. if [[ ${MERGE_TYPE} != binary ]]; then _secureboot_die_if_unset fi } # @FUNCTION: secureboot_sign_efi_file # @USAGE: [] # @DESCRIPTION: # Sign a file using sbsign and the requested key/certificate. # If the file is already signed with our key then the file is skipped. # If no output file is specified the output file will be the same # as the input file, i.e. the file will be overwritten. secureboot_sign_efi_file() { debug-print-function ${FUNCNAME} "$@" use secureboot || return local input_file=${1} local output_file=${2:-${1}} _secureboot_die_if_unset ebegin "Signing ${input_file}" local return=1 if sbverify "${input_file}" --cert "${SECUREBOOT_SIGN_CERT}" &> /dev/null; then ewarn "${input_file} already signed, skipping" return=0 else local args=( "--key=${SECUREBOOT_SIGN_KEY}" "--cert=${SECUREBOOT_SIGN_CERT}" ) if [[ ${SECUREBOOT_SIGN_KEY} == pkcs11:* ]]; then args+=( --engine=pkcs11 ) fi sbsign "${args[@]}" "${input_file}" --output "${output_file}" return=${?} fi eend ${return} || die "Signing ${input_file} failed" } # @FUNCTION: secureboot_auto_sign # @USAGE: [--in-place] # @DESCRIPTION: # Automatically discover and sign efi files in the image directory. # # By default signed files gain the .signed suffix. If the --in-place # argument is given the efi files are replaced with a signed version in place. secureboot_auto_sign() { debug-print-function ${FUNCNAME} "$@" use secureboot || return [[ ${EBUILD_PHASE} == install ]] || die "${FUNCNAME[0]} can only be called in the src_install phase" local -a efi_execs mapfile -td '' efi_execs < <( find "${ED}" -type f \ \( -iname '*.efi' -o -iname '*.efi32' -o -iname '*.efi64' \) \ -print0 || die ) (( ${#efi_execs[@]} )) || die "${FUNCNAME[0]} was called but no efi executables were found" local suffix if [[ ${1} == --in-place ]]; then suffix="" elif [[ -n ${1} ]]; then die "Invalid argument ${1}" else suffix=".signed" fi for efi_exec in "${efi_execs[@]}"; do secureboot_sign_efi_file "${efi_exec}" "${efi_exec}${suffix}" done } fi EXPORT_FUNCTIONS pkg_setup