diff mbox series

[meta-oe] add signing.bbclass as infrastructure for build artifact signing

Message ID 20230213112037.131445-1-jlu@pengutronix.de
State Under Review
Headers show
Series [meta-oe] add signing.bbclass as infrastructure for build artifact signing | expand

Commit Message

Jan Lübbe Feb. 13, 2023, 11:20 a.m. UTC
This adds common infrastructure to access and used asymmetric keys to
sign build artifacts. The approach and implementation was presented at
the recent OpenEmbedded Workshop:
https://pretalx.com/openembedded-workshop-2023/talk/3C8MFF/

A working demo setup for verified boot based on qemu is available at
https://github.com/jluebbe/meta-code-signing.

Signed-off-by: Jan Luebbe <jlu@pengutronix.de>
---
 meta-oe/classes/signing.bbclass | 316 ++++++++++++++++++++++++++++++++
 1 file changed, 316 insertions(+)
 create mode 100644 meta-oe/classes/signing.bbclass
diff mbox series

Patch

diff --git a/meta-oe/classes/signing.bbclass b/meta-oe/classes/signing.bbclass
new file mode 100644
index 000000000000..5c74a319e4f9
--- /dev/null
+++ b/meta-oe/classes/signing.bbclass
@@ -0,0 +1,316 @@ 
+#
+# Copyright Jan Luebbe <jlu@pengutronix.de>
+#
+# SPDX-License-Identifier: MIT
+#
+
+# This class provides a common workflow to use asymmetric (i.e. RSA) keys to
+# sign artifacts. Usually, the keys are either stored as simple files in the
+# file system or on a HSM (Hardware Security Module). While files are easy to
+# use, it's hard to verify that no copies of the private have been made and
+# only authorized persons are able to use the key. Use of an HSM addresses
+# these risks by only allowing use of the key via an API (often PKCS #11). The
+# standard way of referring to a specific key in an HSM are PKCS #11 URIs (RFC
+# 7512).
+#
+# Many software projects support signing using PKCS #11 keys, but configuring
+# this is very project specific. Furthermore, as physical HSMs are not very
+# widespread, testing code signing in CI is not simple. To solve this at the
+# build system level, this class takes the approach of always using PKCS #11 at
+# the recipe level. For cases where the keys are available as files (i.e. test
+# keys in CI), they are imported into SoftHSM (a HSM emulation library).
+# 
+# Recipes access the available keys via a specific role. So, depending on
+# whether we're building during development or for release, a given role can
+# refer to different keys.
+# Each key recipe PROVIDES a virtual package corresponding to the role, allowing
+# the user to select one of multiple keys for a role when needed.
+#
+# For use with a real HSM, a PKCS #11 URI can be set (i.e. in local.conf) to
+# override the SoftHSM key with the real one:
+#
+#   SIGNING_PKCS11_URI[fit] = "pkcs11:serial=DENK0200554;object=ptx-dev-rauc&pin-value=123456"
+#   SIGNING_PKCS11_MODULE[fit] = "/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so"
+#
+# Examples for defining roles and importing keys:
+# 
+#   meta-code-signing/recipes-security/signing-keys/dummy-rsa-key-native.bb
+#   meta-code-signing-demo/recipes-security/ptx-dev-keys/ptx-dev-keys-native_git.bb
+#
+# Examples for using keys for signing:
+#
+#   meta-code-signing-demo/recipes-security/fit-image/linux-fit-image.bb
+#   meta-code-signing-demo/recipes-core/bundles/update-bundle.bb
+#
+# Examples for using keys for authentication:
+#
+#   meta-code-signing-demo/recipes-security/fit-image/barebox_%.bbappend
+#   meta-code-signing-demo/recipes-core/rauc/rauc_%.bbappend
+#
+# Examples for using keys for both signing and authentication:
+#
+#   meta-code-signing-demo/recipes-kernel/linux/linux-yocto_6.1.bbappend
+
+SIGNING_PKCS11_URI ?= ""
+SIGNING_PKCS11_MODULE ?= ""
+
+DEPENDS += "softhsm-native libp11-native opensc-native openssl-native"
+
+def signing_class_prepare(d):
+    import os.path
+
+    def export(role, k, v):
+        k = k % (role, )
+        d.setVar(k, v)
+        d.setVarFlag(k, "export", "1")
+
+    roles = set()
+    roles |= (d.getVarFlags("SIGNING_PKCS11_URI") or {}).keys()
+    roles |= (d.getVarFlags("SIGNING_PKCS11_MODULE") or {}).keys()
+    for role in roles:
+        if not set(role).issubset("abcdefghijklmnopqrstuvwxyz0123456789_"):
+            bb.fatal("key role name '%s' must consist of only [a-z0-9_]" % (role,))
+
+        pkcs11_uri = d.getVarFlag("SIGNING_PKCS11_URI", role) or d.getVar("SIGNING_PKCS11_URI")
+        if not pkcs11_uri.startswith("pkcs11:"):
+            bb.fatal("URI for key role '%s' must start with 'pkcs11:'" % (role,))
+
+        pkcs11_module = d.getVarFlag("SIGNING_PKCS11_MODULE", role) or d.getVar("SIGNING_PKCS11_MODULE")
+        if not os.path.isfile(pkcs11_module):
+            bb.fatal("module path for key role '%s' must be an existing file" % (role,))
+
+        if pkcs11_uri and not pkcs11_module:
+            bb.warn("SIGNING_PKCS11_URI[%s] is set without SIGNING_PKCS11_MODULE[%s]" % (role, role))
+        if pkcs11_module and not pkcs11_uri:
+            bb.warn("SIGNING_PKCS11_MODULE[%s] is set without SIGNING_PKCS11_URI[%s]" % (role, role))
+
+        export(role, "SIGNING_PKCS11_URI_%s_", pkcs11_uri)
+        export(role, "SIGNING_PKCS11_MODULE_%s_", pkcs11_module)
+
+signing_pkcs11_tool() {
+    pkcs11-tool --module "${STAGING_LIBDIR_NATIVE}/softhsm/libsofthsm2.so" --login --pin 1111 $*
+}
+
+signing_import_prepare() {
+    export _SIGNING_ENV_FILE_="${B}/meta-signing.env"
+    rm -f "$_SIGNING_ENV_FILE_"
+
+    export SOFTHSM2_CONF="${B}/softhsm2.conf"
+    export SOFTHSM2_DIR="${B}/softhsm2.tokens"
+    export SOFTHSM2_MOD="${STAGING_LIBDIR_NATIVE}/softhsm/libsofthsm2.so"
+
+    echo "directories.tokendir = $SOFTHSM2_DIR" > "$SOFTHSM2_CONF"
+    echo "objectstore.backend = db" >> "$SOFTHSM2_CONF"
+    rm -rf "$SOFTHSM2_DIR"
+    mkdir -p "$SOFTHSM2_DIR"
+
+    softhsm2-util --module $SOFTHSM2_MOD --init-token --free --label ${PN} --pin 1111 --so-pin 222222
+}
+
+signing_import_define_role() {
+    local role="${1}"
+    case "${1}" in
+        (*[!a-z0-9_]*) false;;
+        (*) true;;
+    esac || bbfatal "invalid role name '${1}', must consist of [a-z0-9_]"
+
+    echo "_SIGNING_PKCS11_URI_${role}_=\"pkcs11:token=${PN};object=$role;pin-value=1111\"" >> $_SIGNING_ENV_FILE_
+    echo "_SIGNING_PKCS11_MODULE_${role}_=\"softhsm\"" >> $_SIGNING_ENV_FILE_
+}
+
+# signing_import_cert_from_der <role> <der>
+#
+# Import a certificate from DER file to a role. To be used
+# with SoftHSM.
+signing_import_cert_from_der() {
+    local role="${1}"
+    local der="${2}"
+
+    signing_pkcs11_tool --type cert --write-object "${der}" --label "${role}"
+}
+
+# signing_import_cert_from_pem <role> <pem>
+#
+# Import a certificate from PEM file to a role. To be used
+# with SoftHSM.
+signing_import_cert_from_pem() {
+    local role="${1}"
+    local pem="${2}"
+
+    openssl x509 \
+        -in "${pem}" -inform pem -outform der |
+    signing_pkcs11_tool --type cert --write-object /proc/self/fd/0 --label "${role}"
+}
+
+# signing_import_pubkey_from_der <role> <pem>
+#
+# Import a public key from DER file to a role. To be used with SoftHSM.
+signing_import_pubkey_from_pem() {
+    local role="${1}"
+    local der="${2}"
+
+    signing_pkcs11_tool --type pubkey --write-object "${der}" --label "${role}"
+}
+
+# signing_import_pubkey_from_pem <role> <pem>
+#
+# Import a public key from PEM file to a role. To be used with SoftHSM.
+signing_import_pubkey_from_pem() {
+    local openssl_keyopt
+    local role="${1}"
+    local pem="${2}"
+
+    if [ -n "${IMPORT_PASS_FILE}" ]; then
+        openssl rsa \
+            -passin "file:${IMPORT_PASS_FILE}" \
+            -in "${pem}" -inform pem -pubout -outform der
+    else
+        openssl rsa \
+            -in "${pem}" -inform pem -pubout -outform der
+    fi |
+    signing_pkcs11_tool --type pubkey --write-object /proc/self/fd/0 --label "${role}"
+}
+
+# signing_import_privkey_from_der <role> <pem>
+#
+# Import a private key from DER file to a role. To be used with SoftHSM.
+signing_import_privkey_from_der() {
+    local role="${1}"
+    local der="${2}"
+    signing_pkcs11_tool --type privkey --write-object "${der}" --label "${role}"
+}
+
+# signing_import_privkey_from_pem <role> <pem>
+#
+# Import a private key from PEM file to a role. To be used with SoftHSM.
+signing_import_privkey_from_pem() {
+    local openssl_keyopt
+    local role="${1}"
+    local pem="${2}"
+
+    if [ -n "${IMPORT_PASS_FILE}" ]; then
+        openssl rsa \
+            -passin "file:${IMPORT_PASS_FILE}" \
+            -in "${pem}" -inform pem -outform der
+    else
+        openssl rsa \
+            -in "${pem}" -inform pem -outform der
+    fi |
+    signing_pkcs11_tool --type privkey --write-object /proc/self/fd/0 --label "${role}"
+}
+
+# signing_import_key_from_pem <role> <pem>
+#
+# Import a private and public key from PEM file to a role. To be used
+# with SoftHSM.
+signing_import_key_from_pem() {
+    local role="${1}"
+    local pem="${2}"
+
+    signing_import_pubkey_from_pem "${role}" "${pem}"
+    signing_import_privkey_from_pem "${role}" "${pem}"
+}
+
+signing_import_finish() {
+    echo "loaded objects:"
+    signing_pkcs11_tool --list-objects
+}
+
+signing_import_install() {
+    install -d ${D}${localstatedir}/lib/softhsm/tokens/${PN}
+    install -m 600 -t ${D}${localstatedir}/lib/softhsm/tokens/${PN} ${B}/softhsm2.tokens/*/*
+    install -d ${D}${localstatedir}/lib/meta-signing.env.d
+    install -m 644 "${B}/meta-signing.env" ${D}${localstatedir}/lib/meta-signing.env.d/${PN}
+}
+
+signing_prepare() {
+    if [ -f ${OPENSSL_CONF} ]; then
+        echo "Using '${OPENSSL_CONF}' for OpenSSL configuration"
+    else
+        echo "Missing 'openssl.cnf' at '${STAGING_ETCDIR_NATIVE}/ssl'"
+        return 1
+    fi
+    if [ -d ${OPENSSL_MODULES} ]; then
+        echo "Using '${OPENSSL_MODULES}' for OpenSSL run-time modules"
+    else
+        echo "Missing OpenSSL module directory at '${OPENSSL_MODULES}'"
+        return 1
+    fi
+    if [ -d ${OPENSSL_ENGINES} ]; then
+        echo "Using '${OPENSSL_ENGINES}' for OpenSSL run-time PKCS#11 modules"
+    else
+        echo "Missing OpenSSL PKCS11 engine directory at '${OPENSSL_ENGINES}'"
+        return 1
+    fi
+
+    export SOFTHSM2_CONF="${WORKDIR}/softhsm2.conf"
+    export SOFTHSM2_DIR="${STAGING_DIR_NATIVE}/var/lib/softhsm/tokens"
+
+    echo "directories.tokendir = $SOFTHSM2_DIR" > "$SOFTHSM2_CONF"
+    echo "objectstore.backend = db" >> "$SOFTHSM2_CONF"
+
+    for env in $(ls "${STAGING_DIR_NATIVE}/var/lib/meta-signing.env.d"); do
+        . "${STAGING_DIR_NATIVE}/var/lib/meta-signing.env.d/$env"
+    done
+}
+# make sure these functions are exported
+signing_prepare[vardeps] += "signing_get_uri signing_get_module"
+
+signing_use_role() {
+    local role="${1}"
+
+    export PKCS11_MODULE_PATH="$(signing_get_module $role)"
+    export PKCS11_URI="$(signing_get_uri $role)"
+
+    if [ -z "$PKCS11_MODULE_PATH" ]; then
+        echo "No PKCS11_MODULE_PATH found for role '${role}'"
+        exit 1
+    fi
+    if [ -z "$PKCS11_URI" ]; then
+        echo "No PKCS11_URI found for role '${role}'"
+        exit 1
+    fi
+}
+
+signing_get_uri() {
+    local role="${1}"
+
+    # prefer local configuration
+    eval local uri="\$SIGNING_PKCS11_URI_${role}_"
+    if [ -n "$uri" ]; then
+        echo "$uri"
+        return
+    fi
+
+    # fall back to softhsm
+    eval echo "\$_SIGNING_PKCS11_URI_${role}_"
+}
+
+signing_get_module() {
+    local role="${1}"
+
+    # prefer local configuration
+    eval local module="\$SIGNING_PKCS11_MODULE_${role}_"
+    if [ -n "$module" ]; then
+        echo "$module"
+        return
+    fi
+
+    # fall back to softhsm
+    eval local module="\$_SIGNING_PKCS11_MODULE_${role}_"
+    if [ "$module" = "softhsm" ]; then
+        echo "${STAGING_LIBDIR_NATIVE}/softhsm/libsofthsm2.so"
+    else
+        echo "$module"
+    fi
+}
+
+python () {
+    signing_class_prepare(d)
+}
+
+export OPENSSL_MODULES="${STAGING_LIBDIR_NATIVE}/ossl-modules"
+export OPENSSL_ENGINES="${STAGING_LIBDIR_NATIVE}/engines-3"
+export OPENSSL_CONF="${STAGING_LIBDIR_NATIVE}/ssl-3/openssl.cnf"
+export SSL_CERT_DIR="${STAGING_LIBDIR_NATIVE}/ssl-3/certs"
+export SSL_CERT_FILE="${STAGING_LIBDIR_NATIVE}/ssl-3/cert.pem"