crates/dwarves-slsa/scripts/yubikey-setup.sh
#!/bin/bash
# yubikey-setup.sh — the owner's ONE-TIME setup of the release signing key on a YubiKey.
#
# yubikey-setup.sh [<out dir>] (default ~/slsa-release-key)
#
# Every step that needs the YubiKey, its PIN or its management key is yours: ykman asks
# you directly. This script never reads, stores or prints a PIN.
#
# What it does:
# 1. shows the YubiKey (serial, PIV status) and stops if slot 9c already holds a key
# (FORCE=1 replaces it; the old release key then no longer signs);
# 2. generates an ECDSA P-256 key ON the YubiKey in PIV slot 9c (signature), with
# touch policy ALWAYS (every signature needs a touch) and PIN policy ONCE (the PIN
# once per session); the private key never leaves the YubiKey;
# 3. stores a self-signed certificate for it in slot 9c (some signers read the slot's
# certificate);
# 4. exports the public key (release-signing-key.pub, the RELEASE VERIFICATION KEY), the
# YubiKey's attestation of the key (proof it was generated on-device with those
# policies) and the attestation CA, and prints the key id.
#
# Then: hand release-signing-key.pub (and the two attestation files) to the coordinator,
# who commits them as slsa/release-signing-key.pub in holger and gunnar.
#
# Needs: ykman (YubiKey Manager 5: `apt install yubikey-manager` / `brew install ykman`,
# or `pipx install yubikey-manager`), pcscd running (Linux: `apt install pcscd`), openssl.
#
# Before the first key, if the PIV PIN, PUK or management key are still the factory
# defaults (ykman piv info warns), change them yourself:
# ykman piv access change-pin
# ykman piv access change-puk
# ykman piv access change-management-key --generate --protect
set -euo pipefail
die() { echo "RED $*" >&2; exit 1; }
OUT=${1:-$HOME/slsa-release-key}
SLOT=9c
SUBJECT=${SLSA_CERT_SUBJECT:-CN=Ignalina release signing (SLSA L3)}
command -v ykman >/dev/null || die "ykman is missing (apt install yubikey-manager)"
command -v openssl >/dev/null || die "openssl is missing"
mkdir -p "$OUT"
PUB=$OUT/release-signing-key.pub
[ ! -e "$PUB" ] || [ "${FORCE:-0}" = 1 ] || die "$PUB exists already (FORCE=1 to make a new release key)"
echo "== the YubiKey"
ykman info
echo
ykman piv info
echo
if ykman piv keys info "$SLOT" >/dev/null 2>&1 && [ "${FORCE:-0}" != 1 ]; then
die "slot $SLOT already holds a key; FORCE=1 replaces it (the old release key then signs nothing new)"
fi
read -r -p "Generate the release signing key in PIV slot $SLOT of THIS YubiKey? [yes/no] " A
[ "$A" = yes ] || die "nothing done"
echo "== 2. the key, on the YubiKey (ykman may ask for the management key or the PIN)"
ykman piv keys generate --algorithm ECCP256 --pin-policy ONCE --touch-policy ALWAYS "$SLOT" "$PUB"
echo "== 3. a self-signed certificate in slot $SLOT (PIN, then TOUCH the key when it blinks)"
ykman piv certificates generate --subject "$SUBJECT" --valid-days 3650 "$SLOT" "$PUB"
echo "== 4. the public key and the YubiKey's attestation"
ykman piv keys attest "$SLOT" "$OUT/release-signing-key.attestation.pem"
ykman piv certificates export f9 "$OUT/yubikey-piv-attestation-ca.pem"
openssl pkey -pubin -in "$PUB" -noout >/dev/null || die "$PUB is not a public key"
KEYID=$(openssl pkey -pubin -in "$PUB" -outform DER | sha256sum | cut -d' ' -f1)
echo
echo "release verification key $PUB"
echo "key id (sha256 of SPKI) $KEYID"
echo "attestation $OUT/release-signing-key.attestation.pem (CA $OUT/yubikey-piv-attestation-ca.pem)"
echo
echo "Next: give $OUT/release-signing-key.pub and the two .pem files to the coordinator, who"
echo "commits them as slsa/ in holger and gunnar. Keep the YubiKey; there is no copy of the key."