Skip to content

Signing identities ​

A signing identity is the trust of one product: the root certificates its Pantavisor images trust, and the keys your builds sign apps (and the BSP) with. With one, a device configured for your identity installs only what you signed; without one, exports are signed with pvr's public developer key, which every development image accepts and anyone can use.

Create one under user menu → Signing (/settings/signing), with pvr signing create, or ask the Hub's MCP connector to (create_signing_identity).

Retiring an identity, uploading a key and certifying a key you keep ask you to confirm it is you: your password, or your second factor on accounts that sign in without one.

How a device decides ​

A Pantavisor image carries two bundles of root certificates:

File in the imageHoldsChecks
/etc/pantavisor/pvs/trust/ca-oem-certificates.crtyour OEM rootapp signatures whose certificate CN is the image's PV_OEM_NAME
/etc/pantavisor/pvs/trust/ca-certificates.crtyour platform root, or your BSP vendor'severything else: the BSP (bsp/**), the OEM configuration (<PV_OEM_NAME>/**) and signatures with any other CN

So an identity has up to two roots, and they must differ: with one root in both bundles an app key could also sign the BSP. Its name is the image's PV_OEM_NAME, and its app certificate has that name as CN.

What signs what ​

Every part of a revision is covered by a signature (_sigs/<part>.json), and the image's PV_SECUREBOOT_MODE decides what the device does with it:

