#!/bin/bash # make-ca.sh -- the StarshipOS capsule-signing PKI, made once, on the CI box (Captain Bob, # 2026-11-07: "New root CA on the CI box. Valid StarshipOS rather than snake oil intermediate"). # # StarshipOS Root CA Ed25519, 20 years, self-signed. Its key never leaves this box: # not in the repository, not in Gitea, not in any image. # StarshipOS Capsule Signing Ed25519, 10 years, signed by the root; CA:TRUE, pathlen:0. # Its certificate is built into every image (capsules/pki/ # starshipos-intermediate.der) and checks each capsule at boot; # its key signs the capsules (mkcapsule --sign-key), in CI from # the repository secret CAPSULE_SIGN_KEY. # # Replaces the offline LithosAnanke Root CA and its snakeoil intermediate (FABRIC-2.md, # Milestone 6) for every build. Run as root: # # sudo ci/pki/make-ca.sh [dir] # dir defaults to /var/lib/starshipos-ca # # It refuses to overwrite an existing CA. When it is done it prints the two certificates (public: # paste them where they are wanted) and says how to put the signing key into Gitea. With # GITEA_TOKEN set (a token that may administer the repository), it stores the secret itself. set -euo pipefail umask 077 dir=${1:-/var/lib/starshipos-ca} repo=${GITEA_REPO:-rajames/LithosAnanke} api=${GITEA_API:-https://gitea.starshipos-f18.org/api/v1} command -v openssl >/dev/null || { echo "make-ca: needs openssl" >&2; exit 1; } [ -e "$dir/root/root.key" ] && { echo "make-ca: $dir already holds a CA; not overwriting it" >&2; exit 1; } mkdir -p "$dir/root" "$dir/signing" cd "$dir" cat > ca.cnf <<'CNF' [ req ] distinguished_name = dn prompt = no [ dn ] [ root_ext ] basicConstraints = critical, CA:TRUE keyUsage = critical, keyCertSign, cRLSign subjectKeyIdentifier = hash [ signing_ext ] basicConstraints = critical, CA:TRUE, pathlen:0 keyUsage = critical, keyCertSign, cRLSign, digitalSignature subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always CNF # the root openssl genpkey -algorithm ed25519 -out root/root.key openssl req -new -x509 -key root/root.key -days 7305 -config ca.cnf -extensions root_ext \ -subj "/O=StarshipOS/CN=StarshipOS Root CA" -out root/root.pem # the capsule-signing intermediate, signed by the root openssl genpkey -algorithm ed25519 -out signing/signing.key openssl req -new -key signing/signing.key -config ca.cnf \ -subj "/O=StarshipOS/CN=StarshipOS Capsule Signing" -out signing/signing.csr openssl x509 -req -in signing/signing.csr -CA root/root.pem -CAkey root/root.key \ -CAcreateserial -days 3652 -extfile ca.cnf -extensions signing_ext -out signing/signing.pem openssl x509 -in signing/signing.pem -outform DER -out signing/starshipos-intermediate.der openssl verify -CAfile root/root.pem signing/signing.pem chmod 700 root signing; chmod 600 root/* signing/* if [ -n "${GITEA_TOKEN:-}" ]; then python3 -c 'import json,sys; print(json.dumps({"data": open(sys.argv[1]).read()}))' signing/signing.key | curl -fsS -o /dev/null -X PUT -H "Authorization: token $GITEA_TOKEN" -H 'Content-Type: application/json' \ "$api/repos/$repo/actions/secrets/CAPSULE_SIGN_KEY" -d @- && echo "make-ca: stored the signing key as $repo's secret CAPSULE_SIGN_KEY" else echo "make-ca: put $dir/signing/signing.key, whole, into $repo's Actions secret CAPSULE_SIGN_KEY" echo " (Settings -> Actions -> Secrets), or run again with GITEA_TOKEN set -- after" echo " removing $dir, since it refuses to overwrite a CA." fi echo echo "=== The root certificate (public) -- $dir/root/root.pem" cat root/root.pem echo "=== The capsule-signing certificate (public) -- $dir/signing/signing.pem" cat signing/signing.pem echo "=== Fingerprints" openssl x509 -in root/root.pem -noout -fingerprint -sha256 -subject -enddate openssl x509 -in signing/signing.pem -noout -fingerprint -sha256 -subject -issuer -enddate