-
Notifications
You must be signed in to change notification settings - Fork 305
[RFC] qcom-firmware-sign: build-time secure-boot signing of Qualcomm firmware #2598
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Igor Opaniuk (igoropaniuk)
wants to merge
10
commits into
qualcomm-linux:master
Choose a base branch
from
igoropaniuk:feat/secure-boot-signing
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
5086824
security-profiles-native: package Qualcomm Security Profile XMLs
igoropaniuk f032556
sectools-native: package Qualcomm Security Tools v2 binary
igoropaniuk 446e1ec
qcom-firmware-sign: bbclass for sectools-driven firmware signing
igoropaniuk a998372
ci: add ECDSA test-keys kas overlay and test fixtures
igoropaniuk 8c7a2de
ci: add secure-boot kas overlay to enable firmware signing
igoropaniuk 6265560
firmware-qcom-boot-common: wire in qcom-firmware-sign
igoropaniuk bb6079a
firmware-qcom: wire in qcom-firmware-sign
igoropaniuk 52c0458
qdl: drop stale v2.1 recipe
igoropaniuk efca6cc
qdl-native: package linux-msm/qdl v2.7 build-host tool
igoropaniuk a1ee798
image_types_qcom_vip: add qcomflash-vip image type
igoropaniuk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,257 @@ | ||
| # | ||
| # Copyright (c) 2026 Qualcomm Innovation Center, Inc. All rights reserved. | ||
| # SPDX-License-Identifier: BSD-3-Clause-Clear | ||
| # | ||
|
|
||
| QCOM_FIRMWARE_SIGN_ENABLE ??= "0" | ||
|
|
||
| # Sectools binary -- defaults to the one staged by sectools-native. | ||
| QCOM_FIRMWARE_SIGN_SECTOOLS ?= "${STAGING_BINDIR_NATIVE}/sectools" | ||
|
|
||
| QCOM_FIRMWARE_SIGN_SECPROFILE ?= "" | ||
| QCOM_FIRMWARE_SIGN_SECPROFILE_DIR ?= "${STAGING_DATADIR_NATIVE}/qcom-security-profiles" | ||
|
|
||
| # Directory containing the OEM signing material: | ||
| # qpsa_rootca0.cer -- root certificate | ||
| # qpsa_attestca0.cer -- attestation CA certificate | ||
| # qpsa_attestca0.key -- attestation CA private key | ||
| # sha384_roots_hash.txt -- root hash for verification | ||
| # Tests use ci/test-keys/ecdsa via ci/ecdsa-secure-boot-test-keys.yml. | ||
| QCOM_FIRMWARE_SIGN_KEY_DIR ?= "" | ||
|
|
||
| # Fuse identifiers, passed through to consumers / signing wrappers. | ||
| QCOM_FUSE_OEM_HW_ID ?= "" | ||
| QCOM_FUSE_OEM_PRODUCT_ID ?= "" | ||
| QCOM_FUSE_SEC_KEY_DERIVATION_KEY ?= "" | ||
|
|
||
| # Anti-rollback version embedded in the signed images. | ||
| QCOM_FIRMWARE_SIGN_ANTI_ROLLBACK ?= "0x0" | ||
|
|
||
| # Pull in the build-host signing helpers only when signing is enabled. | ||
| DEPENDS += "${@bb.utils.contains('QCOM_FIRMWARE_SIGN_ENABLE', '1', \ | ||
| 'sectools-native security-profiles-native', '', d)}" | ||
|
|
||
| # Mapping from common firmware filenames to sectools image-id labels. | ||
| QCOM_FIRMWARE_SIGN_IMAGE_ID_MAP ??= "\ | ||
| a660_zap.mbn:GPU-MICRO-CODE \ | ||
| adsp.mbn:ADSP \ | ||
| aop.mbn:AOP \ | ||
| cdsp.mbn:CDSP \ | ||
| cpucp.elf:CPUCP \ | ||
| devcfg.mbn:TZ-DEVCFG \ | ||
| hypvm.mbn:QHEE \ | ||
| imagefv.elf:UEFIFV \ | ||
| ipa_fws.mbn:IPA-FW \ | ||
| loadalgota64.mbn:TZ-APP-OEM \ | ||
| msbtfw11.mbn:SKIP \ | ||
| multi_image.mbn:OEM-MISC \ | ||
| prog_firehose_ddr.elf:DEVICE-PROGRAMMER \ | ||
| prog_firehose_lite.elf:DEVICE-PROGRAMMER \ | ||
| qupv3fw.elf:QUPV3 \ | ||
| sec.elf:SEC-ELF \ | ||
| shrm.elf:SHRM \ | ||
| tz.mbn:TZ \ | ||
| uefi.elf:UEFI \ | ||
| uefi_sec.mbn:TZ-APP-OEM \ | ||
| vpu20_1v.mbn:VENUS-FW \ | ||
| vpu30_4v.mbn:VENUS-FW \ | ||
| vpu30_4v_16mb.mbn:VENUS-FW \ | ||
| wpss.mbn:WPSS \ | ||
| xbl_config.elf:XBL-CONFIG \ | ||
| xbl_config_gunyah.elf:XBL-CONFIG \ | ||
| xbl_config_kvm.elf:XBL-CONFIG \ | ||
| xbl.elf:XBL \ | ||
| XblRamdump.elf:XBL-RAM-DUMP \ | ||
| DigestsToSign.bin.mbn:VIP \ | ||
| FD02C9DA-306C-48C7-A49C-BBD827AE86EE.mbn:TZ-APP-OEM \ | ||
| " | ||
|
|
||
| # Internal helper: print the absolute path of the security profile XML | ||
| # (resolving QCOM_FIRMWARE_SIGN_SECPROFILE bare filenames against | ||
| # QCOM_FIRMWARE_SIGN_SECPROFILE_DIR), or fail with bbfatal. | ||
| qcom_sign_resolve_secprofile() { | ||
| profile="${QCOM_FIRMWARE_SIGN_SECPROFILE}" | ||
| if [ -z "${profile}" ]; then | ||
| bbfatal "QCOM_FIRMWARE_SIGN_SECPROFILE is empty -- set it to a profile filename (e.g. kodiak_security_profile.xml) or to an absolute path." | ||
| fi | ||
| case "${profile}" in | ||
| /*) printf '%s\n' "${profile}" ;; | ||
| *) printf '%s/%s\n' "${QCOM_FIRMWARE_SIGN_SECPROFILE_DIR}" "${profile}" ;; | ||
| esac | ||
| } | ||
|
|
||
| # Verifies that everything the signing step needs exists before the | ||
| # task actually runs -- avoids cryptic sectools errors deep in the | ||
| # signing pipeline. | ||
| qcom_check_signing_enabled() { | ||
| if [ "${QCOM_FIRMWARE_SIGN_ENABLE}" != "1" ]; then | ||
| return 1 | ||
| fi | ||
| if [ ! -x "${QCOM_FIRMWARE_SIGN_SECTOOLS}" ]; then | ||
| bbfatal "QCOM_FIRMWARE_SIGN_SECTOOLS ('${QCOM_FIRMWARE_SIGN_SECTOOLS}') is missing or not executable." | ||
| fi | ||
| secprofile="$(qcom_sign_resolve_secprofile)" | ||
| if [ ! -f "${secprofile}" ]; then | ||
| bbfatal "Security profile not found: ${secprofile}" | ||
| fi | ||
| if [ ! -d "${QCOM_FIRMWARE_SIGN_KEY_DIR}" ]; then | ||
| bbfatal "QCOM_FIRMWARE_SIGN_KEY_DIR ('${QCOM_FIRMWARE_SIGN_KEY_DIR}') is not a directory." | ||
| fi | ||
| for f in qpsa_rootca0.cer qpsa_attestca0.cer qpsa_attestca0.key sha384_roots_hash.txt; do | ||
| if [ ! -f "${QCOM_FIRMWARE_SIGN_KEY_DIR}/${f}" ]; then | ||
| bbfatal "Required key file missing: ${QCOM_FIRMWARE_SIGN_KEY_DIR}/${f}" | ||
| fi | ||
| done | ||
| return 0 | ||
| } | ||
|
|
||
| # Look up the sectools image-id for a given firmware filename in | ||
| # QCOM_FIRMWARE_SIGN_IMAGE_ID_MAP. Prints the id (or "SKIP" / "") | ||
| # to stdout; never fails. | ||
| qcom_sign_lookup_image_id() { | ||
| basename="$1" | ||
| for entry in ${QCOM_FIRMWARE_SIGN_IMAGE_ID_MAP}; do | ||
| case "${entry}" in | ||
| "${basename}:"*) printf '%s\n' "${entry#*:}"; return 0 ;; | ||
| esac | ||
| done | ||
| printf '\n' | ||
| } | ||
|
|
||
| # Sign a single file in place. $1 is the absolute path; any non-zero | ||
| # sectools exit triggers a bbfatal in the caller. The primary signal | ||
| # for "is this file signable" is the filename->image-id map -- the | ||
| # upstream Qualcomm reference binaries (qcm6490_bootbinaries.1.0- | ||
| # test-device-public et al.) arrive WITHOUT a pre-existing signature, | ||
| # so checking `--inspect` for a Software ID skips everything. | ||
| qcom_sign_only_file() { | ||
| file_path="$1" | ||
| file_name="$(basename "${file_path}")" | ||
| secprofile="$(qcom_sign_resolve_secprofile)" | ||
|
|
||
| image_id="$(qcom_sign_lookup_image_id "${file_name}")" | ||
| if [ -z "${image_id}" ]; then | ||
| bbdebug 1 "${file_name}: not in image-id map, skipping" | ||
| return 0 | ||
| fi | ||
| case "${image_id}" in | ||
| SKIP|SKIP:*) | ||
| bbnote "${file_name}: skipping (mapped to ${image_id})" | ||
| return 0 | ||
| ;; | ||
| esac | ||
|
|
||
| # Validate the image-id against the active security profile. Each | ||
| # profile only declares a subset of the global image-id namespace, | ||
| # so skip-with-warn rather than fail for out-of-scope ids. | ||
| # | ||
| # sectools v1.48 prints the list as a numbered enumeration, e.g. | ||
| # Available Image IDs: | ||
| # 1. ABL | ||
| # 2. ACPI | ||
| # ... | ||
| # 44. XBL | ||
| # 45. XBL-CONFIG | ||
| if ! "${QCOM_FIRMWARE_SIGN_SECTOOLS}" secure-image --available-image-ids \ | ||
| --security-profile "${secprofile}" 2>/dev/null \ | ||
| | sed -nE 's/^[[:space:]]*[0-9]+\.[[:space:]]*([A-Za-z][A-Za-z0-9_-]*)[[:space:]]*$/\1/p' \ | ||
| | grep -Fqx "${image_id}"; then | ||
| bbwarn "${file_name}: image-id '${image_id}' is not valid for the active security profile, skipping" | ||
| return 0 | ||
| fi | ||
|
|
||
| bbnote "sectools sign ${file_name} as ${image_id}" | ||
| pre_sha="$(sha256sum "${file_path}" | cut -d' ' -f1)" | ||
| "${QCOM_FIRMWARE_SIGN_SECTOOLS}" secure-image \ | ||
| --sign "${file_path}" \ | ||
| --image-id="${image_id}" \ | ||
| --security-profile "${secprofile}" \ | ||
| --anti-rollback-version="${QCOM_FIRMWARE_SIGN_ANTI_ROLLBACK}" \ | ||
| --signing-mode LOCAL \ | ||
| --root-certificate-index 0 \ | ||
| --root-certificate="${QCOM_FIRMWARE_SIGN_KEY_DIR}/qpsa_rootca0.cer" \ | ||
| --ca-certificate="${QCOM_FIRMWARE_SIGN_KEY_DIR}/qpsa_attestca0.cer" \ | ||
| --ca-key="${QCOM_FIRMWARE_SIGN_KEY_DIR}/qpsa_attestca0.key" \ | ||
| --outfile "${file_path}" \ | ||
| || bbfatal "Signing ${file_name} failed" | ||
|
|
||
| # sectools' --sign returns 0 for non-fatal "I don't know how to sign | ||
| # this" cases (e.g. plain ELF passed in for an image-id whose profile | ||
| # entry expects MBN-V6) and produces a byte-identical output instead | ||
| # of erroring out. Detect that explicitly so silent no-ops surface as | ||
| # warnings instead of being caught downstream by a confusing | ||
| # verify-root failure. | ||
| post_sha="$(sha256sum "${file_path}" | cut -d' ' -f1)" | ||
| if [ "${pre_sha}" = "${post_sha}" ]; then | ||
| bbwarn "${file_name}: sectools --sign left the file byte-identical when signing as '${image_id}' -- the input does not appear to be in the format that image-id expects. Mark this entry as 'SKIP:<reason>' in QCOM_FIRMWARE_SIGN_IMAGE_ID_MAP if this is intentional." | ||
| fi | ||
| } | ||
|
|
||
| # Sign + verify-root in one step. Use this for files where the active | ||
| # security profile permits OEM-only signatures (i.e. does NOT have | ||
| # <oem_vouch_for_disallowed/> on the image entry). For files where the | ||
| # profile requires hybrid OEM+QTI vouching (e.g. VIP under the upstream | ||
| # github security-profiles repo), call qcom_sign_only_file() instead -- | ||
| # sectools' verify-root rejects OEM-only signatures against those | ||
| # profile entries even though the boot ROM accepts them. | ||
| qcom_sign_verify_file() { | ||
| file_path="$1" | ||
| file_name="$(basename "${file_path}")" | ||
|
|
||
| pre_sha="$(sha256sum "${file_path}" | cut -d' ' -f1)" | ||
| qcom_sign_only_file "$1" || return $? | ||
| post_sha="$(sha256sum "${file_path}" | cut -d' ' -f1)" | ||
|
|
||
| # If sign was a no-op (file not in map, mapped to SKIP, image-id | ||
| # rejected by the profile, or sectools silently refused -- the warn | ||
| # case in qcom_sign_only_file) there is nothing to verify. Skipping | ||
| # the verify-root is correct: running it against unchanged bytes | ||
| # would fail with a confusing "infile is not signed by OEM" error. | ||
| if [ "${pre_sha}" = "${post_sha}" ]; then | ||
| bbdebug 1 "${file_name}: unchanged after sign attempt, skipping verify-root" | ||
| return 0 | ||
| fi | ||
|
|
||
| root_hash="0x$(cut -d' ' -f2 "${QCOM_FIRMWARE_SIGN_KEY_DIR}/sha384_roots_hash.txt")" | ||
| "${QCOM_FIRMWARE_SIGN_SECTOOLS}" secure-image --verify-root "${root_hash}" "${file_path}" \ | ||
| || bbfatal "Root hash verification of ${file_name} failed" | ||
| } | ||
|
|
||
| # Default do_qcom_firmware_sign: walks ${B}/firmware-to-sign and signs | ||
| # every *.mbn / *.elf in it. Consumers normally override this to point | ||
| # at the actual directory holding their images (e.g. ${D}/ for recipes | ||
| # using firmware_install, or ${S}/ for archive-unpacked recipes). | ||
| do_qcom_firmware_sign() { | ||
| if ! qcom_check_signing_enabled ; then | ||
| return 0 | ||
| fi | ||
|
|
||
| sign_root="${B}/firmware-to-sign" | ||
| if [ ! -d "${sign_root}" ]; then | ||
| bbnote "qcom-firmware-sign: ${sign_root} does not exist, nothing to do." | ||
| return 0 | ||
| fi | ||
|
|
||
| bbnote "qcom-firmware-sign: signing MBN/ELF under ${sign_root}" | ||
| find "${sign_root}" -type f \( -iname '*.mbn' -o -iname '*.elf' \) | while read -r f; do | ||
| qcom_sign_verify_file "${f}" || bbfatal "Failed to sign: ${f}" | ||
| done | ||
| } | ||
|
|
||
| do_qcom_firmware_sign[vardeps] += "\ | ||
| QCOM_FIRMWARE_SIGN_ENABLE \ | ||
| QCOM_FIRMWARE_SIGN_KEY_DIR \ | ||
| QCOM_FIRMWARE_SIGN_SECPROFILE \ | ||
| QCOM_FIRMWARE_SIGN_SECPROFILE_DIR \ | ||
| QCOM_FIRMWARE_SIGN_ANTI_ROLLBACK \ | ||
| QCOM_FIRMWARE_SIGN_IMAGE_ID_MAP \ | ||
| QCOM_FUSE_OEM_HW_ID \ | ||
| QCOM_FUSE_OEM_PRODUCT_ID \ | ||
| QCOM_FUSE_SEC_KEY_DERIVATION_KEY \ | ||
| " | ||
|
|
||
| # Run before do_deploy (for recipes that ship signed blobs via DEPLOYDIR, | ||
| # e.g. firmware-qcom-boot-common.inc) and before do_package (for recipes | ||
| # that ship via package_install into /lib/firmware, e.g. | ||
| # firmware-qcom.inc / firmware-qcom-hlosfw style consumers) | ||
| addtask qcom_firmware_sign before do_deploy before do_package after do_install | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again :-)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing EOL |
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we move this to an inc file or similar? This is just metadata for the class.