keymgmt.sh (4825B)
1 #!/bin/sh 2 # mk/keymgmt.sh - signing key management. 3 # 4 # keymgmt.sh new [--type rsa|supercop] [--for apk|unos] <stem> 5 # Generate a key named <stem>-<8 hex> in ~/.unos-keys/ and stage its 6 # public half into channels/base/packages/unos-keys/files/<for>/. Defaults: rsa for 7 # apk (4096-bit), supercop for unos. apk mandates RSA: supercop-for-apk 8 # is refused. After adding keys, bump the unos-keys version and rebuild. 9 # 10 # keymgmt.sh use <name|file> 11 # Select the RSA key used to sign packages; writes its full path to 12 # .sign-key (repo root, never committed). CI sets UNOS_SIGN_KEY or 13 # writes .sign-key itself. Accepts a stem, a filename in ~/.unos-keys/, 14 # or a path. 15 # 16 # keymgmt.sh list 17 # Show local keys and which one is active. 18 set -eu 19 20 HERE=$(cd "$(dirname "$0")" && pwd) 21 ROOT=$(cd "${HERE}/.." && pwd) 22 KEYDIR="${HOME}/.unos-keys" 23 # unos-keys lives in base: trust anchors are not channel-specific. 24 PKGKEYS="${ROOT}/channels/base/packages/unos-keys/files" 25 26 msg() { printf '==> %s\n' "$*"; } 27 die() { printf 'keymgmt.sh: error: %s\n' "$*" >&2; exit 1; } 28 29 valid_stem() { 30 case "$1" in 31 ''|*[!A-Za-z0-9@._-]*) return 1;; 32 *) return 0;; 33 esac 34 } 35 36 cmd_new() { 37 type=rsa 38 for= 39 while [ $# -gt 0 ]; do 40 case "$1" in 41 --type) type=$2; shift 2;; 42 --for) for=$2; shift 2;; 43 -h|--help) cmd_usage; exit 0;; 44 --*) die "unknown flag: $1";; 45 *) break;; 46 esac 47 done 48 [ $# = 1 ] || die "usage: keymgmt.sh new [--type rsa|supercop] [--for apk|unos] <stem>" 49 stem=$1 50 valid_stem "${stem}" || die "bad stem (allowed: A-Z a-z 0-9 @ . _ -): ${stem}" 51 case "${type}" in rsa|supercop) ;; *) die "type must be rsa or supercop";; esac 52 if [ -z "${for}" ]; then 53 if [ "${type}" = rsa ]; then for=apk; else for=unos; fi 54 fi 55 case "${for}" in apk|unos) ;; *) die "--for must be apk or unos";; esac 56 if [ "${for}" = apk ] && [ "${type}" != rsa ]; then 57 die "apk signatures mandate RSA; supercop keys are unos-only" 58 fi 59 60 mkdir -p "${KEYDIR}" 61 chmod 700 "${KEYDIR}" 62 i=0 63 while :; do 64 suffix=$(openssl rand -hex 4) 65 base="${stem}-${suffix}" 66 case "${type}" in 67 rsa) priv="${base}.rsa"; pub="${base}.rsa.pub";; 68 supercop) priv="${base}.ed25519"; pub="${base}.ed25519.pub";; 69 esac 70 [ -e "${KEYDIR}/${priv}" ] || [ -e "${KEYDIR}/${pub}" ] || break 71 i=$((i + 1)) 72 [ "${i}" -lt 10 ] || die "cannot find a free key name, retry" 73 done 74 75 case "${type}" in 76 rsa) 77 openssl genrsa -out "${KEYDIR}/${priv}" 4096 2>/dev/null 78 openssl rsa -in "${KEYDIR}/${priv}" -pubout -out "${KEYDIR}/${pub}" 2>/dev/null 79 # Enforce minimum (defence in depth, even though we just generated 4096) 80 "${HERE}/check-keysize.sh" "${KEYDIR}/${priv}" 4096 >/dev/null 81 ;; 82 supercop) 83 supercop generate -k "${KEYDIR}/${priv}" 84 supercop printkey -k "${KEYDIR}/${priv}" --public-only -f asc -o "${KEYDIR}/${pub}" 85 ;; 86 esac 87 chmod 600 "${KEYDIR}/${priv}" 88 89 mkdir -p "${PKGKEYS}/${for}" 90 cp "${KEYDIR}/${pub}" "${PKGKEYS}/${for}/${pub}" 91 msg "key: ${KEYDIR}/${priv}" 92 msg "pub staged: channels/base/packages/unos-keys/files/${for}/${pub}" 93 echo "next: bump unos-keys version in channels/base, rebuild it, re-index" 94 } 95 96 cmd_use() { 97 [ $# = 1 ] || die "usage: keymgmt.sh use <name|file>" 98 arg=$1 99 if [ -f "${arg}" ]; then 100 case "${arg}" in 101 /*) path=${arg};; 102 *) path=${PWD}/${arg};; 103 esac 104 elif [ -f "${KEYDIR}/${arg}" ]; then 105 path=${KEYDIR}/${arg} 106 elif [ -f "${KEYDIR}/${arg}.rsa" ]; then 107 path=${KEYDIR}/${arg}.rsa 108 else 109 die "no such key: ${arg}" 110 fi 111 openssl rsa -in "${path}" -check -noout >/dev/null 2>&1 \ 112 || die "not an RSA private key: ${path}" 113 "${HERE}/check-keysize.sh" "${path}" 4096 >/dev/null \ 114 || die "key too small (4096-bit minimum): ${path}" 115 printf '%s\n' "${path}" > "${ROOT}/.sign-key" 116 msg "signing key: ${path}" 117 } 118 119 cmd_list() { 120 # shellcheck disable=SC1090 121 . "${HERE}/sign-key.inc" 122 active=$(resolve_sign_key 2>/dev/null) || active= 123 found=0 124 for f in "${KEYDIR}"/*; do 125 [ -e "${f}" ] || continue 126 found=1 127 mark=" " 128 [ "${f}" = "${active}" ] && mark="*" 129 case "${f}" in 130 *.rsa) kind="rsa";; 131 *.rsa.pub) kind="rsa-pub";; 132 *.ed25519) kind="ed25519";; 133 *.ed25519.pub) kind="ed25519-pub";; 134 *) kind="?";; 135 esac 136 printf '%s %-12s %s\n' "${mark}" "${kind}" "${f}" 137 done 138 [ "${found}" = 1 ] || echo "(no keys in ${KEYDIR})" 139 } 140 141 cmd_usage() { 142 cat <<EOF 143 usage: keymgmt.sh new [--type rsa|supercop] [--for apk|unos] <stem> 144 keymgmt.sh use <name|file> 145 keymgmt.sh list 146 EOF 147 } 148 149 [ $# -ge 1 ] || { cmd_usage >&2; exit 1; } 150 cmd=$1; shift 151 case "${cmd}" in 152 new) cmd_new "$@";; 153 use) cmd_use "$@";; 154 list) cmd_list "$@";; 155 -h|--help) cmd_usage; exit 0;; 156 *) cmd_usage >&2; exit 1;; 157 esac