Appearance
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 image | Holds | Checks |
|---|---|---|
/etc/pantavisor/pvs/trust/ca-oem-certificates.crt | your OEM root | app signatures whose certificate CN is the image's PV_OEM_NAME |
/etc/pantavisor/pvs/trust/ca-certificates.crt | your platform root, or your BSP vendor's | everything 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 revision | Signed by | Key | Checked 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>-platform | ca-certificates.crt |
The BSP (bsp/**), the device skeleton and the image base's containers | the factory or image build | <identity>-platform | ca-certificates.crt |
| Anything signed with pvr's developer key | any build without a signing key | public, anyone has it | only 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_MODE | The device… |
|---|---|
strict | refuses a revision with any unsigned key or any signature that does not verify |
lenient | refuses signatures that do not verify; accepts unsigned keys |
audit | logs what strict would refuse, installs anyway |
disabled | does 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.crtTwo ways to get the keys
| Generated by Pantahub | Own certificates | |
|---|---|---|
| For | trying the feature, small fleets | companies and advanced users with their own PKI |
| Roots | made by the Hub; their keys stay sealed in the Hub | yours; their keys never reach the Hub |
| Signing keys | made by the Hub, rotated with one click | issued by your CA, uploaded by you |
| What the Hub holds | everything, encrypted | the 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
- 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. - The identity's keys appear among your secrets as
acme-gateway-oem(andacme-gateway-platform), marked as belonging to the identity. - Build with it: choose
acme-gateway-oemas Signing key in the build form, or--signing-secret acme-gateway-oem/signing-secret: acme-gateway-oemin pvr and GitHub Actions. The build summary shows the export as signed and verified against the identity's root. - 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-appThe 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.crtThen:
- Signing → New signing identity: name
acme(the CN of the app certificate), My own certificates, and pasteoem-root.crtandplatform-root.crt. Root certificates only: the form refuses a private key. - On the identity, Upload key for each role: the private key followed by its certificate and any intermediates.shOr with pvr:
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) keyshThe 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.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 - Build with
acme-oemas 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 nopathlen). - 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 rootOEM 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:
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.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,oeminstart_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" } } } }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'sca-certificates.crt, never the OEM bundle. Without an identity, givenameandsigning_secret(apvssecret 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:
| Variable | Value |
|---|---|
PV_OEM_NAME | the identity name, acme |
PV_SECUREBOOT_MODE | strict 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_DIR | the absolute path of trust/: its bundles replace the image's /etc/pantavisor/pvs/trust/*.crt |
PV_SIGN_PLATFORM_KEY, PV_SIGN_PLATFORM_X5C | the 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_X5C | optional: 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.yamlWith 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.