Digest signing¶
A digest signing request carries a hash you compute, not your data. The platform signs it with the operation's HSM key, after approval if the operation needs one, and you download the signature or CMS and place it in your artifact.
- Hash locally. Only the digest leaves your machine, together with what you pass in
--cms-cert,--cms-contentor--cms-extra-attributes. imagesigning add <subcommand> ... [--format CMS] [--cms-* settings]. Settings the platform can check without the key are refused when you send the request, before anyone approves it: an unreadable certificate, an unknown value, conflicting options, a private key in--cms-cert, and on DigestSigning and SignOCI any setting that needs a certificate you did not pass.- Approval, as for every request.
- The HSM signs and the platform builds the CMS. A problem that only the key reveals ends the request in state
Failed, with the reason in
errorStatusanderrorDetail(see Errors). imagesigning get -I <request-uuid> --wait -O <file> [--bundle <file>.json].
Use imagesigning add when your device reads a CMS or a raw signature. Use
imagesigning envelope when it reads JWS, COSE, DSSE, IMA, Mender or a signed kernel module.
The digest subcommands are DigestSigning, SignHABCSF, SignHABIMG and SignAHAB. They take the same
options, and each accepts any digest operation: what you get depends on the type of the operation --operid
names, as the tables below show.
Algorithms¶
| Key | Hash (-H) |
Options | Output |
|---|---|---|---|
| RSA, all supported key sizes | SHA256, SHA384, SHA512 | --padding PKCS_v1_5 (default) |
PKCS #1 v1.5 signature |
| RSA, all supported key sizes | SHA256, SHA384, SHA512 | --padding PSS, optionally --salt-length |
RSASSA-PSS, MGF1 with the same hash |
| EC P-224, P-256, P-384, P-521 | SHA256, SHA384, SHA512 | --encoding DER (default) |
ASN.1 DER ECDSA-Sig-Value |
| EC P-224, P-256, P-384, P-521 | SHA256, SHA384, SHA512 | --encoding RAW |
r‖s, 56 / 64 / 96 / 132 bytes |
| EC P-224, P-256, P-384, P-521 | SHA256, SHA384, SHA512 | --encoding RAW_LE |
r‖s with each half little-endian |
-pis the digest, base64.-Hnames the hash it was made with and defaults to SHA512, so pass-H SHA256for a SHA-256 digest. signing-tool refuses a digest longer than the hash it sends, and warns when-Hdoes not match the digest length.- RSA keys need a digest of exactly the hash length: 32, 48 or 64 bytes.
- PSS salt: the hash length unless
--salt-length Ngives N bytes (--salt-length hashsays so explicitly). The salt can be at mostkey size in bytes - hash length - 2, so RSA-1024 with SHA-512 needs--salt-length 62or less. ESP32, MCUboot, JWSPS*, AHAB and TF-A expect a salt of the hash length, which is the default. RAW_LEis for verifiers that read each half ofr‖slittle-endian. The platform returnsr‖s;imagesigning get -Oconverts it, using the payload metadata entrysigningtool.signatureEncoding=RAW_LEthataddrecords.- The REST API takes the same options as one property per
optionalParametersitem, for example{"hashAlgorithm": "SHA384"},{"signaturePadding": "PSS"},{"saltLength": 32},{"signatureEncoding": "RAW"}. An empty value counts as not set, as{"saltLength": 0}does.
Post-quantum: ML-DSA
ML-DSA keys sign the message itself (-H NONE, -p is the message, base64). In a CMS
(--format CMS -H SHA512) they sign a SHA-512 digest; for kernel modules use
--cms-preset kernel-module-mldsa. See Supported keys.
ESP32 Secure Boot V2 and MCUboot (RSA-3072/2048, PSS, SHA-256, 32-byte salt):
DIGEST=$(sha256sum image.bin | cut -d' ' -f1 | xxd -r -p | base64 -w0)
signing-tool -n test.ini imagesigning add DigestSigning -P <product-uuid> --operid <operation-uuid> \
-p "$DIGEST" -H SHA256 --padding PSS -N "fw-1.4.0" -D "firmware 1.4.0"
signing-tool -n test.ini imagesigning get -I <request-uuid> --wait -O image.sig
Which CMS do I get?¶
--format CMS asks for a CMS (RFC 5652) instead of a raw signature. The first matching row applies.
| What you send | Operation | What you get |
|---|---|---|
a --cms-* setting without --format CMS |
any | refused when the request is sent (signing-tool already refuses a --cms-preset) |
no --format, or --format PKCS1 |
any digest operation | raw signature |
--format CMS --padding PSS, no --cms-* setting |
any digest operation | EC keys: a raw DER ECDSA signature; RSA keys: Failed (unsupportedSignaturePadding). Add a --cms-* setting for a CMS |
--format CMS and any --cms-* setting, a --cms-preset included |
any digest operation | a CMS built from your settings |
--format CMS, no --cms-* setting |
SignHABCSF, SignHABIMG, SignAHAB, DetachedSignature | the CMS these operations have always returned |
--format CMS, no --cms-* setting |
DigestSigning, SignOCI | a CMS that names the signer by key ID and embeds no certificate |
--format takes CMS exactly; any other value is sent as PKCS1, and another case such as --format cms also
prints a warning.
Where the certificate comes from¶
A DigestSigning key has no platform certificate. Export its public key, have your CA certify it, and pass the
signer certificate, plus the intermediates where your verifier accepts them, with --cms-cert. HAB and
DetachedSignature operations already have a platform certificate chain.
signing-tool -n test.ini product getpubkey -P <product-uuid> --operid <operation-uuid> -O op.pub
printf '%s\n' 'basicConstraints=CA:FALSE' 'keyUsage=critical,digitalSignature' \
'extendedKeyUsage=emailProtection' 'subjectKeyIdentifier=hash' 'authorityKeyIdentifier=keyid' > signer.ext
openssl x509 -new -subj "/CN=Firmware signing" -force_pubkey op.pub \
-CA issuing-ca.pem -CAkey issuing-ca.key -days 730 -extfile signer.ext -out signer.pem
cat signer.pem issuing-ca.pem > chain.pem
This needs OpenSSL 3.0 or later; set -days by your CA's policy. issuing-ca.pem is the CA that signs the
certificate; append further intermediates to chain.pem if your root is further up. Do not include the root: the
verifier holds it as its trust anchor. subjectKeyIdentifier=hash lets a verifier find this certificate when a
CMS names the signer by key ID.
--cms-cert takes PEM CERTIFICATE blocks or a PKCS7 bundle, is repeatable, and refuses a file that holds a
private key. The certificate whose public key is the operation's key is the signer; if none matches, the request
fails with invalidCMSCertificates.
CMS settings¶
| signing-tool | REST property | Values | Default |
|---|---|---|---|
--cms-cert FILE |
cmsCertificates |
PEM certificates or a PKCS7 bundle, base64 | none |
--cms-signed-attributes |
cmsSignedAttributes |
STANDARD (contentType, signingTime, messageDigest), NO_SIGNING_TIME, NONE |
STANDARD |
--cms-extra-attributes FILE |
cmsExtraSignedAttributes |
a DER SET OF Attribute added to the signed attributes |
none |
--cms-signer-id |
cmsSignerIdentifier |
ISSUER_AND_SERIAL, SUBJECT_KEY_ID |
issuer and serial when a signer certificate is known, else key ID |
--cms-embed |
cmsEmbedCertificates |
CALLER (your --cms-cert certificates), ALL (yours and the operation chain), SIGNER, NONE |
CALLER with --cms-cert, else NONE |
--cms-content-type OID |
cmsContentType |
dotted OID | 1.2.840.113549.1.7.1 (id-data) |
--cms-content FILE |
cmsContent |
content to encapsulate; -p must be its digest |
detached |
NONEsigned attributes: the key signs the digest directly. It cannot be combined with--cms-content, extra attributes or another content type.- A key ID is the certificate's SubjectKeyIdentifier extension. Without a certificate it is the SHA-1 of the
public key bit string (RFC 5280 method 1), which is what
subjectKeyIdentifier=hashproduces. - Extra attributes are single-valued and may not repeat contentType, signingTime or messageDigest.
--encoding RAWandRAW_LEdo not combine with--format CMS.- With an RSA key,
--padding PSSgives an RSASSA-PSS CMS signer; EC keys ignore it. - ML-DSA keys:
-H SHA512and signed attributes (STANDARDorNO_SIGNING_TIME);--padding PSSis refused.
A preset fills these settings for a consumer; settings you give yourself win. Every preset sends at least one setting, so a request with a preset always gets a CMS built from your settings.
| Preset | Settings |
|---|---|
swupdate |
--cms-signed-attributes STANDARD (the default value); with --cms-cert, issuer and serial, your certificates embedded |
swupdate-mbedtls |
--cms-embed SIGNER |
rauc |
--cms-signed-attributes STANDARD (the default value) |
rauc-verity |
as rauc; needs --cms-content <manifest> |
cst-hab4 |
--cms-embed NONE --cms-signer-id ISSUER_AND_SERIAL (on DigestSigning it needs --cms-cert; on a HAB operation the chain's leaf is the signer) |
kernel-module |
--cms-signed-attributes NONE --cms-embed NONE (RSA, ECDSA) |
kernel-module-mldsa |
--cms-signed-attributes NO_SIGNING_TIME --cms-embed NONE (ML-DSA; -H SHA512) |
uefi |
--cms-signed-attributes NONE --cms-embed CALLER (needs --cms-cert) |
Recipes¶
Each recipe uses one DigestSigning operation unless it says otherwise, and the signer.pem / chain.pem from
Where the certificate comes from. root.pem is your root CA.
SWUpdate with OpenSSL¶
signing-tool -n test.ini imagesigning add DigestSigning -P <product-uuid> --operid <operation-uuid> \
-N swu -D "sw-description 1.4.0" \
-p "$(openssl dgst -sha256 -binary sw-description | base64)" -H SHA256 \
--format CMS --cms-preset swupdate --cms-cert chain.pem
signing-tool -n test.ini imagesigning get -I <request-uuid> --wait -O sw-description.sig --bundle swu.json
openssl cms -verify -binary -inform DER -in sw-description.sig -content sw-description \
-CAfile root.pem -purpose smimesign -out /dev/null
CMS Verification successful means SWUpdate (built with CONFIG_SIGALG_CMS, started with -k root.pem)
accepts it. SWUpdate requires keyUsage=digitalSignature and extendedKeyUsage=emailProtection by default; for
a codeSigning certificate set cert-purpose in swupdate.cfg. --cms-preset rauc gives the same CMS for a
RAUC plain bundle.
SWUpdate with mbedTLS¶
SWUpdate 2026.05 or later built with mbedTLS 3.4 or later reads at most one certificate from the CMS, so embed only the signer and give SWUpdate the root and the intermediates:
signing-tool -n test.ini imagesigning add DigestSigning -P <product-uuid> --operid <operation-uuid> \
-N swu -D "sw-description 1.4.0" \
-p "$(openssl dgst -sha256 -binary sw-description | base64)" -H SHA256 \
--format CMS --cms-preset swupdate-mbedtls --cms-cert signer.pem
signing-tool -n test.ini imagesigning get -I <request-uuid> --wait -O sw-description.sig
cat root.pem issuing-ca.pem > root-and-intermediates.pem # swupdate -k root-and-intermediates.pem
Use RSA PKCS #1 v1.5 or ECDSA; the mbedTLS verifier does not take RSA-PSS.
RAUC verity and crypt bundles¶
A verity or crypt bundle's signature is a CMS that encapsulates the manifest. Build the bundle with a throwaway key, take its manifest, sign that, and put the new signature in:
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -subj "/CN=throwaway" \
-keyout throwaway.key -out throwaway.pem -days 1
rauc bundle --cert=throwaway.pem --key=throwaway.key bundle-dir/ unsigned.raucb
rauc extract-signature --keyring=throwaway.pem unsigned.raucb throwaway.sig
openssl cms -verify -noverify -binary -inform DER -in throwaway.sig -out manifest.raucm
signing-tool -n test.ini imagesigning add DigestSigning -P <product-uuid> --operid <operation-uuid> \
-N rauc -D "bundle 1.4.0" \
-p "$(openssl dgst -sha256 -binary manifest.raucm | base64)" -H SHA256 \
--format CMS --cms-preset rauc-verity --cms-cert chain.pem --cms-content manifest.raucm
signing-tool -n test.ini imagesigning get -I <request-uuid> --wait -O bundle.sig
rauc replace-signature --keyring=throwaway.pem --signing-keyring=root.pem unsigned.raucb bundle.sig bundle.raucb
rauc info --keyring=root.pem bundle.raucb
bundle-dir/manifest.raucm must set format=verity (or crypt). If the target sets use-bundle-signing-time,
keep the default STANDARD signed attributes, which carry the signing time.
NXP HAB4 with your own key¶
When your SRK CA certified a DigestSigning key, the cst-hab4 preset gives the CMS layout NXP CST produces:
signed attributes, signer by issuer and serial, no certificates.
signing-tool -n test.ini imagesigning add DigestSigning -P <product-uuid> --operid <operation-uuid> \
-N csf -D "CSF 1.4.0" -p "$(openssl dgst -sha256 -binary csf-data.bin | base64)" -H SHA256 \
--format CMS --cms-preset cst-hab4 --cms-cert csf.pem
signing-tool -n test.ini imagesigning get -I <request-uuid> --wait -O csf-data.sig
csf.pem is the CSF (or IMG) certificate your SRK CA issued for the operation's key. The device's HAB ROM
verifies the result.
NXP HAB4 operations with other settings¶
SignHABCSF and SignHABIMG return the CMS they always have. Any --cms-* setting gives a CMS built from your
settings, signed with the operation's chain leaf:
signing-tool -n test.ini imagesigning add SignHABCSF -P <product-uuid> --operid <operation-uuid> \
-N csf -D "CSF 1.4.0" -p "$(openssl dgst -sha256 -binary csf-data.bin | base64)" -H SHA256 \
--format CMS --cms-signed-attributes NO_SIGNING_TIME
Linux kernel modules¶
imagesigning envelope kernel-module signs and appends in one step. For a build that appends
the signature itself, the kernel-module preset returns the bare CMS for scripts/sign-file -s:
signing-tool -n test.ini imagesigning add DigestSigning -P <product-uuid> --operid <operation-uuid> \
-N module -D "my_module 1.4.0" -p "$(openssl dgst -sha512 -binary my_module.ko | base64)" -H SHA512 \
--format CMS --cms-preset kernel-module --cms-cert signer.pem
signing-tool -n test.ini imagesigning get -I <request-uuid> --wait -O my_module.p7s
scripts/sign-file -s my_module.p7s sha512 signer.pem my_module.ko
signer.pem is the certificate the kernel trusts for the key (built in with CONFIG_SYSTEM_TRUSTED_KEYS, or in
the secondary keyring). Without --cms-cert the CMS names the signer by key ID, which the kernel matches only
against a certificate made with subjectKeyIdentifier=hash. ML-DSA keys use --cms-preset kernel-module-mldsa.
UEFI authenticated variables¶
cert-to-efi-sig-list -g "$(uuidgen)" db.pem db.esl
sign-efi-sig-list -o -t "2026-10-07 12:00:00" db db.esl db.tbs
signing-tool -n test.ini imagesigning add DigestSigning -P <product-uuid> --operid <operation-uuid> \
-N db -D "db update" -p "$(openssl dgst -sha256 -binary db.tbs | base64)" -H SHA256 \
--format CMS --cms-preset uefi --cms-cert KEK.pem
signing-tool -n test.ini imagesigning get -I <request-uuid> --wait -O db.p7
openssl pkcs7 -inform DER -in db.p7 -out db.p7.pem
sign-efi-sig-list -i db.p7.pem -t "2026-10-07 12:00:00" db db.esl db.auth
KEK.pem is the KEK certificate for the operation's key. Use the same -t timestamp in both
sign-efi-sig-list calls. The firmware verifies db.auth when you write the variable.
CAdES-style signing certificate attribute¶
Some verifiers want the ESS signingCertificateV2 attribute. Compute it from the signer certificate and pass it
as an extra signed attribute (needs Python with asn1crypto):
python3 - signer.pem ess.der <<'EOF'
import hashlib
import sys
from asn1crypto import cms, pem, tsp, x509
_, _, der = pem.unarmor(open(sys.argv[1], "rb").read())
cert = x509.Certificate.load(der)
v2 = tsp.SigningCertificateV2({"certs": [{
"cert_hash": hashlib.sha256(der).digest(),
"issuer_serial": {"issuer": [x509.GeneralName({"directory_name": cert.issuer})],
"serial_number": cert.serial_number}}]})
open(sys.argv[2], "wb").write(cms.CMSAttributes([{"type": "signing_certificate_v2", "values": [v2]}]).dump())
EOF
signing-tool -n test.ini imagesigning add DigestSigning -P <product-uuid> --operid <operation-uuid> \
-N doc -D "document 1.4.0" -p "$(openssl dgst -sha256 -binary document.pdf | base64)" -H SHA256 \
--format CMS --cms-cert chain.pem --cms-extra-attributes ess.der
signing-tool -n test.ini imagesigning get -I <request-uuid> --wait -O document.p7s
openssl cms -verify -cades -binary -inform DER -in document.p7s -content document.pdf -CAfile root.pem \
-purpose any -out /dev/null
Signed by this key¶
Without any --cms-* setting a DigestSigning --format CMS request returns a CMS that names the signer by key
ID and embeds nothing. --bundle records that signing-tool verified it against the operation's public key:
signing-tool -n test.ini imagesigning add DigestSigning -P <product-uuid> --operid <operation-uuid> \
-N data -D "data 1.4.0" -p "$(openssl dgst -sha256 -binary data.bin | base64)" -H SHA256 --format CMS
signing-tool -n test.ini imagesigning get -I <request-uuid> --wait -O data.p7s --bundle data.json
openssl cms -verify -binary -inform DER -in data.p7s -content data.bin -certfile chain.pem -CAfile root.pem \
-purpose any -out /dev/null
The OpenSSL check finds the signer in chain.pem by key ID, so the signer certificate must be made with subjectKeyIdentifier=hash.
Envelopes¶
imagesigning envelope <kind> builds the bytes to be signed from your local files, sends only their digests,
puts every -F into one request with one approval, and verifies each result against the operation's public key
before it writes anything. Envelopes need a DigestSigning operation.
imagesigning envelope <kind> -P <product-uuid> --operid <operation-uuid> --state-dir DIR
-F <input> [-F ...] -O <output> [kind options] [--bundle FILE]
- With one
-F,-Ois the output file. With several (kernel-moduleandima),-Ois a directory and each output keeps its input's path below the inputs' common directory. --state-dirrecords a pending request, so running the same command again resumes it instead of asking for a second approval. A changed input or option starts a new request.--approver-token @FILEapproves the request in the same run, and--wait-timeoutsets how long to wait, as inimagesigning sign.- If the request fails, signing-tool lists each input that was not signed and the request's
errorStatus, and writes no output. - Approvers see
signingtool.envelope=<kind>and the input file name in each payload's metadata. - The JWS, COSE_Sign1, DSSE and Mender recipes verify with
op.pubfromproduct getpubkey; the kernel module and IMA recipes verify with the certificate the device trusts.
| Kind | Signs | Keys | Output |
|---|---|---|---|
jws |
BASE64URL(header).BASE64URL(payload) (RFC 7515) |
--alg RS256/384/512, PS256/384/512, ES256/384/512 |
compact JWS |
cose-sign1 |
the COSE Sig_structure (RFC 9052) |
--alg ES256/384/512, PS256/384/512 |
COSE_Sign1 |
dsse |
the DSSE pre-authentication encoding | RSA (--padding PKCS_v1_5 or PSS), EC P-256, P-384, P-521 |
DSSE JSON envelope |
kernel-module |
the module | RSA PKCS #1 v1.5, ECDSA, ML-DSA | the signed module |
ima |
the file's hash | RSA PKCS #1 v1.5, ECDSA | security.ima values and ima.xattrs |
mender |
the Artifact's manifest |
RSA PKCS #1 v1.5, ECDSA P-256 | manifest.sig |
JWS¶
signing-tool -n test.ini imagesigning envelope jws -P <product-uuid> --operid <operation-uuid> --state-dir .sign \
-F manifest.json -O manifest.jws --alg ES256 --kid release-2026
python3 -c 'from jwcrypto import jwk, jws; t = jws.JWS(); t.deserialize(open("manifest.jws").read()); t.verify(jwk.JWK.from_pem(open("op.pub", "rb").read()), alg="ES256"); print("JWS verified")'
--omit-payload leaves the payload out of the output (RFC 7515 appendix F); give it to the verifier separately.
COSE_Sign1¶
pip install 'signing-tool[cose]'
signing-tool -n test.ini imagesigning envelope cose-sign1 -P <product-uuid> --operid <operation-uuid> \
--state-dir .sign -F payload.bin -O payload.cose --alg ES256 --kid release-2026
pip install pycose
python3 -c 'from pycose.keys import CoseKey; from pycose.messages import CoseMessage; m = CoseMessage.decode(open("payload.cose", "rb").read()); m.key = CoseKey.from_pem_public_key(open("op.pub").read()); print("COSE verified" if m.verify_signature() else "COSE FAILED")'
--external-aad FILE adds external additional authenticated data; --omit-payload leaves the payload out.
DSSE¶
signing-tool -n test.ini imagesigning envelope dsse -P <product-uuid> --operid <operation-uuid> --state-dir .sign \
-F statement.json -O statement.dsse.json --payload-type application/vnd.in-toto+json --keyid release-2026
python3 -c 'import json; from cryptography.hazmat.primitives.serialization import load_pem_public_key; from securesystemslib.dsse import Envelope; from securesystemslib.signer import SSlibKey; key = SSlibKey.from_crypto(load_pem_public_key(open("op.pub", "rb").read()), keyid="release-2026"); Envelope.from_dict(json.load(open("statement.dsse.json"))).verify([key], 1); print("DSSE verified")'
The check needs securesystemslib. The example is for an EC P-256 key. For RSA, --padding PSS matches in-toto and
the default PKCS #1 v1.5 matches sigstore; pass the same scheme to SSlibKey.from_crypto (rsassa-pss-sha256 or rsa-pkcs1v15-sha256).
Kernel module¶
signing-tool -n test.ini imagesigning envelope kernel-module -P <product-uuid> --operid <operation-uuid> \
--state-dir .sign -F drivers/a.ko -F drivers/net/b.ko -O signed/ --cms-cert signer.pem
perl scripts/extract-module-sig.pl -0 signed/a.ko > a.unsigned.ko
perl scripts/extract-module-sig.pl -s signed/a.ko > a.p7s
openssl cms -verify -binary -inform DER -in a.p7s -content a.unsigned.ko -certfile chain.pem -CAfile root.pem \
-purpose any -out /dev/null
The outputs are signed/a.ko and signed/net/b.ko; scripts/extract-module-sig.pl is in the kernel source
tree. The kernel loads the module when it trusts signer.pem (see
Linux kernel modules). -H selects SHA512 (default), SHA384 or SHA256;
--cms-signer-id SUBJECT_KEY_ID names the signer by key ID instead of issuer and serial. ML-DSA keys need
-H SHA512.
IMA¶
signing-tool -n test.ini imagesigning envelope ima -P <product-uuid> --operid <operation-uuid> --state-dir .sign \
-F rootfs/usr/bin/app -F rootfs/usr/lib/libapp.so -O ima/
cp ima/bin/app.sig rootfs/usr/bin/app.sig
openssl x509 -in signer.pem -outform DER -out signer.der
evmctl ima_verify --key signer.der --sigfile rootfs/usr/bin/app
(cd rootfs/usr && setfattr --restore=../../ima/ima.xattrs)
Each input gets <path>.sig with its security.ima value; ima.xattrs sets them all with setfattr --restore,
run from the inputs' common directory (here rootfs/usr). The key ID in the signature is taken from the public
key as subjectKeyIdentifier=hash computes it, so the kernel's .ima keyring needs a certificate made that way;
--keyid sets another. -H selects SHA256 (default), SHA384 or SHA512.
Mender¶
tar -xf unsigned.mender manifest
signing-tool -n test.ini imagesigning envelope mender -P <product-uuid> --operid <operation-uuid> \
--state-dir .sign -F manifest -O manifest.sig
mkdir artifact && tar -C artifact -xf unsigned.mender
cp manifest.sig artifact/
tar -C artifact --format=ustar -cf signed.mender $(tar -tf unsigned.mender | sed '/^manifest$/a manifest.sig')
mender-artifact validate -k op.pub signed.mender
The tar call keeps the Artifact's member order and puts manifest.sig directly after manifest. Mender takes
RSA (PKCS #1 v1.5, SHA-256) and ECDSA P-256.
The result bundle¶
--bundle FILE on imagesigning get and imagesigning envelope writes a record of the request. FILE.json is
the record alone, with hashes instead of your files. FILE.zip or FILE.tar.gz holds the record as
request.json and the results themselves: for get, each payload's result under payloads/<n>/, as -O
writes it; for envelope, only the outputs, under outputs/. --json prints the record.
An abbreviated record from the SWUpdate recipe:
{"bundle": {"signingTool": "3.11.0", "api": "https://<api>", "time": "2026-10-07T12:00:00Z"},
"request": {"id": "...", "name": "swu", "description": "sw-description 1.4.0", "state": "Ready"},
"payloads": [{"state": "Ready",
"storedOptionalParameters": [
{"type": "cmsCertificates", "sha256": "9f...", "size": 2412},
{"type": "cmsSignedAttributes", "value": "STANDARD"},
{"type": "hashAlgorithm", "value": "SHA256"},
{"type": "signatureFormat", "value": "CMS"},
{"type": "signaturePadding", "value": "PKCS_v1_5"}],
"result": {"file": "sw-description.sig", "sha256": "3c...", "size": 1874}}],
"operationPublicKey": {"pem": "-----BEGIN PUBLIC KEY-----...", "sha256": "..."},
"verification": {"method": "certificate", "certificateSha256": "...", "publicKeySha256": "...",
"checked": "payloads/1/sw-description.sig: CMS signature verified against operationPublicKey; messageDigest is the submitted digest",
"repeat": "openssl cms -verify -binary -inform DER -in payloads/1/sw-description.sig -content <input> -CAfile <root.pem>"}}
verification.methodiscertificatewhen the results embed one signer certificate,publicKeywhen signing-tool knows the signer's public key but no certificate, andnonewhen it knows neither.publicKeySha256(andcertificateSha256forcertificate) binds the record to one signer key.checkedis what signing-tool verified: one line per payload forget, one line for an envelope.repeatis written only forcertificate: a command for your own trust chain, where<root.pem>is your CA and<input>the file you hashed.- Every payload that can be checked must verify against the same key, or no bundle is written. A payload that
cannot be checked is labelled "not verified" in
checked: a raw signature or a CMS without signed attributes (getholds only the digest's SHA-256, not the digest), a CMS from an operation without a public key of its own that embeds no certificate, and the raw DER ECDSA signature that--format CMS --padding PSSwithout a--cms-*setting returns, which-Ostill writes. A CMS that does not verify writes no bundle. - The record holds hashes instead of your files, certificates, encapsulated content and secrets, never holds a
token and does not name approvers. A
.zipor.tar.gzbundle carries the results, and for envelopes the outputs:kernel-module,dsseand attachedjwsandcose-sign1outputs contain the input. - For an operation without a public key of its own (HAB, DetachedSignature),
operationPublicKeyis the embedded signer certificate's key, or{"unavailable": "chain operation"}.
Verifying a raw signature¶
signing-tool -n test.ini product getpubkey -P <product-uuid> --operid <operation-uuid> -O op.pub
printf '%s' "$DIGEST" | base64 -d > digest.bin
# RSA PKCS #1 v1.5
openssl pkeyutl -verify -pubin -inkey op.pub -in digest.bin -sigfile image.sig -pkeyopt digest:sha256
# RSA-PSS (salt = hash length; use rsa_pss_saltlen:<N> for --salt-length N)
openssl pkeyutl -verify -pubin -inkey op.pub -in digest.bin -sigfile image.sig \
-pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pss -pkeyopt rsa_pss_saltlen:-1
# ECDSA DER
openssl pkeyutl -verify -pubin -inkey op.pub -in digest.bin -sigfile image.sig -pkeyopt digest:sha256
A raw r‖s signature verifies the same way after converting it to DER, for example with Python's
cryptography.hazmat.primitives.asymmetric.utils.encode_dss_signature.
Errors¶
signing-tool refuses some command lines itself, before anything is sent (exit 2): a digest longer than the
hash it sends, a --cms-cert file that holds a private key or no CERTIFICATE or PKCS7 block, a
--cms-preset without --format CMS, a preset without a setting it needs (rauc-verity without
--cms-content), an empty --cms-content or --cms-extra-attributes file (--cms-content <file>: the file is
empty, --cms-extra-attributes <file>: the file is empty), and an empty --cms-* value (<flag> needs a
value).
The platform refuses the rest of what it can check without the key when the request is sent, with HTTP 400 and
before anyone approves it. The message names the payload (payload. or payloads[<n>].), for example:
ERROR: HTTP 400 Bad Request: payload.optionalParameters: saltLength needs signaturePadding PSS
| What you send | Message after payload.optionalParameters: |
|---|---|
a --cms-* setting without --format CMS |
<setting> needs signatureFormat CMS |
--cms-*, --salt-length or --encoding on an operation that does not sign digests |
<setting> applies only to DetachedSignature, DigestSigning, SignAHAB, SignHABCSF, SignHABIMG and SignOCI, not <operation type> |
--salt-length without --padding PSS |
saltLength needs signaturePadding PSS |
--encoding with -H NONE |
signatureEncoding cannot be used with hashAlgorithm NONE |
--encoding RAW or RAW_LE with --format CMS |
signatureEncoding RAW cannot be used with signatureFormat CMS |
--cms-embed CALLER without --cms-cert |
cmsEmbedCertificates CALLER needs cmsCertificates |
DigestSigning or SignOCI without --cms-cert, with --cms-signer-id ISSUER_AND_SERIAL |
DigestSigning keys have no certificate: cmsSignerIdentifier ISSUER_AND_SERIAL needs cmsCertificates |
DigestSigning or SignOCI without --cms-cert, with --cms-embed SIGNER or ALL |
DigestSigning keys have no certificate: cmsEmbedCertificates SIGNER needs cmsCertificates |
--cms-signed-attributes NONE with --cms-content |
cmsSignedAttributes NONE cannot be used with cmsContent |
--cms-signed-attributes NONE with --cms-extra-attributes |
cmsSignedAttributes NONE cannot be used with cmsExtraSignedAttributes |
--cms-signed-attributes NONE with another --cms-content-type |
cmsSignedAttributes NONE needs cmsContentType 1.2.840.113549.1.7.1 |
a --cms-cert file the platform cannot read |
a message that starts with cmsCertificates and names the problem |
a --cms-extra-attributes file that is not a DER SET OF Attribute, a primitive SET included |
cmsExtraSignedAttributes is not a DER SET OF Attribute |
| an extra attribute with more than one value, given twice, or one of contentType, signingTime and messageDigest | cmsExtraSignedAttributes attribute <OID> must have exactly one value, ... appears twice, ... is set by the service |
a --cms-content-type that is not a dotted OID |
cmsContentType "<value>" is not a dotted OID |
| a setting larger than the platform stores | a message that names the setting and the limit |
The operation type and the setting names in a message are the ones of your request. In the REST API a new
property (saltLength, signatureEncoding, cms*) must be the only property of its optionalParameters item
(payload.optionalParameters[<n>]: <property> must be the only property of its item), and in a payload that uses
a new property or SHA384 the same parameter twice with different values is refused
(payload.optionalParameters set <parameter> twice with different values, followed by the two values except for
cmsCertificates, cmsExtraSignedAttributes and cmsContent).
A DigestSigning payload is checked before the request goes for approval: it must be uploaded, be base64 and not
be empty, and for an RSA or ML-DSA key decode to exactly the digest of the hash algorithm. A payload that fails
ends the request in state Failed without an approval, with invalidDigestSize or payloadIsNotBase64Encoded and
the fault, naming the payloads entry, in errorDetail. An EC key signs the digest it is given, so a shorter
digest is accepted.
A request that fails after approval ends in state Failed with errorStatus and errorDetail:
| errorStatus | Cause |
|---|---|
invalidDigestSize |
an RSA digest that is not exactly the hash length; in a CMS built from settings, or an RSA CMS, a digest that is not the hash length |
unsupportedSignaturePadding |
a salt longer than the key allows (the detail names the bound); --padding PSS --salt-length with an EC key (saltLength needs an RSA key with PSS); --format CMS --padding PSS with an RSA key and no --cms-* setting; padding NONE with a CMS |
invalidCMSCertificates |
no --cms-cert certificate matches the operation's key, the chain's leaf does not, or SUBJECT_KEY_ID with a signer certificate that has no SubjectKeyIdentifier |
invalidCMSSettings |
--cms-content whose hash is not the digest; ML-DSA with PSS or without -H SHA512; ML-DSA with --cms-preset kernel-module or --cms-signed-attributes NONE (ML-DSA CMS needs signed attributes: without them the key signs the content, which the service never receives); a CMS larger than the platform stores (the detail names the limit) |
payloadIsNotBase64Encoded |
a digest that is not base64 in a request for a CMS built from settings, including DigestSigning and SignOCI --format CMS without a --cms-* setting |
keyNotSuitableForSigning |
--encoding with an RSA key (signatureEncoding needs an ECDSA key) |
emptyCMSTree |
a HAB or DetachedSignature operation without a certificate chain, given --cms-* settings but no --cms-cert |
Changes from earlier behaviour¶
signing-tool 3.11.0 and the platform update change these cases; everything else behaves as before.
-Hin another case (-H sha256) is sent as that algorithm when the digest has its length. Earlier versions sent SHA512, and RSA signatures and EC CMS results of those requests did not verify.- A digest longer than the hash signing-tool sends is refused before it is sent; the platform refused it after approval.
--format cmsin another case is still sent as PKCS1, now with a warning.- DigestSigning and SignOCI with
--format CMSreturn a CMS; these requests failed before. - RSA-PSS with HSM keys works, with a salt of the hash length unless you set one.
--format CMS --padding PSSwithout a--cms-*setting fails for RSA keys withunsupportedSignaturePaddingand a hint to add a setting; EC keys return the raw DER ECDSA signature as before.- An RSA digest shorter than the hash fails with
invalidDigestSize; the platform used to return a signature that never verified. - Failed requests report the specific reason in
errorStatusinstead ofdefaultErrorStatus. - REST: in a payload that uses a new property or
SHA384, the same parameter twice with different values is refused; the platform used one of them. A new property sharing anoptionalParametersitem with another property is refused; it was ignored. imagesigning get -Oon a request with several payloads fails and suggests--bundle.
See also the signing overview and MCUboot signing, where the platform signs the whole image instead.