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
82 lines
4.0 KiB
Bash
Executable File
82 lines
4.0 KiB
Bash
Executable File
#!/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
|