pki: the StarshipOS CA, made on the CI box; capsules signed in CI
Build / Image RASPI (push) Successful in 2m17s
Build / Image PC (push) Successful in 2m31s
Build / Image MILKV (push) Successful in 2m20s
Build / v4 tests (32- and 64-bit cells) (push) Successful in 8m51s

Ruled by Captain Bob, 2026-11-07: "New root CA on the CI box. Valid
StarshipOS rather than snake oil intermediate."

- ci/pki/make-ca.sh makes the StarshipOS Root CA (its key stays on the CI
  box) and the StarshipOS Capsule Signing intermediate it issues, and
  stores the signing key as the secret CAPSULE_SIGN_KEY.
- The kernel finds the signing certificate by one name,
  CAPSULE_SIGNING_CERT_NAME = pki:starshipos-intermediate.der. The
  certificate itself lands once make-ca.sh has run; until then the
  snakeoil intermediate is in the tree under its old name and the
  capsules check as "no root key" (a warning, as "missing" was).
- mkcapsule refuses a signing key that is not that certificate's.
- SIGN_KEY has no default; acceptance, build, nightly and release set it
  from the secret.

FABRIC-3.md XLVI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
This commit is contained in:
Claude
2026-11-07 04:15:29 +00:00
parent 4ae1f759d5
commit 03027f9769
13 changed files with 246 additions and 19 deletions
+7 -7
View File
@@ -207,7 +207,8 @@ MKCAPSULE_SRC = tools/mkcapsule.c tools/pkcs8_ed25519.c \
kernel/src/crypto/ed25519.c \
kernel/src/crypto/fe25519.c \
kernel/src/crypto/scalar25519.c \
kernel/src/crypto/sha512.c
kernel/src/crypto/sha512.c \
kernel/src/crypto/x509_ed25519.c
MKCAPSULE_BIN = $(BUILD_DIR)/tools/mkcapsule
# mkartemis: formats and signs an Artemis partition at build with the kernel's own
# block-subsystem and artemis_sig code (tools/mkartemis.c), built as v4/Makefile builds them.
@@ -215,12 +216,11 @@ MKARTEMIS_SRC = tools/mkartemis.c kernel/src/artemis_sig.c \
$(addprefix v3/src/,block_subsystem.c blkio_ram.c blkio_file.c log.c \
platform/platform_init.c platform/linux/time.c)
MKARTEMIS_BIN = $(BUILD_DIR)/tools/mkartemis
# Milestone 6 (Phase 8): the snakeoil intermediate's private key, generated
# offline outside this repo entirely (see FABRIC-2.md's Phase 8 §Milestone 6
# writeup) -- not present in CI or a fresh checkout, so signing is skipped
# gracefully ($(wildcard ...) below) rather than failing the build. Override
# with `make SIGN_KEY=/path/to/key.pem ...` on a machine that holds it.
SIGN_KEY ?= /home/rajames/CLionProjects/lithosananke-ca/intermediate/snakeoil-intermediate.key
# The capsule-signing key: "StarshipOS Capsule Signing"'s private key (ci/pki/make-ca.sh,
# FABRIC-3.md XLVI). CI sets SIGN_KEY from the secret CAPSULE_SIGN_KEY; elsewhere set it by hand
# (`make SIGN_KEY=/path/to/signing.key ...`). Unset or absent, the capsules are built unsigned,
# which boots with a warning per capsule. mkcapsule refuses a key that is not the certificate's.
SIGN_KEY ?=
SIGN_KEY_ARGS = $(if $(wildcard $(SIGN_KEY)),--sign-key $(SIGN_KEY),)
CAPSULE_GENERATED = $(BUILD_DIR)/capsule_generated.c
CAPSULE_GENERATED_OBJ = $(BUILD_DIR)/capsule_generated.o
+2 -1
View File
@@ -171,7 +171,8 @@ typedef struct {
*
* Milestone 6 (Phase 8): each capsule's payload bytes (the same bytes
* content_hash already covers), signed by mkcapsule at build time with
* the snakeoil intermediate's private key. has_sig=0 for a capsule built
* the capsule-signing key (StarshipOS Capsule Signing, capsule_sig.h;
* the snakeoil intermediate's until 2026-11-07). has_sig=0 for a capsule built
* before this feature existed or otherwise unsigned -- a real, distinct
* state, not "signature is all-zero bytes" (which sig[64] full of 0x00
* would otherwise look ambiguous with). Indexed 1:1 with
+9 -3
View File
@@ -22,6 +22,12 @@
#include "starkernel/capsule.h"
/* The certificate whose key the capsules are signed with: "StarshipOS
* Capsule Signing", issued by the StarshipOS Root CA (ci/pki/make-ca.sh;
* FABRIC-3.md XLVI), replacing the snakeoil intermediate of Milestone 6.
* mkcapsule refuses to sign with a key that is not this certificate's. */
#define CAPSULE_SIGNING_CERT_NAME "pki:starshipos-intermediate.der"
typedef enum {
CAPSULE_SIG_OK = 0, /* has_sig=1, and it verifies */
CAPSULE_SIG_MISSING, /* has_sig=0 -- not signed at all */
@@ -31,9 +37,9 @@ typedef enum {
/*
* Verify capsule descs[index]'s Ed25519 signature against the embedded
* snakeoil intermediate cert's public key (capsule name
* "pki:snakeoil-intermediate.der", found and parsed once, cached for
* every later call this boot -- the cert doesn't change mid-boot).
* capsule-signing certificate's public key (CAPSULE_SIGNING_CERT_NAME,
* found and parsed once, cached for every later call this boot -- the
* cert doesn't change mid-boot).
*
* descs/names/sigs must be the same three parallel arrays
* (capsule_get_descriptors()/capsule_get_names()/capsule_get_signatures()),
+4 -3
View File
@@ -6,9 +6,10 @@
*
* x509_extract_ed25519_pubkey() only ever reads SubjectPublicKeyInfo --
* no signature verification, no chain validation, no extension parsing.
* That's the capsule-PKI use case (Milestone 6): the embedded snakeoil
* intermediate cert is trusted because it's baked into the trusted build,
* never re-verified against the offline root CA at boot.
* That's the capsule-PKI use case (Milestone 6): the embedded capsule-
* signing cert (StarshipOS Capsule Signing since 2026-11-07, the snakeoil
* intermediate before it) is trusted because it's baked into the trusted
* build, never re-verified against its root CA at boot.
*
* x509_verify_signature()/x509_extract_serial() (added 2026-08-28,
* FABRIC-2.md §F.7/§F.17) are for CERTVERIFY -- a regular user's cert,
+1 -3
View File
@@ -4,8 +4,6 @@
#include "starkernel/ed25519.h"
#include <string.h>
#define SNAKEOIL_CERT_NAME "pki:snakeoil-intermediate.der"
/* Cached once per boot: the cert doesn't change mid-boot, and every
* capsule birth (Mama, then every child VM) would otherwise re-find and
* re-parse the same cert. -1 = not yet attempted, 0 = failed, 1 = ready. */
@@ -20,7 +18,7 @@ static int find_and_cache_pubkey(const CapsuleDesc *descs,
pubkey_state = 0; /* assume failure unless every step below succeeds */
for (uint32_t i = 0; i < desc_count; i++) {
if (strcmp(names[i].name, SNAKEOIL_CERT_NAME) == 0) {
if (strcmp(names[i].name, CAPSULE_SIGNING_CERT_NAME) == 0) {
const CapsuleDesc *cert_desc = &descs[i];
if (x509_extract_ed25519_pubkey(arena_base + cert_desc->offset,
(size_t)cert_desc->length,