Skip to content

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.

KeyUsed byWhat it approves
Build keyspace buildApplications and compositions
Board boot keyBoard firmwareOne boot image for one target
Release keyFirst boot stageKernel, runner, applications, and security version
Mission command keyFlight and payload computersCommands that change node state
Link keysSDLS and BPSecData 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:

Terminal window
space keys generate dev
space keys use dev
space keys list
space keys public dev

Ordinary 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:

Terminal window
space build . -t app:release --release --signing-profile release

A --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:

Terminal window
space build . -t app:release --release --signing-key ./release.pem

Select 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:

Terminal window
space build . -t app:dev --keystore dev
space build . -t app:release --release --keystore sealed \
--signing-profile release

A 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:

Terminal window
space cert ca \
--name mission \
--max-services 8 \
--memory 4096 \
--cpus 0,1,2,3 \
--block 8192 \
--bridges payload-net \
--out operator.pem

Issue a smaller limit to one project:

Terminal window
space cert issue \
--ca operator.pem \
--subject mission:payload \
--max-services 4 \
--memory 2048 \
--cpus 0,1 \
--block 4096 \
--bridges payload-net \
--out payload.pem

Check the certificate and its limits:

Terminal window
space cert show payload.pem --ca operator.pem

The 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:

Terminal window
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.004

space 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:

Terminal window
sudo install -d -o root -g root -m 0755 /etc/space/certs /etc/space/keys
sudo install -o root -g root -m 0644 \
operator.pem /etc/space/certs/operator.pem
sudo install -o root -g root -m 0644 \
payload.pem /etc/space/certs/payload.pem
sudo install -o root -g root -m 0644 \
release-build.pem /etc/space/keys/release-build.pem

Find the user ID of the local controller process, then create a systemd drop-in:

Terminal window
id -u space-gateway
sudo install -d -o root -g root -m 0755 \
/etc/systemd/system/space-runner.service.d
sudoedit /etc/systemd/system/space-runner.service.d/20-deployment-cert.conf

Set <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/keys

Restart the service and inspect its startup checks:

Terminal window
sudo systemctl restart space-runner
sudo systemctl status space-runner
sudo journalctl -u space-runner -b

At 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.

A local controller or remote ground operation reaches the runner through a checked transport. The runner then checks the command, project limit, software, and requested resources before launch.

The runner checks a launch in this order:

  1. The Unix socket user ID is allowed. A command that crossed ground or project boundaries also has a valid mission signature.
  2. The command names the target and project. Its sequence number, expiration, and expected generation are valid.
  3. The project matches the deployment certificate, and the request stays below its service, memory, CPU, storage, and bridge limits.
  4. The application signature matches a pinned build key, and the image bytes match the signed digest.
  5. The signed space.capability file permits the requested message routes, storage ranges, devices, and keys.
  6. The target has the selected isolation backend and enough free resources.
  7. 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:

Terminal window
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-reachable

Then rehearse and ship a release:

Terminal window
space deploy app:release --target cm5-demo --plan
space 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:

  1. space build records the digest of each service and signs the list.
  2. During boot, dm-verity checks each filesystem block against its recorded root hash.
  3. space-runner compares the service bytes with the signed digest and checks the service’s resource limits before starting it.

Verification chain

Verification inputResult
Pinned rootAuthorizes the signed targets bundle.
Signed targets bundleCommits the workload blob digest.
Blob digestSelects the content-addressed partition.
Content-addressed partitionSupplies 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.