unos-repository

APK repository for unos
git clone git://git.finwo.net/misc/unos-repository
Log | Files | Refs | README

keymgmt.sh (4708B)


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