Part of the revisionSigned byKeyChecked against
Your apps (<app>/**)your app builds<identity>-oem (CN = identity name)ca-oem-certificates.crt
The OEM configuration (<identity>/**)the build that adds the OEM part<identity>-platformca-certificates.crt
The BSP (bsp/**), the device skeleton and the image base's containersthe factory or image build<identity>-platformca-certificates.crt
Anything signed with pvr's developer keyany build without a signing keypublic, anyone has itonly images that keep the default trust

An OEM key can never sign bsp/** or <identity>/**: a device checks those against the platform bundle whatever the certificate says.

PV_SECUREBOOT_MODEThe device…
strictrefuses a revision with any unsigned key or any signature that does not verify
lenientrefuses signatures that do not verify; accepts unsigned keys
auditlogs what strict would refuse, installs anyway
disableddoes not check

Image builds run the same checks before writing an image, so a revision a strict device would refuse fails the build instead of the device. The build log lists each signature, its signer and the bundle it verified with:

text
Signature bsp by acme-platform, verified with ca-certificates.crt
Signature hello by acme, verified with ca-oem-certificates.crt

Two ways to get the keys ​

Generated by PantahubOwn certificates
Fortrying the feature, small fleetscompanies and advanced users with their own PKI
Rootsmade by the Hub; their keys stay sealed in the Hubyours; their keys never reach the Hub
Signing keysmade by the Hub, rotated with one clickissued by your CA, uploaded by you
What the Hub holdseverything, encryptedthe root certificates (public) and the signing keys you upload, encrypted

Either way, private keys are never shown again: not in the web app, not in pvr, not to an assistant over MCP. Builds get them, decrypted, only while they sign.

Generated by Pantahub ​

  1. Signing → New signing identity: a name (acme-gateway), Generated by Pantahub, and Also a platform root if you build your own images and want to sign their BSP too.
  2. The identity's keys appear among your secrets as acme-gateway-oem (and acme-gateway-platform), marked as belonging to the identity.
  3. Build with it: choose acme-gateway-oem as Signing key in the build form, or --signing-secret acme-gateway-oem / signing-secret: acme-gateway-oem in pvr and GitHub Actions. The build summary shows the export as signed and verified against the identity's root.
  4. Rotate gives a role a new key under the same root: images keep trusting it. Revoke removes the key; Retire revokes every key and destroys the root keys, for good. Each asks for your password first, and none is available over the MCP connector.

Signing on your machine ​

To sign with pvr outside the builder, make a key on your machine and have the Hub certify it. Only a certificate request leaves the machine:

sh
pvr signing enroll acme-gateway              # --role platform for the BSP key
# writes ~/.pvr/pvs/identities/acme-gateway-oem/{key.pem,x5c.pem,cacerts.pem}
K=~/.pvr/pvs/identities/acme-gateway-oem
pvr sig --key $K/key.pem --x5c $K/x5c.pem add --part my-app --noconfig
pvr add . && pvr commit
pvr sig --cacerts $K/cacerts.pem ls my-app

The Hub sets the certificate's name and usage itself, whatever the request says, and lists it on the identity as a key kept on a user's machine.

Own certificates ​

Create two roots and a signing certificate for each, keep the root keys offline, and upload. With openssl:

sh
# 1. The two roots. Keep *-root.key offline; only the .crt goes to the Hub.
openssl ecparam -name secp384r1 -genkey -noout -out oem-root.key
openssl req -x509 -new -key oem-root.key -sha384 -days 3650 \
  -subj "/O=Acme/CN=Acme OEM Root CA" \
  -addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
  -addext "keyUsage=critical,keyCertSign,cRLSign" -out oem-root.crt

openssl ecparam -name secp384r1 -genkey -noout -out platform-root.key
openssl req -x509 -new -key platform-root.key -sha384 -days 3650 \
  -subj "/O=Acme/CN=Acme Platform Root CA" \
  -addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
  -addext "keyUsage=critical,keyCertSign,cRLSign" -out platform-root.crt

# 2. Signing certificates: Code Signing, and the CN Pantavisor routes on —
#    the identity name for apps, <name>-platform for the BSP.
cat > leaf.ext <<'EOF'
basicConstraints=critical,CA:FALSE
keyUsage=critical,digitalSignature
extendedKeyUsage=codeSigning
EOF

openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out acme-oem.key
openssl req -new -key acme-oem.key -subj "/O=Acme/CN=acme" -out acme-oem.csr
openssl x509 -req -in acme-oem.csr -CA oem-root.crt -CAkey oem-root.key \
  -CAcreateserial -days 365 -sha256 -extfile leaf.ext -out acme-oem.crt

openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out acme-platform.key
openssl req -new -key acme-platform.key -subj "/O=Acme/CN=acme-platform" -out acme-platform.csr
openssl x509 -req -in acme-platform.csr -CA platform-root.crt -CAkey platform-root.key \
  -CAcreateserial -days 365 -sha256 -extfile leaf.ext -out acme-platform.crt

# 3. Check them.
openssl verify -CAfile oem-root.crt acme-oem.crt
openssl verify -CAfile platform-root.crt acme-platform.crt

Then:

  1. Signing → New signing identity: name acme (the CN of the app certificate), My own certificates, and paste oem-root.crt and platform-root.crt. Root certificates only: the form refuses a private key.
  2. On the identity, Upload key for each role: the private key followed by its certificate and any intermediates.
    sh
    cat acme-oem.key acme-oem.crt            # paste as the Apps (OEM) key
    cat acme-platform.key acme-platform.crt  # paste as the BSP (platform) key
    Or with pvr:
    sh
    pvr signing create acme --oem-root oem-root.crt --platform-root platform-root.crt
    cat acme-oem.key acme-oem.crt | pvr signing upload-key acme --role oem
    cat acme-platform.key acme-platform.crt | pvr signing upload-key acme --role platform
    The Hub checks that the key matches the certificate, that it chains to the identity's root for code signing, and that its CN is right, before it keeps it.
  3. Build with acme-oem as the signing key, as above. To replace a key, issue a new one from your CA and upload it.

Notes:

  • An intermediate CA works: put it after the certificate when you upload, and give the root pathlen:1 (or no pathlen).
  • RSA keys of 2048 bits or more work too; Pantavisor verifies RS256, ES256, ES384 and ES512.
  • Keep signing certificates short (a year): Pantavisor does not check revocation lists, so an expired or replaced key is how a key stops mattering.

Signing locally with pvr ​

The same certificate signs on your machine, without the Hub:

sh
pvr sig --key acme-oem.key --x5c acme-oem.crt add --part my-app --noconfig
pvr add . && pvr commit
pvr sig --cacerts oem-root.crt ls my-app     # verify against your root

OEM configuration part ​

Pantavisor reads per-fleet settings from the running revision, in <PV_OEM_NAME>/<PV_POLICY>.config: PH_CREDS_HOST, PV_CONTROL_REMOTE, PH_UPDATER_NETWORK_TIMEOUT, the device join token PH_FACTORY_AUTOTOK and the other keys Pantavisor allows at its OEM level. The image's PV_POLICY picks the file, so one part can hold several policies, such as green.config and red.config:

text
acme-gateway/
├── green.config      PH_FACTORY_AUTOTOK=<green token>, PV_CONTROL_REMOTE=1, …
└── red.config        PH_FACTORY_AUTOTOK=<red token>, …

A build adds this part to the app's export. It is optional:

  1. Keep each token as a config secret (Secrets → Configuration value, or pvr secret add --type config), with the repositories it may be used for as hosts. Its value is never shown again.

  2. In the build form, App name and secrets → Add an OEM configuration part: one policy per file, each key a value or a config secret, and any other files. With pvr, pass --oem oem.json; with the MCP connector, oem in start_build:

    json
    {
      "configs": {
        "green": { "PH_CREDS_HOST": "api.example.com", "PV_CONTROL_REMOTE": "1",
                   "PH_FACTORY_AUTOTOK": { "secret": "green-autotok" } },
        "red":   { "PH_FACTORY_AUTOTOK": { "secret": "red-autotok" } }
      }
    }
  3. Sign the app with an identity's key (acme-gateway-oem). The part is named after the identity and signed with its platform key (acme-gateway-platform): Pantavisor checks <PV_OEM_NAME>/** against the image's ca-certificates.crt, never the OEM bundle. Without an identity, give name and signing_secret (a pvs secret the image's default bundle trusts).

The same keys work in an image build: the part joins the factory revision of each image, and each image loads its own policy's file. PH_CREDS_HOST in it points the devices at another Hub (such as api.stage.pantahub.com) without rebuilding the image: the OEM level overrides the image's pantahub.config.

The build's summary lists the part's files and who signed it, never the values. The values are in the export and on every device that installs it: share the export like the tokens it holds.

Building an image that trusts the identity ​

meta-pantavisor's pv-trust class builds an image for your identity from a few variables. Get the bundles first:

sh
pvr signing trust acme -o $PWD/trust     # ca-oem-certificates.crt, ca-certificates.crt, pantavisor.config

(Show image setup on the identity gives the same files and settings.) Then set, in local.conf or exported for kas/with-oem-trust.yaml:

VariableValue
PV_OEM_NAMEthe identity name, acme
PV_SECUREBOOT_MODEstrict to refuse anything unsigned; lenient while you test
PV_POLICY (in PANTAVISOR_CONFIG)the policy of the OEM part the image uses, such as green
PV_TRUST_DIRthe absolute path of trust/: its bundles replace the image's /etc/pantavisor/pvs/trust/*.crt
PV_SIGN_PLATFORM_KEY, PV_SIGN_PLATFORM_X5Cthe platform key and certificate: they sign the BSP and the device skeleton. Use pvr signing enroll acme --role platform (Hub keys) or your CA's
PV_SIGN_OEM_KEY, PV_SIGN_OEM_X5Coptional: the OEM key and certificate, for the containers built into the image (else they take the platform key)
sh
export PV_OEM_NAME=acme PV_SECUREBOOT_MODE=strict PV_TRUST_DIR=$PWD/trust
export PV_SIGN_PLATFORM_KEY=~/.pvr/pvs/identities/acme-platform/key.pem
export PV_SIGN_PLATFORM_X5C=~/.pvr/pvs/identities/acme-platform/x5c.pem
kas build kas/scarthgap.yaml:kas/bsp-base.yaml:kas/machines/<machine>.yaml:kas/with-oem-trust.yaml

With kas-container, the files must be inside the work directory, and the paths are the container's (/work/...).

The image then trusts only your roots: apps your builds sign with acme-oem install; apps signed with pvr's developer key, with another identity, or an OEM key signing bsp/**, are refused in strict.

The MCP tool get_trust_bundle answers the same, so an assistant can set an image build up for you.