1#
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 an HSM (Hardware Security Module). While files are easy to
10# use, it's hard to verify that no copies of the private key have been made
11# and 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> <der>
146#
147# Import a public key from DER file to a role. To be used with SoftHSM.
148signing_import_pubkey_from_der() {
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 pkey \
165            -passin "file:${IMPORT_PASS_FILE}" \
166            -in "${pem}" -inform pem -pubout -outform der
167    else
168        openssl pkey \
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> <der>
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 pkey \
193            -passin "file:${IMPORT_PASS_FILE}" \
194            -in "${pem}" -inform pem -outform der
195    else
196        openssl pkey \
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    export OPENSSL_MODULES="${STAGING_LIBDIR_NATIVE}/ossl-modules"
228    export OPENSSL_ENGINES="${STAGING_LIBDIR_NATIVE}/engines-3"
229    export OPENSSL_CONF="${STAGING_LIBDIR_NATIVE}/ssl-3/openssl.cnf"
230    export SSL_CERT_DIR="${STAGING_LIBDIR_NATIVE}/ssl-3/certs"
231    export SSL_CERT_FILE="${STAGING_LIBDIR_NATIVE}/ssl-3/cert.pem"
232
233    if [ -f ${OPENSSL_CONF} ]; then
234        echo "Using '${OPENSSL_CONF}' for OpenSSL configuration"
235    else
236        echo "Missing 'openssl.cnf' at '${STAGING_ETCDIR_NATIVE}/ssl'"
237        return 1
238    fi
239    if [ -d ${OPENSSL_MODULES} ]; then
240        echo "Using '${OPENSSL_MODULES}' for OpenSSL run-time modules"
241    else
242        echo "Missing OpenSSL module directory at '${OPENSSL_MODULES}'"
243        return 1
244    fi
245    if [ -d ${OPENSSL_ENGINES} ]; then
246        echo "Using '${OPENSSL_ENGINES}' for OpenSSL run-time PKCS#11 modules"
247    else
248        echo "Missing OpenSSL PKCS11 engine directory at '${OPENSSL_ENGINES}'"
249        return 1
250    fi
251
252    export SOFTHSM2_CONF="${WORKDIR}/softhsm2.conf"
253    export SOFTHSM2_DIR="${STAGING_DIR_NATIVE}/var/lib/softhsm/tokens"
254
255    echo "directories.tokendir = $SOFTHSM2_DIR" > "$SOFTHSM2_CONF"
256    echo "objectstore.backend = db" >> "$SOFTHSM2_CONF"
257
258    for env in $(ls "${STAGING_DIR_NATIVE}/var/lib/meta-signing.env.d"); do
259        . "${STAGING_DIR_NATIVE}/var/lib/meta-signing.env.d/$env"
260    done
261}
262# make sure these functions are exported
263signing_prepare[vardeps] += "signing_get_uri signing_get_module"
264
265signing_use_role() {
266    local role="${1}"
267
268    export PKCS11_MODULE_PATH="$(signing_get_module $role)"
269    export PKCS11_URI="$(signing_get_uri $role)"
270
271    if [ -z "$PKCS11_MODULE_PATH" ]; then
272        echo "No PKCS11_MODULE_PATH found for role '${role}'"
273        exit 1
274    fi
275    if [ -z "$PKCS11_URI" ]; then
276        echo "No PKCS11_URI found for role '${role}'"
277        exit 1
278    fi
279}
280
281signing_get_uri() {
282    local role="${1}"
283
284    # prefer local configuration
285    eval local uri="\$SIGNING_PKCS11_URI_${role}_"
286    if [ -n "$uri" ]; then
287        echo "$uri"
288        return
289    fi
290
291    # fall back to softhsm
292    eval echo "\$_SIGNING_PKCS11_URI_${role}_"
293}
294
295signing_get_module() {
296    local role="${1}"
297
298    # prefer local configuration
299    eval local module="\$SIGNING_PKCS11_MODULE_${role}_"
300    if [ -n "$module" ]; then
301        echo "$module"
302        return
303    fi
304
305    # fall back to softhsm
306    eval local module="\$_SIGNING_PKCS11_MODULE_${role}_"
307    if [ "$module" = "softhsm" ]; then
308        echo "${STAGING_LIBDIR_NATIVE}/softhsm/libsofthsm2.so"
309    else
310        echo "$module"
311    fi
312}
313
314python () {
315    signing_class_prepare(d)
316}
317