Skip to content
Generating and listing a signing identity work today and run on this page; the release build and space verify need a registry and a builder VM, so they are shown rather than run by the documentation gate.

Signing and verification

Every SpaceOS build is signed. Development builds can use a local identity. Release builds require an explicit non-development identity and rollback index.

Signing provides authenticity and integrity. It does not encrypt an image or hide its contents.

Build identities

A build identity contains two independently generated signing keys:

  • Ed25519 provides the classical signature.
  • ML-DSA-65 provides the post-quantum signature.

space keys generate stores the two private keys in separate files and records their paths under one identity name. Separate files do not provide separate custody by themselves. Move each file to the custody system that owns it when the two keys must be controlled independently.

The identity fingerprint printed by the CLI is the digest of the Ed25519 public key. It is not a digest of both keys.

List the local identities with space keys ls. The star marks the identity a development build signs with:

Terminal window
$ space keys ls
NAME FINGERPRINT ED25519 ML-DSA-65
* dev sha256:1248e4c798905f249f72b4807b877da0fedfd9264582254c8e42bd468059994d /tmp/space-docs/config/keys/dev.ed25519.pem /tmp/space-docs/config/keys/dev.mldsa65.pem

Create an identity, select it, and export its public key:

Terminal window
$ space keys generate flight
$ space keys use flight
$ space keys public flight > flight.ed25519.pub.pem

space keys public writes the Ed25519 public key. Keep the public key or its fingerprint in an independent channel when another machine must verify an image.

Development builds

If no identity is configured, the first development build creates and selects an identity named dev. Later development builds reuse the selected identity.

Terminal window
$ space build build.yaml -t <your-registry>/app:dev

The development identity is for the local build loop. A release build refuses the dev profile and refuses the same development key if it is renamed or passed by file path.

Release builds

Create or import the release identity before running the release build. This local command is suitable for a bench identity:

Terminal window
$ space keys generate release

For production custody, generate the keys in the system that authorizes the release. Make both private-key files available to the build only for the signing operation.

Build the release with an explicit identity and rollback index:

Terminal window
$ space build build.yaml -t <your-registry>/app:release \
> --release \
> --signing-profile release \
> --rollback-index 1

A release build enforces these checks:

  • The signing identity already exists. The build does not create it.
  • The identity is not the recorded development identity.
  • --rollback-index is at least 1.
  • The secret keystore posture is sealed. A release rejects the development secret keystore.

A tag ending in :release, :prod, or :production does not enable release behavior. The build refuses such a tag unless --release is present.

You can name the two private-key files instead of a profile:

Terminal window
$ space build build.yaml -t <your-registry>/app:release \
> --release \
> --signing-key-ed25519 ./release.ed25519.pem \
> --signing-key-mldsa65 ./release.mldsa65.pem \
> --rollback-index 1

Both file arguments are required. The CLI does not search beside one key for the other.

The --keystore option controls the workload secret store in the measured launch plan. It does not control build-signing key storage. A sealed workload keystore does not prove that the release signing keys were held in an HSM or a separate signing service.

Verify delivered build evidence

space verify rechecks a delivered image in the local OCI store. It requires a publisher public key obtained independently from the image.

Pull the image if it is not already present, then verify it:

Terminal window
$ space image pull <registry>/<project>/app:release
$ space verify <registry>/<project>/app:release \
> --anchor ./publisher.ed25519.pub.pem

The command checks this evidence chain:

  1. The image index contains digest-valued SBOM and provenance annotations.
  2. The referenced blobs exist and match those digests.
  3. The blobs decode as in-toto statements.
  4. The detached SCITT signed statement for each item verifies under --anchor.
  5. The statement subjects match the partitions published by the image index.

The command exits nonzero when a link fails. It distinguishes a bad anchor, a wrong signer, a malformed annotation, a missing local image, tampered evidence, and a tampered published artifact.

space verify does not pull the image. It does not inspect a running node, verify board secure boot, or prove release-key custody. Those are separate checks.

Node admission

The node authorizes launch bytes through a TUF metadata chain encoded as COSE signatures over CBOR:

Metadata roleCheck
rootA threshold of pinned keys authorizes the root metadata.
timestampSelects the current snapshot metadata.
snapshotSelects one targets metadata version.
targetsAuthorizes a path, byte length, and SHA-256 digest.

The node starts from public root keys pinned out of band. It verifies the root threshold, then timestamp, snapshot, and targets. It rejects a target when the path, length, or digest does not match the launch bytes.

The signed metadata and content travel with the release, so a node verifies the chain it has selected without reaching the network. A tag in the local store selects that chain: spaceos-trust:current for launch trust, and spaceos-release-trust:current for release trust. Retained root and targets versions refuse an older set.

A tag is a mutable pointer, and no signature covers what it points at. The store cannot tell a maintainer re-pointing a tag from an attacker doing it, so a tag decides which chain a node checks. The pinned root keys bound this. A chain reached through a tag still has to verify under them, so an attacker who controls a tag cannot forge trust metadata; they can put forward any chain the pinned keys have ever signed. The gap closes when a node takes the chain by digest: the anchor pinned out of band when the node is provisioned, and everything the chain selects named by metadata the node has already verified as current.

A root can require both Ed25519 and ML-DSA-65 signatures. A threshold of two rejects a root carrying only one of those signatures.

Keep the boundaries separate

  • A build signature authenticates build evidence and release metadata. It does not encrypt image contents.
  • The Ed25519 key printed by space keys public is the anchor accepted by space verify. The ML-DSA-65 half is used by the launch-trust metadata chain.
  • The sealed workload keystore protects runtime secret material. It is not the release signing-key store.
  • Board secure boot, mission-command authorization, and link security use separate verification paths. A successful space verify result does not assert those paths.

See Boot and updates for target boot and rollback behavior.