Skip to content

RAUC signing

RAUC (Robust Auto-Update Controller) signing enables secure software updates by generating and managing cryptographic keys and certificates within a Public Key Infrastructure (PKI). This section outlines the steps to configure a product to support RAUC signing, including setting up the necessary PKI hierarchy and enabling the RAUC bundle signing operation with ephemeral keys and certificates. Using unique, one-time certificates for each bundle enhances security by allowing individual bundles to be revoked if needed.

Prerequisites

Configuring RAUC Signing

To enable RAUC signing, you must create a product with a PKI hierarchy that supports the generation of a Certificate Authority (CA) and ephemeral signing keys. The RAUC bundles are signed using unique, one-time keys and certificates generated on demand, ensuring secure and revocable updates.

Step-by-Step Configuration

  1. Create a Product with RAUC Signing Support:

    • Configure a product that includes a PKI hierarchy for RAUC signing and enables the RAUC bundle signing operation.
    • Ensure the product is set up to use the RaucBundleSigningWithOneTimeKey operation, which generates ephemeral keys and certificates for each signing operation.
  2. Set Up the PKI Hierarchy:

    • During product creation, a PKI tree is generated, including a Certificate Authority (CA) stored in the HSM.
    • The CA issues short-lived certificates for signing RAUC bundles, with each certificate tied to a unique ephemeral key.
  3. Use RAUC Bundle Signing:

    • The ephemeral key is generated in the HSM, used for signing, and then discarded to enhance security.

Key Features of RAUC Signing

  • Ephemeral Keys and Certificates: Each RAUC bundle is signed with a unique, one-time key and certificate, generated on demand and discarded after use.
  • HSM Security: All cryptographic operations occur within the HSM's secure boundary, ensuring key protection and integrity.
  • Revocation Capability: Using unique certificates for each bundle allows for selective revocation of compromised or outdated bundles.

Best Practices

  • Use Unique Certificate Names: Opt for a UUID-based Common Name (CN) for certificates to ensure uniqueness and traceability.
  • Validate Configuration: Verify that the HSM token and profile are correctly configured to support RAUC signing operations.
  • Document Configuration Details: Maintain detailed records of the product and PKI configurations for troubleshooting and auditing purposes.

Next Steps

Input data for RAUC signing

The input is a tar.gz file containing a request.json file data directory containing the files to be signed or previously signed package. The request.json is used to inform what kind of payload there is in the tar package and how to process that. Please make sure the format field in the request.json matches the RAUC manifest format field.

Plain bundling and resign cases

Plain bundle request.json is as follows

request.json:

{
 "format": "plain",  <===format of the bundle: plain, verity
 "directory": "data", <===directory inside the tar package which contains the data to be signed/encrypted
 "mkSquashfsArgs": "-b 64k"  <===Optional params to mksquash. Remove entry if not needed. Used when creating the bundle
}

Example of plain payload

$ tar -tvf rauc-plain.tar.gz 
drwxrwxr-x user/user   0 2025-03-27 14:34 data/
-rw-rw-r-- user/user 65536 2025-03-27 14:10 data/rootfs.img
-rw-rw-r-- user/user 32768 2025-03-27 14:10 data/appfs.img
-rw-rw-r-- user/user  8192 2025-03-27 14:10 data/space-dummy
-rw-rw-r-- user/user   144 2025-03-27 15:39 data/manifest.raucm
-rw-rw-r-- user/user    52 2025-03-27 14:15 request.json

Plain resign request.json is as follows:

request.json:

{
 "format": "plain",  <===format of the bundle: plain, verity
 "resign": true, <===set to true if resign the package.  Then input file must be provided
 "inputFile": "update.raucb" <=== previously signed package
}

Example of plain resign payload

$ tar -tvf rauc-plain-resign.tar.gz 
drwxrwxr-x vagrant/vagrant   0 2025-05-22 14:52 ./
-rw-rw-r-- vagrant/vagrant 13763 2025-05-22 13:41 ./update.raucb
-rw-rw-r-- vagrant/vagrant    78 2025-05-22 14:52 ./request.json

Verity bundling and resign cases

verity bundle request.json is as follows

request.json:

{
 "format": "verity",  <===format of the bundle: plain, verity
 "directory": "data", <===directory inside the tar package which contains the data to be signed/encrypted
 "mkSquashfsArgs": "-b 64k"  <===Optional params to mksquash. Remove entry is not needed. Used when creating the bundle
}

