summaryrefslogtreecommitdiff
blob: 4459b0341845bc124ea4db9539e3f243bed7a4cd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# Copyright 1999-2024 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

# @ECLASS: secureboot.eclass
# @MAINTAINER:
# Andrew Ammerlaan <andrewammerlaan@gentoo.org>
# @AUTHOR:
# Author: Andrew Ammerlaan <andrewammerlaan@gentoo.org>
# @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[0]} "${@}"
	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[0]} "${@}"
	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: <input file> [<output file>]
# @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[0]} "${@}"
	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[0]} "${@}"
	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