diff options
Diffstat (limited to 'meta-oe/classes')
-rw-r--r-- | meta-oe/classes/gitpkgv.bbclass | 14 | ||||
-rw-r--r-- | meta-oe/classes/image_types_sparse.bbclass | 11 | ||||
-rw-r--r-- | meta-oe/classes/image_types_verity.bbclass | 137 | ||||
-rw-r--r-- | meta-oe/classes/signing.bbclass | 316 |
4 files changed, 466 insertions, 12 deletions
diff --git a/meta-oe/classes/gitpkgv.bbclass b/meta-oe/classes/gitpkgv.bbclass index 180421ed35..5ab507969c 100644 --- a/meta-oe/classes/gitpkgv.bbclass +++ b/meta-oe/classes/gitpkgv.bbclass @@ -25,17 +25,17 @@ # # inherit gitpkgv # -# PV = "1.0+gitr${SRCPV}" # expands to something like 1.0+gitr3+4c1c21d7dbbf93b0df336994524313dfe0d4963b -# PKGV = "1.0+gitr${GITPKGV}" # expands also to something like 1.0+gitr31337+4c1c21d7d +# PV = "1.0+git" # expands to 1.0+git +# PKGV = "1.0+git${GITPKGV}" # expands also to something like 1.0+git31337+4c1c21d7d # # or # # inherit gitpkgv # -# PV = "1.0+gitr${SRCPV}" # expands to something like 1.0+gitr3+4c1c21d7dbbf93b0df336994524313dfe0d4963b -# PKGV = "${GITPKGVTAG}" # expands to something like 1.0-31337+g4c1c21d -# if there is tag v1.0 before this revision or -# ver1.0-31337+g4c1c21d if there is tag ver1.0 +# PV = "1.0+git" # expands to 1.0+git +# PKGV = "${GITPKGVTAG}" # expands to something like 1.0-31337+g4c1c21d +# if there is tag v1.0 before this revision or +# ver1.0-31337+g4c1c21d if there is tag ver1.0 GITPKGV = "${@get_git_pkgv(d, False)}" GITPKGVTAG = "${@get_git_pkgv(d, True)}" @@ -56,7 +56,7 @@ def gitpkgv_drop_tag_prefix(d, version): def get_git_pkgv(d, use_tags): import os import bb - from pipes import quote + from shlex import quote src_uri = d.getVar('SRC_URI').split() fetcher = bb.fetch2.Fetch(src_uri, d) diff --git a/meta-oe/classes/image_types_sparse.bbclass b/meta-oe/classes/image_types_sparse.bbclass index 69e24cbb79..d6ea68968e 100644 --- a/meta-oe/classes/image_types_sparse.bbclass +++ b/meta-oe/classes/image_types_sparse.bbclass @@ -8,9 +8,10 @@ inherit image_types SPARSE_BLOCK_SIZE ??= "4096" CONVERSIONTYPES += "sparse" -CONVERSION_CMD:sparse() { - INPUT="${IMAGE_NAME}${IMAGE_NAME_SUFFIX}.${type}" - truncate --no-create --size=%${SPARSE_BLOCK_SIZE} "$INPUT" - img2simg -s "$INPUT" "$INPUT.sparse" ${SPARSE_BLOCK_SIZE} -} + +CONVERSION_CMD:sparse = " \ + truncate --no-create --size=%${SPARSE_BLOCK_SIZE} "${IMAGE_NAME}.${type}"; \ + img2simg -s "${IMAGE_NAME}.${type}" "${IMAGE_NAME}.${type}.sparse" ${SPARSE_BLOCK_SIZE}; \ + " + CONVERSION_DEPENDS_sparse = "android-tools-native" diff --git a/meta-oe/classes/image_types_verity.bbclass b/meta-oe/classes/image_types_verity.bbclass new file mode 100644 index 0000000000..b42217c453 --- /dev/null +++ b/meta-oe/classes/image_types_verity.bbclass @@ -0,0 +1,137 @@ +# SPDX-License-Identifier: MIT +# +# Copyright Pengutronix <yocto@pengutronix.de> +# + +# Support generating a dm-verity image and the parameters required to assemble +# the corresponding table for the device-mapper driver. The latter will be +# stored in the file ${DEPLOY_DIR_IMAGE}/<IMAGE_LINK_NAME>.verity-params. Note +# that in the resulting image the hash tree data is appended to the contents of +# the original image without an explicit superblock to keep things simple and +# compact. +# +# The above mentioned parameter file can be sourced by a shell to finally create +# the desired blockdevice via "dmsetup" (found in meta-oe's recipe +# "libdevmapper"), e.g. +# +# . <IMAGE_LINK_NAME>.verity-params +# dmsetup create <dm_dev_name> --readonly --table "0 $VERITY_DATA_SECTORS \ +# verity 1 <dev> <hash_dev> \ +# $VERITY_DATA_BLOCK_SIZE $VERITY_HASH_BLOCK_SIZE \ +# $VERITY_DATA_BLOCKS $VERITY_DATA_BLOCKS \ +# $VERITY_HASH_ALGORITHM $VERITY_ROOT_HASH $VERITY_SALT \ +# 1 ignore_zero_blocks" +# +# As the hash tree data is found at the end of the image, <dev> and <hash_dev> +# should be the same blockdevice in the command shown above while <dm_dev_name> +# is the name of the to be created dm-verity-device. +# +# The root hash is calculated using a salt to make attacks more difficult. Thus, +# please grant each image recipe its own salt which could be generated e.g. via +# +# dd if=/dev/random bs=1k count=1 | sha256sum +# +# and assign it to the parameter VERITY_SALT. + +inherit image-artifact-names + +do_image_verity[depends] += "cryptsetup-native:do_populate_sysroot" + +CLASS_VERITY_SALT = "4e5f0d9b6ccac5e843598d4e4545046232b48451a399acb2106822b43679b375" +VERITY_SALT ?= "${CLASS_VERITY_SALT}" +VERITY_BLOCK_SIZE ?= "4096" +VERITY_IMAGE_FSTYPE ?= "ext4" +VERITY_IMAGE_SUFFIX ?= ".verity" +VERITY_INPUT_IMAGE ?= "${IMGDEPLOYDIR}/${IMAGE_LINK_NAME}.${VERITY_IMAGE_FSTYPE}" + +IMAGE_TYPEDEP:verity = "${VERITY_IMAGE_FSTYPE}" +IMAGE_TYPES_MASKED += "verity" + +python __anonymous() { + if 'verity' not in d.getVar('IMAGE_FSTYPES'): + return + + dep_task = 'do_image_{}'.format(d.getVar('VERITY_IMAGE_FSTYPE').replace('-', '_')) + bb.build.addtask('do_image_verity', 'do_image_complete', dep_task, d) +} + +python do_image_verity () { + import os + import subprocess + import shutil + + link = d.getVar('VERITY_INPUT_IMAGE') + image = os.path.realpath(link) + + verity_image_suffix = d.getVar('VERITY_IMAGE_SUFFIX') + verity = '{}{}'.format(image, verity_image_suffix) + + # For better readability the parameter VERITY_BLOCK_SIZE is specified in + # bytes. It must be a multiple of the logical sector size which is 512 bytes + # in Linux. Make sure that this is the case as otherwise the resulting + # issues would be hard to debug later. + block_size = int(d.getVar('VERITY_BLOCK_SIZE')) + if block_size % 512 != 0: + bb.fatal("VERITY_BLOCK_SIZE must be a multiple of 512!") + + salt = d.getVar('VERITY_SALT') + if salt == d.getVar('CLASS_VERITY_SALT'): + bb.warn("Please overwrite VERITY_SALT with an image specific one!") + + shutil.copyfile(image, verity) + + data_size_blocks, data_size_rest = divmod(os.stat(verity).st_size, block_size) + data_blocks = data_size_blocks + (1 if data_size_rest else 0) + data_size = data_blocks * block_size + + bb.debug(1, f"data_size_blocks: {data_size_blocks}, {data_size_rest}") + bb.debug(1, f"data_size: {data_size}") + + # Create verity image + try: + output = subprocess.check_output([ + 'veritysetup', 'format', + '--no-superblock', + '--salt={}'.format(salt), + '--data-blocks={}'.format(data_blocks), + '--data-block-size={}'.format(block_size), + '--hash-block-size={}'.format(block_size), + '--hash-offset={}'.format(data_size), + verity, verity, + ]) + except subprocess.CalledProcessError as err: + bb.fatal('%s returned with %s (%s)' % (err.cmd, err.returncode, err.output)) + + try: + with open(image + '.verity-info', 'wb') as f: + f.write(output) + except Exception as err: + bb.fatal('Unexpected error %s' % err) + + # Create verity params + params = [] + for line in output.decode('ASCII').splitlines(): + if not ':' in line: + continue + k, v = line.split(':', 1) + k = k.strip().upper().replace(' ', '_') + v = v.strip() + bb.debug(1, f"{k} {v}") + params.append('VERITY_{}={}'.format(k, v)) + + params.append('VERITY_DATA_SECTORS={}'.format(data_size//512)) + + try: + with open(image + '.verity-params', 'w') as f: + f.write('\n'.join(params)) + except Exception as err: + bb.fatal('Unexpected error %s' % err) + + # Create symlinks + for suffix in [ verity_image_suffix, '.verity-info', '.verity-params' ]: + try: + os.remove(link + suffix) + except FileNotFoundError: + pass + os.symlink(os.path.basename(image) + suffix, link + suffix) +} diff --git a/meta-oe/classes/signing.bbclass b/meta-oe/classes/signing.bbclass new file mode 100644 index 0000000000..f52d861b76 --- /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 an HSM (Hardware Security Module). While files are easy to +# use, it's hard to verify that no copies of the private key 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> <der> +# +# Import a public key from DER file to a role. To be used with SoftHSM. +signing_import_pubkey_from_der() { + 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 pkey \ + -passin "file:${IMPORT_PASS_FILE}" \ + -in "${pem}" -inform pem -pubout -outform der + else + openssl pkey \ + -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> <der> +# +# 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 pkey \ + -passin "file:${IMPORT_PASS_FILE}" \ + -in "${pem}" -inform pem -outform der + else + openssl pkey \ + -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() { + 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" + + 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) +} |