Example of verity payload

$ tar -tvf rauc-verity.tar.gz 
drwxrwxr-x vagrant/vagrant   0 2025-03-27 14:34 data/
-rw-rw-r-- vagrant/vagrant 65536 2025-03-27 14:10 data/rootfs.img
-rw-rw-r-- vagrant/vagrant 32768 2025-03-27 14:10 data/appfs.img
-rw-rw-r-- vagrant/vagrant  8192 2025-03-27 14:10 data/space-dummy
-rw-rw-r-- vagrant/vagrant   144 2025-03-27 15:39 data/manifest.raucm
-rw-rw-r-- vagrant/vagrant    52 2025-03-27 14:15 request.json

verity resign request.json is as follows:

request.json:

{
 "format": "verity",  <===format of the bundle: plain, verity
 "resign": true, <===set to true if resign the package.  Then input file must be provided
 "inputFile": "update.raucb" <=== previously signed package
}

Example of verity resign payload

$ tar -tvf rauc-verity-resign.tar.gz 
drwxrwxr-x vagrant/vagrant   0 2025-05-22 14:52 ./
-rw-rw-r-- vagrant/vagrant 18350 2025-05-22 14:52 ./update.raucb
-rw-rw-r-- vagrant/vagrant    80 2025-05-22 14:53 ./request.json

Crypt bundling

crypt bundle request.json is as follows

request.json:

{
 "format": "crypt",  <===format of the bundle: plain, verity or crypt
 "directory": "data", <===directory inside the tar package which contains the data to be signed/encrypted
 "encryptionCertsFile": "certs.pem" <===file containing a list of recipient certificates in PEM format. Only used if format is crypt.
}

For crypt bundle the manifest.raucm needs to have the bundle format set to crypt. An example of manifest.raucm

[update]
compatible=Test Config
version=2011.03-2

[bundle]
format=crypt

[image.rootfs]
filename=rootfs.img

[image.appfs]
filename=appfs.img

Example of crypt payload

drwxrwxr-x vagrant/vagrant   0 2025-05-19 12:51 ./
-rw-rw-r-- vagrant/vagrant  89 2025-03-27 14:16 ./request.json
drwxrwxr-x vagrant/vagrant   0 2025-03-27 14:35 ./data/
-rw-rw-r-- vagrant/vagrant 143 2025-03-27 15:39 ./data/manifest.raucm
-rw-rw-r-- vagrant/vagrant 65536 2025-03-21 08:48 ./data/rootfs.img
-rw-rw-r-- vagrant/vagrant 32768 2025-03-21 08:48 ./data/appfs.img
-rw-rw-r-- vagrant/vagrant  8192 2025-03-21 08:48 ./data/space-dummy
-rw-rw-r-- vagrant/vagrant  1424 2025-07-21 11:42 ./test.pem

Crypt resign is not supported.

Output data

The output download is already in binary format. When verifying the system.conf must have a check-purpose=codesign entry in the keyring section. See example below.

Example response when checking with rauc tool the package

The signature is not verified:

$ rauc info -c testdata/system.conf --no-verify /tmp/tmpdownload.bin
rauc-Message: 11:50:09.604: Using central status file /tmp/rauc/central.raucs
rauc-Message: 11:50:09.605: Failed to load system status: No such file or directory
rauc-Message: 11:50:09.605: Reading bundle: /tmp/tmpdownload.bin
Compatible:     'Test Config'
Version:        '2011.03-2'
Description:    '(null)'
Build:          '(null)'
Hooks:          ''
Bundle Format:  verity
  Verity Salt:  'f7dcb7e174024a6e0f8e270e4135966343e1eacd27036272039e93ce054d2f1f'
  Verity Hash:  '5cb373656d5a2d931d5834dff0009287b59c9bb0114e0ca4422301bf18715c9f'
  Verity Size:  4096
Manifest Hash:  'f2b9334db73c0f5ef6dcfff5ceaa32e58be6801a290b12569450d6823fb5abe8'


2 Images:
  [rootfs]
    Filename:  rootfs.img
    Checksum:  de2f256064a0af797747c2b97505dc0b9f3df0de4f489eac731c23ae9ca9cc31
    Size:      65.5 kB (65,536 bytes)
    Hooks:     
  [appfs]
    Filename:  appfs.img
    Checksum:  c35020473aed1b4642cd726cad727b63fff2824ad68cedd7ffb73c7cbd890479
    Size:      32.8 kB (32,768 bytes)
    Hooks:     

