blob: 5c74a319e4f90f94a888b798d8f7ce945062175a [file] [log] [blame]
Andrew Geissler6aa7eec2023-03-03 12:41:14 -06001#
2# Copyright Jan Luebbe <jlu@pengutronix.de>
3#
4# SPDX-License-Identifier: MIT
5#
6
7# This class provides a common workflow to use asymmetric (i.e. RSA) keys to
8# sign artifacts. Usually, the keys are either stored as simple files in the
9# file system or on a HSM (Hardware Security Module). While files are easy to
10# use, it's hard to verify that no copies of the private have been made and
11# only authorized persons are able to use the key. Use of an HSM addresses
12# these risks by only allowing use of the key via an API (often PKCS #11). The
13# standard way of referring to a specific key in an HSM are PKCS #11 URIs (RFC
14# 7512).
15#
16# Many software projects support signing using PKCS #11 keys, but configuring
17# this is very project specific. Furthermore, as physical HSMs are not very
18# widespread, testing code signing in CI is not simple. To solve this at the
19# build system level, this class takes the approach of always using PKCS #11 at
20# the recipe level. For cases where the keys are available as files (i.e. test
21# keys in CI), they are imported into SoftHSM (a HSM emulation library).
22#
23# Recipes access the available keys via a specific role. So, depending on
24# whether we're building during development or for release, a given role can
25# refer to different keys.
26# Each key recipe PROVIDES a virtual package corresponding to the role, allowing
27# the user to select one of multiple keys for a role when needed.
28#
29# For use with a real HSM, a PKCS #11 URI can be set (i.e. in local.conf) to
30# override the SoftHSM key with the real one:
31#
32# SIGNING_PKCS11_URI[fit] = "pkcs11:serial=DENK0200554;object=ptx-dev-rauc&pin-value=123456"
33# SIGNING_PKCS11_MODULE[fit] = "/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so"
34#
35# Examples for defining roles and importing keys:
36#
37# meta-code-signing/recipes-security/signing-keys/dummy-rsa-key-native.bb
38# meta-code-signing-demo/recipes-security/ptx-dev-keys/ptx-dev-keys-native_git.bb
39#
40# Examples for using keys for signing:
41#
42# meta-code-signing-demo/recipes-security/fit-image/linux-fit-image.bb
43# meta-code-signing-demo/recipes-core/bundles/update-bundle.bb
44#
45# Examples for using keys for authentication:
46#
47# meta-code-signing-demo/recipes-security/fit-image/barebox_%.bbappend
48# meta-code-signing-demo/recipes-core/rauc/rauc_%.bbappend
49#
50# Examples for using keys for both signing and authentication:
51#
52# meta-code-signing-demo/recipes-kernel/linux/linux-yocto_6.1.bbappend
53
54SIGNING_PKCS11_URI ?= ""
55SIGNING_PKCS11_MODULE ?= ""
56
57DEPENDS += "softhsm-native libp11-native opensc-native openssl-native"
58
59def signing_class_prepare(d):
60 import os.path
61
62 def export(role, k, v):
63 k = k % (role, )
64 d.setVar(k, v)
65 d.setVarFlag(k, "export", "1")
66
67 roles = set()
68 roles |= (d.getVarFlags("SIGNING_PKCS11_URI") or {}).keys()
69 roles |= (d.getVarFlags("SIGNING_PKCS11_MODULE") or {}).keys()
70 for role in roles:
71 if not set(role).issubset("abcdefghijklmnopqrstuvwxyz0123456789_"):
72 bb.fatal("key role name '%s' must consist of only [a-z0-9_]" % (role,))
73
74 pkcs11_uri = d.getVarFlag("SIGNING_PKCS11_URI", role) or d.getVar("SIGNING_PKCS11_URI")
75 if not pkcs11_uri.startswith("pkcs11:"):
76 bb.fatal("URI for key role '%s' must start with 'pkcs11:'" % (role,))
77
78 pkcs11_module = d.getVarFlag("SIGNING_PKCS11_MODULE", role) or d.getVar("SIGNING_PKCS11_MODULE")
79 if not os.path.isfile(pkcs11_module):
80 bb.fatal("module path for key role '%s' must be an existing file" % (role,))
81
82 if pkcs11_uri and not pkcs11_module:
83 bb.warn("SIGNING_PKCS11_URI[%s] is set without SIGNING_PKCS11_MODULE[%s]" % (role, role))
84 if pkcs11_module and not pkcs11_uri:
85 bb.warn("SIGNING_PKCS11_MODULE[%s] is set without SIGNING_PKCS11_URI[%s]" % (role, role))
86
87 export(role, "SIGNING_PKCS11_URI_%s_", pkcs11_uri)
88 export(role, "SIGNING_PKCS11_MODULE_%s_", pkcs11_module)
89
90signing_pkcs11_tool() {
91 pkcs11-tool --module "${STAGING_LIBDIR_NATIVE}/softhsm/libsofthsm2.so" --login --pin 1111 $*
92}
93
94signing_import_prepare() {
95 export _SIGNING_ENV_FILE_="${B}/meta-signing.env"
96 rm -f "$_SIGNING_ENV_FILE_"
97
98 export SOFTHSM2_CONF="${B}/softhsm2.conf"
99 export SOFTHSM2_DIR="${B}/softhsm2.tokens"
100 export SOFTHSM2_MOD="${STAGING_LIBDIR_NATIVE}/softhsm/libsofthsm2.so"
101
102 echo "directories.tokendir = $SOFTHSM2_DIR" > "$SOFTHSM2_CONF"
103 echo "objectstore.backend = db" >> "$SOFTHSM2_CONF"
104 rm -rf "$SOFTHSM2_DIR"
105 mkdir -p "$SOFTHSM2_DIR"
106
107 softhsm2-util --module $SOFTHSM2_MOD --init-token --free --label ${PN} --pin 1111 --so-pin 222222
108}
109
110signing_import_define_role() {
111 local role="${1}"
112 case "${1}" in
113 (*[!a-z0-9_]*) false;;
114 (*) true;;
115 esac || bbfatal "invalid role name '${1}', must consist of [a-z0-9_]"
116
117 echo "_SIGNING_PKCS11_URI_${role}_=\"pkcs11:token=${PN};object=$role;pin-value=1111\"" >> $_SIGNING_ENV_FILE_
118 echo "_SIGNING_PKCS11_MODULE_${role}_=\"softhsm\"" >> $_SIGNING_ENV_FILE_
119}
120
121# signing_import_cert_from_der <role> <der>
122#
123# Import a certificate from DER file to a role. To be used
124# with SoftHSM.
125signing_import_cert_from_der() {
126 local role="${1}"
127 local der="${2}"
128
129 signing_pkcs11_tool --type cert --write-object "${der}" --label "${role}"
130}
131
132# signing_import_cert_from_pem <role> <pem>
133#
134# Import a certificate from PEM file to a role. To be used
135# with SoftHSM.
136signing_import_cert_from_pem() {
137 local role="${1}"
138 local pem="${2}"
139
140 openssl x509 \
141 -in "${pem}" -inform pem -outform der |
142 signing_pkcs11_tool --type cert --write-object /proc/self/fd/0 --label "${role}"
143}
144
145# signing_import_pubkey_from_der <role> <pem>
146#
147# Import a public key from DER file to a role. To be used with SoftHSM.
148signing_import_pubkey_from_pem() {
149 local role="${1}"
150 local der="${2}"
151
152 signing_pkcs11_tool --type pubkey --write-object "${der}" --label "${role}"
153}
154
155# signing_import_pubkey_from_pem <role> <pem>
156#
157# Import a public key from PEM file to a role. To be used with SoftHSM.
158signing_import_pubkey_from_pem() {
159 local openssl_keyopt
160 local role="${1}"
161 local pem="${2}"
162
163 if [ -n "${IMPORT_PASS_FILE}" ]; then
164 openssl rsa \
165 -passin "file:${IMPORT_PASS_FILE}" \
166 -in "${pem}" -inform pem -pubout -outform der
167 else
168 openssl rsa \
169 -in "${pem}" -inform pem -pubout -outform der
170 fi |
171 signing_pkcs11_tool --type pubkey --write-object /proc/self/fd/0 --label "${role}"
172}
173
174# signing_import_privkey_from_der <role> <pem>
175#
176# Import a private key from DER file to a role. To be used with SoftHSM.
177signing_import_privkey_from_der() {
178 local role="${1}"
179 local der="${2}"
180 signing_pkcs11_tool --type privkey --write-object "${der}" --label "${role}"
181}
182
183# signing_import_privkey_from_pem <role> <pem>
184#
185# Import a private key from PEM file to a role. To be used with SoftHSM.
186signing_import_privkey_from_pem() {
187 local openssl_keyopt
188 local role="${1}"
189 local pem="${2}"
190
191 if [ -n "${IMPORT_PASS_FILE}" ]; then
192 openssl rsa \
193 -passin "file:${IMPORT_PASS_FILE}" \
194 -in "${pem}" -inform pem -outform der
195 else
196 openssl rsa \
197 -in "${pem}" -inform pem -outform der
198 fi |
199 signing_pkcs11_tool --type privkey --write-object /proc/self/fd/0 --label "${role}"
200}
201
202# signing_import_key_from_pem <role> <pem>
203#
204# Import a private and public key from PEM file to a role. To be used
205# with SoftHSM.
206signing_import_key_from_pem() {
207 local role="${1}"
208 local pem="${2}"
209
210 signing_import_pubkey_from_pem "${role}" "${pem}"
211 signing_import_privkey_from_pem "${role}" "${pem}"
212}
213
214signing_import_finish() {
215 echo "loaded objects:"
216 signing_pkcs11_tool --list-objects
217}
218
219signing_import_install() {
220 install -d ${D}${localstatedir}/lib/softhsm/tokens/${PN}
221 install -m 600 -t ${D}${localstatedir}/lib/softhsm/tokens/${PN} ${B}/softhsm2.tokens/*/*
222 install -d ${D}${localstatedir}/lib/meta-signing.env.d
223 install -m 644 "${B}/meta-signing.env" ${D}${localstatedir}/lib/meta-signing.env.d/${PN}
224}
225
226signing_prepare() {
227 if [ -f ${OPENSSL_CONF} ]; then
228 echo "Using '${OPENSSL_CONF}' for OpenSSL configuration"
229 else
230 echo "Missing 'openssl.cnf' at '${STAGING_ETCDIR_NATIVE}/ssl'"
231 return 1
232 fi
233 if [ -d ${OPENSSL_MODULES} ]; then
234 echo "Using '${OPENSSL_MODULES}' for OpenSSL run-time modules"
235 else
236 echo "Missing OpenSSL module directory at '${OPENSSL_MODULES}'"
237 return 1
238 fi
239 if [ -d ${OPENSSL_ENGINES} ]; then
240 echo "Using '${OPENSSL_ENGINES}' for OpenSSL run-time PKCS#11 modules"
241 else
242 echo "Missing OpenSSL PKCS11 engine directory at '${OPENSSL_ENGINES}'"
243 return 1
244 fi
245
246 export SOFTHSM2_CONF="${WORKDIR}/softhsm2.conf"
247 export SOFTHSM2_DIR="${STAGING_DIR_NATIVE}/var/lib/softhsm/tokens"
248
249 echo "directories.tokendir = $SOFTHSM2_DIR" > "$SOFTHSM2_CONF"
250 echo "objectstore.backend = db" >> "$SOFTHSM2_CONF"
251
252 for env in $(ls "${STAGING_DIR_NATIVE}/var/lib/meta-signing.env.d"); do
253 . "${STAGING_DIR_NATIVE}/var/lib/meta-signing.env.d/$env"
254 done
255}
256# make sure these functions are exported
257signing_prepare[vardeps] += "signing_get_uri signing_get_module"
258
259signing_use_role() {
260 local role="${1}"
261
262 export PKCS11_MODULE_PATH="$(signing_get_module $role)"
263 export PKCS11_URI="$(signing_get_uri $role)"
264
265 if [ -z "$PKCS11_MODULE_PATH" ]; then
266 echo "No PKCS11_MODULE_PATH found for role '${role}'"
267 exit 1
268 fi
269 if [ -z "$PKCS11_URI" ]; then
270 echo "No PKCS11_URI found for role '${role}'"
271 exit 1
272 fi
273}
274
275signing_get_uri() {
276 local role="${1}"
277
278 # prefer local configuration
279 eval local uri="\$SIGNING_PKCS11_URI_${role}_"
280 if [ -n "$uri" ]; then
281 echo "$uri"
282 return
283 fi
284
285 # fall back to softhsm
286 eval echo "\$_SIGNING_PKCS11_URI_${role}_"
287}
288
289signing_get_module() {
290 local role="${1}"
291
292 # prefer local configuration
293 eval local module="\$SIGNING_PKCS11_MODULE_${role}_"
294 if [ -n "$module" ]; then
295 echo "$module"
296 return
297 fi
298
299 # fall back to softhsm
300 eval local module="\$_SIGNING_PKCS11_MODULE_${role}_"
301 if [ "$module" = "softhsm" ]; then
302 echo "${STAGING_LIBDIR_NATIVE}/softhsm/libsofthsm2.so"
303 else
304 echo "$module"
305 fi
306}
307
308python () {
309 signing_class_prepare(d)
310}
311
312export OPENSSL_MODULES="${STAGING_LIBDIR_NATIVE}/ossl-modules"
313export OPENSSL_ENGINES="${STAGING_LIBDIR_NATIVE}/engines-3"
314export OPENSSL_CONF="${STAGING_LIBDIR_NATIVE}/ssl-3/openssl.cnf"
315export SSL_CERT_DIR="${STAGING_LIBDIR_NATIVE}/ssl-3/certs"
316export SSL_CERT_FILE="${STAGING_LIBDIR_NATIVE}/ssl-3/cert.pem"