Signing and verification
The Platform uses separate keys for software, board boot, mission commands, and the ground link. Each belongs to a different component, and losing one key does not give access to the others.
| Key | Used by | What it approves |
|---|---|---|
| Build key | space build | Applications and compositions |
| Board boot key | Board firmware | One boot image for one target |
| Release key | First boot stage | Kernel, runner, applications, and security version |
| Mission command key | Flight and payload computers | Commands that change node state |
| Link keys | SDLS and BPSec | Data sent over the ground link |
Do not reuse one key for these jobs. A run.space login token signs none of them.
Development identity
Every build signs; there is no unsigned build. The first space build creates
a development identity, trusts it from then on, and prints its fingerprint
once:
using new dev signing identity "dev" (SHA256:...)Manage identities explicitly when you need more than the default:
space keys generate devspace keys use devspace keys listspace keys public devOrdinary development builds use the selected identity and print its algorithm and fingerprint in the build output. The development identity is a convenience for the local loop, not a production identity.
Release builds
The release boundary is the explicit --release flag, not the tag spelling:
space build . -t app:release --release --signing-profile releaseA --release build requires an explicit, non-development signing profile and
rejects both the development identity and unsigned output.
A release-looking tag such as :release, :prod, or :production without
--release is refused. Tag spelling does not change signing strictness.
--signing-key KEY remains the low-level path for a concrete key accepted by
the selected signing profile:
space build . -t app:release --release --signing-key ./release.pemSelect how the node stores keys
A development build stores its local key in the development image. A release build stores encrypted keys and requires the target’s hardware-backed key-encryption key during boot. Select the mode explicitly when needed:
space build . -t app:dev --keystore devspace build . -t app:release --release --keystore sealed \ --signing-profile releaseA release build rejects --keystore dev. The boot log reports which mode it
loaded.
Set a project’s resource limit
The runner loads a deployment certificate at boot. The certificate sets the maximum resources one project may use: service count, memory, CPUs, storage, and network bridges. The runner keeps enforcing those values while the ground link is down.
Create the operator certificate:
space cert ca \ --name mission \ --max-services 8 \ --memory 4096 \ --cpus 0,1,2,3 \ --block 8192 \ --bridges payload-net \ --out operator.pemIssue a smaller limit to one project:
space cert issue \ --ca operator.pem \ --subject mission:payload \ --max-services 4 \ --memory 2048 \ --cpus 0,1 \ --block 4096 \ --bridges payload-net \ --out payload.pemCheck the certificate and its limits:
space cert show payload.pem --ca operator.pemThe output names mission:payload and prints the limits the runner enforces.
The project certificate can reduce the operator limits but cannot increase
them.
Add the certificate to a system-image target
Store both certificates in the target record:
space target connect sat-42 \ --profile cm5 \ --signing-key keys/sat-42.pem \ --operator-ca operator.pem \ --deploy-cert payload.pem \ --endpoint ipn:7.1 \ --cla-address sat-42.example:4556 \ --tle sat-42.tle \ --station 37.619,-122.375,0.004space deploy --plan includes the certificate digests in the signed release.
For a system-image target, SpaceOS writes the files to the read-only root at
/etc/space/certs/operator.pem and /etc/space/certs/payload.pem. The runner
starts with those paths.
Install the certificate on vendor Linux
Copy the public certificates and the trusted build key into root-owned directories:
sudo install -d -o root -g root -m 0755 /etc/space/certs /etc/space/keyssudo install -o root -g root -m 0644 \ operator.pem /etc/space/certs/operator.pemsudo install -o root -g root -m 0644 \ payload.pem /etc/space/certs/payload.pemsudo install -o root -g root -m 0644 \ release-build.pem /etc/space/keys/release-build.pemFind the user ID of the local controller process, then create a systemd drop-in:
id -u space-gatewaysudo install -d -o root -g root -m 0755 \ /etc/systemd/system/space-runner.service.dsudoedit /etc/systemd/system/space-runner.service.d/20-deployment-cert.confSet <controller-uid> to the number printed by id:
[Service]ExecStart=ExecStart=/usr/bin/space-runner serve \ --socket /run/space-runner/control.sock \ --peer-uid <controller-uid> \ --operator-ca /etc/space/certs/operator.pem \ --deploy-cert /etc/space/certs/payload.pem \ --keys-dir /etc/space/keysRestart the service and inspect its startup checks:
sudo systemctl restart space-runnersudo systemctl status space-runnersudo journalctl -u space-runner -bAt startup, the runner checks that payload.pem was signed by operator.pem,
has not expired, and does not exceed the operator limit. It then binds the
certificate subject mission:payload and its limits to launch requests. If a
check fails, the runner does not open its control socket.
Send a launch request
On the target, space-init, the SpaceOS gateway, or an operator running
space run --name sends the request. The local transport is a private Unix
socket. Messages use length-prefixed, versioned CBOR. The kernel reports the
caller’s user ID, and the runner accepts only the user IDs configured with
--peer-uid.
For a connected runner, the SpaceOS gateway receives queued operations from run.space over HTTPS using the node’s pool credential. For a spacecraft link, the operation arrives as a signed object over CFDP and BPv7, protected by BPSec and SDLS. In both cases, the gateway passes the verified operation to the same local Unix socket. A network connection or pool credential alone cannot approve a launch.
The runner checks a launch in this order:
- The Unix socket user ID is allowed. A command that crossed ground or project boundaries also has a valid mission signature.
- The command names the target and project. Its sequence number, expiration, and expected generation are valid.
- The project matches the deployment certificate, and the request stays below its service, memory, CPU, storage, and bridge limits.
- The application signature matches a pinned build key, and the image bytes match the signed digest.
- The signed
space.capabilityfile permits the requested message routes, storage ranges, devices, and keys. - The target has the selected isolation backend and enough free resources.
- The service passes its health check before SpaceOS reports it as running.
Target signing
Board facts are stored on a connected target: the board profile, the secure-boot signer, the device tree, the board kernel, and the uplink transport. Register them once:
space target connect cm5-demo --profile cm5-dev \ --signing-key keys/cm5-demo.pem \ --endpoint ipn:7.1 --cla-address cm5-demo.local:4556 --always-reachableThen rehearse and ship a release:
space deploy app:release --target cm5-demo --planspace deploy app:release --target cm5-demo--plan checks the composition, selects the target files, builds the boot
image, and signs it. It prints the result without contacting the target. The
second command queues the release and returns a deploy ID.
On a board with native secure boot, the boot chain has more than one signer.
On the Raspberry Pi CM5 reference target, a customer RSA key enrolled in
one-time-programmable memory authorizes the firmware boot stage, the Ed25519
release manifest binds the boot contents, and the
TUF bundle governs workload launch. Writing that
key into the chip is permanent, so enrolling and fusing it are separate admin
commands that ask for confirmation (space target enroll,
space target fuse). The deploy command has no enrollment or fusing
authority.
Verify a release without ground contact
The node checks a release using files already on board:
space buildrecords the digest of each service and signs the list.- During boot, dm-verity checks each filesystem block against its recorded root hash.
space-runnercompares the service bytes with the signed digest and checks the service’s resource limits before starting it.
Verification chain
| Verification input | Result |
|---|---|
| Pinned root | Authorizes the signed targets bundle. |
| Signed targets bundle | Commits the workload blob digest. |
| Blob digest | Selects the content-addressed partition. |
| Content-addressed partition | Supplies the exact launched bytes. |
Admission requires a pinned root and a committed digest that matches the launch bytes. Unikernel binaries and container root filesystems use the same content-addressed verification path.
Trust model: TUF over COSE
The launch-trust layer implements the security model of
TUF, The Update Framework, the same design used to
secure software updates on the ground. A pinned root key delegates to three
narrower roles: timestamp says the metadata is fresh, snapshot fixes one
consistent set of versions so an attacker cannot mix an old service into a new
release, and targets names the digests that may launch. Each role needs a
minimum number of signatures, and each carries a version number that only
increases, so an old bundle cannot be replayed.
Every role is a COSE signature envelope over CBOR, and the signed bundle travels as an OCI artifact next to the images it authorizes, so one pull, or one offline copy of the store, delivers metadata and images together.
The root key is pinned out of band, baked into the device image. Rotating it
is an offline operation requiring signatures under both the old and new keys,
so recovering from a compromise does not need a live revocation service.
Marketplace publishing hands out scoped sub-authorities under targets,
limited by app identity, architecture, and release namespace, using the same
mechanism.
Post-quantum profile
The mission launch-trust profile signs with ML-DSA-65 (NIST FIPS 204), a standardized signature algorithm chosen to remain secure against a future quantum computer. The signature thresholds allow a bounded transition from today’s Ed25519 to ML-DSA-65: during the change-over, a bundle can carry both. The CLI flow does not change with the algorithm: a build identity signs the composition, and a target key signs the board image.