Signature unverified

Example response with validation

The CA certificate used for verification can be obtained using the client functionality

(venv) $ ./signing-tool.py -c -t $TOKEN -a https://app.laavat.io/<CustomerName>/api/v1/ \
  secrets get -P 53a0a1da-3af9-4279-a62a-687d3b4f5c9b \
  -C client.private -O /tmp/prod.json
Full secret payload written to: /tmp/prod.json

Then extract the CA certificate chain

$ jq -r .caInfo[0].pkcs7chain < /tmp/prod.json | base64 -d \
| openssl pkcs7 -print_certs > /tmp/cachain.pem

Check the signature:

$ rauc info -c testdata/system.conf \
  --keyring=/tmp/cachain.pem  /tmp/signingresponse.bin
rauc-Message: 12:06:36.026: Using central status file /tmp/rauc/central.raucs
rauc-Message: 12:06:36.026: Failed to load system status: No such file or directory
rauc-Message: 12:06:36.026: Reading bundle: /tmp/rauctest.bin
rauc-Message: 12:06:36.027: Verifying bundle signature... 
rauc-Message: 12:06:36.028: Verified inline signature by 'C = FI, O = Test Comp, CN = 3adbce62-ded3-496c-bcb0-950bf0343915'
Compatible:     'Test Config'
Version:        '2011.03-2'
Description:    '(null)'
Build:          '(null)'
Hooks:          ''
Bundle Format:  verity
  Verity Salt:  'd759d02e534409c0eb935eb7a18cea501f5d353635aa376fbc596a2f49c7c4cb'
  Verity Hash:  'b8a2e2ac03c822b1762acbc6bf8cc717b19d71708d9b532959bf72e121917df4'
  Verity Size:  4096
Manifest Hash:  '4b018c8c8aa579afcc4bc577f97f407ba09585618b397101cdefa72a510f812c'


2 Images:
  [rootfs]
    Filename:  rootfs.img
    Checksum:  de2f256064a0af797747c2b97505dc0b9f3df0de4f489eac731c23ae9ca9cc31
    Size:      65.5 kB (65,536 bytes)
    Hooks:     
  [appfs]
    Filename:  appfs.img
    Checksum:  c35020473aed1b4642cd726cad727b63fff2824ad68cedd7ffb73c7cbd890479
    Size:      32.8 kB (32,768 bytes)
    Hooks:     

Certificate Chain:
 0 Subject: C = FI, O = Test Comp, CN = 3adbce62-ded3-496c-bcb0-950bf0343915
   Issuer: C = FI, CN = CA for One Time Signing certs
   SPKI sha256: CF:53:B1:1D:0B:6B:61:FA:CB:DA:AF:92:5E:A0:03:84:BB:A8:EA:28:52:AE:83:78:96:6C:4F:5F:E5:59:C3:C9
   Not Before: Sep 30 08:52:37 2025 GMT
   Not After:  Aug 13 08:40:57 2225 GMT
 1 Subject: C = FI, CN = CA for One Time Signing certs
   Issuer: C = FI, CN = CA for One Time Signing certs
   SPKI sha256: 92:78:D8:02:B2:DB:E7:B9:D8:63:11:73:99:C2:DF:41:8B:7D:2D:2B:10:95:C7:16:2D:1C:F9:F8:E2:9F:1D:20
   Not Before: Sep 30 08:40:57 2025 GMT
   Not After:  Aug 13 08:40:57 2225 GMT

The system.conf used in the testing.

Make sure the keyring section contains the check-purpose variable.

[system]
compatible=FooCorp Super BarBazzer
bootloader=barebox
data-directory=/tmp/rauc
bundle-formats=-plain

[keyring]
check-purpose=codesign

Example response for encrypted bundles with validation

The CA certificate used for verification can be obtained using the client functionality

For example:

(venv) $ ./signing-tool.py -c -t $TOKEN -a https://app.laavat.io/<CustomerName>/api/v1/ \
  secrets get -P 53a0a1da-3af9-4279-a62a-687d3b4f5c9b \
  -C client.private -O /tmp/prod.json
Full secret payload written to: /tmp/prod.json

Then extract the CA certificate chain

$ jq -r .caInfo[0].pkcs7chain < /tmp/prod.json | base64 -d \
| openssl pkcs7 -print_certs > /tmp/cachain.pem

private.pem contains the private key of one of the recipients. The public certificates were delivered as part of signingrequest. See encryptionCertsFile option in the request.json.

