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