Decrypt and check the signature

$ rauc  info -c "testdata/system.conf" /tmp/tmpdownload.bin \
  --key=private.pem --keyring /tmp/cachain.pem 
rauc-Message: 12:10:39.607: Using central status file /tmp/rauc/central.raucs
rauc-Message: 12:10:39.607: Failed to load system status: No such file or directory
rauc-Message: 12:10:39.607: Reading bundle: /tmp/rauctestenc.bin
rauc-Message: 12:10:39.611: Decrypting signature...
rauc-Message: 12:10:39.612: Verifying bundle signature... 
rauc-Message: 12:10:39.613: Verified inline signature by 'C = FI, O = Test Comp, CN = f48fa4d2-ec87-45dd-abb0-df4fba62344a'
Compatible:     'Test Config'
Version:        '2011.03-2'
Description:    '(null)'
Build:          '(null)'
Hooks:          ''
Bundle Format:  crypt [encrypted CMS]
  Crypt Key:    '<hidden>'
  Verity Salt:  '9859076f5f9699e29e08293d4fe9ab5c4ecf6eb8860bafe0fc1ec6e0530d6d76'
  Verity Hash:  'fc35e33cfa5008354cd747f7c2815da9f7b5fe7311fb3bef06ce0d12ecf28295'
  Verity Size:  4096
Manifest Hash:  '22c7a5dd4a5679f08b06318be7eeeb06b3cd3d57b1a2cbc4b9ecb298aae3b34a'


2 Images:
  [rootfs]
    Filename:  rootfs.img
    Checksum:  de2f256064a0af797747c2b97505dc0b9f3df0de4f489eac731c23ae9ca9cc31
    Size:      65.5 kB (65,536 bytes)
    Hooks:     
  [appfs]
    Filename:  appfs.img
    Checksum:  c35020473aed1b4642cd726cad727b63fff2824ad68cedd7ffb73c7cbd890479
    Size:      32.8 kB (32,768 bytes)
    Hooks:     

Certificate Chain:
 0 Subject: C = FI, O = Test Comp, CN = f48fa4d2-ec87-45dd-abb0-df4fba62344a
   Issuer: C = FI, CN = CA for One Time Signing certs
   SPKI sha256: 74:2C:FE:17:DB:43:1B:5F:B1:26:71:8E:D7:13:7C:C2:74:E7:74:E5:61:3C:44:01:90:DC:FE:23:61:A4:3C:93
   Not Before: Sep 30 09:08:18 2025 GMT
   Not After:  Aug 13 08:40:57 2225 GMT
 1 Subject: C = FI, CN = CA for One Time Signing certs
   Issuer: C = FI, CN = CA for One Time Signing certs
   SPKI sha256: 92:78:D8:02:B2:DB:E7:B9:D8:63:11:73:99:C2:DF:41:8B:7D:2D:2B:10:95:C7:16:2D:1C:F9:F8:E2:9F:1D:20
   Not Before: Sep 30 08:40:57 2025 GMT
   Not After:  Aug 13 08:40:57 2225 GMT

Make sure the keyring contains the check-purpose variable

[system]
compatible=FooCorp Super BarBazzer
bootloader=barebox
data-directory=/tmp/rauc
bundle-formats=-plain

[keyring]
check-purpose=codesign

Example usage with reference client package: RAUC signing

RAUC signing examples ($TOKEN contains the "regular" user token and $APPROVERTOKEN contains a token for a user that's in the approvers group):

(venv) $./signing-tool.py -c -t $TOKEN -a https://app.laavat.io/<CustomerName>/api/v1 imagesigning add RaucBundleSigning \
    -N test -D test2 -P adcb30d8-f009-438e-b1b2-96f507b306cb \
    --operid 4add90e9-ffb3-4708-9554-ed2e82e8fd71 -F rauc-verity.tar.gz

# Approve the rauc signing request
(venv) $ ./signing-tool.py -c -t $APPROVERTOKEN -a https://app.laavat.io/<CustomerName>/api/v1 imagesigning \
    approve -I a83081a6-1d3b-4117-a81b-0ebcfcf0669c

# Get the signed payload
(venv) $ ./signing-tool.py -c -t $TOKEN -a https://app.laavat.io/<CustomerName>/api/v1 imagesigning \
    get -I a83081a6-1d3b-4117-a81b-0ebcfcf0669c -O /tmp/signed.rauc.bundle