260 lines
4.3 KiB
Bash
260 lines
4.3 KiB
Bash
#!/bin/sh
|
|
# cassh - Manager for an OpenSSH Certification Authority
|
|
#
|
|
# Written in 2022 by Lucas
|
|
#
|
|
# To the extent possible under law, the author(s) have dedicated all
|
|
# copyright and related and neighboring rights to this software to the
|
|
# public domain worldwide. This software is distributed without any
|
|
# warranty.
|
|
# You should have received a copy of the CC0 Public Domain Dedication
|
|
# along with this software. If not, see
|
|
# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
|
|
|
usage()
|
|
{
|
|
cat - <<EOF >&2
|
|
Usage:
|
|
${0##*/} issue [-hqv] [-I key_id] [-n principals]
|
|
[-V validity_interval]
|
|
${0##*/} mkfile authorized_keys [options ...]
|
|
${0##*/} mkfile known_hosts [hostnames ...]
|
|
${0##*/} revoke [-qv] file ...
|
|
EOF
|
|
exit 1
|
|
}
|
|
|
|
err()
|
|
{
|
|
printf "%s: %s\n" "${0##*/}" "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
# Returns the comment from the loaded secret key in ssh-agent, if any is
|
|
# present.
|
|
get_ca_sk_comment_from_pk()
|
|
{
|
|
ssh-keygen -lf "$1" 2>/dev/null | {
|
|
read -r pk_sz pk_fp pk_extra
|
|
_comment=$(ssh-add -l | while read -r sk_sz sk_fp sk_extra; do
|
|
if [ "X$sk_fp" = "X$pk_fp" ]; then
|
|
echo "${sk_extra% (*)}"
|
|
break
|
|
fi
|
|
done)
|
|
echo "${_comment:-}"
|
|
}
|
|
}
|
|
|
|
format()
|
|
{
|
|
_s=$1
|
|
shift
|
|
|
|
_cleanup=unset
|
|
while [ $# -ge 2 ]; do
|
|
_k=$1 _v=$2
|
|
shift 2
|
|
case $_k in
|
|
[A-Za-z])
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
eval "_token_${_k}=\$_v"
|
|
_cleanup=$_cleanup" _token_${_k}"
|
|
done
|
|
if [ $# -ne 0 ]; then
|
|
return 1
|
|
fi
|
|
|
|
_out=
|
|
while [ "${_s#*%}" != "$_s" ]; do
|
|
_t=${_s#*%}
|
|
_out=$_out${_s%"%"$_t}
|
|
_s=$_t
|
|
_c=${_s%${_s#?}}
|
|
|
|
if [ -z "${_c:-}" ]; then
|
|
return 1
|
|
elif [ X"${_c}" = X% ]; then
|
|
_out=$_out%
|
|
else
|
|
eval "_out=$_out\$_token_${_c}" || return 1
|
|
fi
|
|
|
|
_s=${_s#$_c}
|
|
done
|
|
_out=$_out$_s
|
|
|
|
eval "$_cleanup"
|
|
|
|
echo "$_out"
|
|
}
|
|
|
|
strjoin()
|
|
{
|
|
_c=$1
|
|
shift
|
|
|
|
_out=
|
|
for _s; do
|
|
_out=${_out:+$_out$_c}$_s
|
|
done
|
|
|
|
echo "$_out"
|
|
}
|
|
|
|
main_issue()
|
|
{
|
|
hflag=
|
|
key_id_fmt=%C/%f
|
|
nflag=false
|
|
principals_fmt=
|
|
qflag=
|
|
validity_interval=always:forever
|
|
vflag=
|
|
while getopts hI:n:qV:v flag; do
|
|
case $flag in
|
|
h) hflag=-h ;;
|
|
I) key_id_fmt=$OPTARG ;;
|
|
n) nflag=true principals_fmt=$OPTARG ;;
|
|
q) qflag=-q ;;
|
|
V) validity_interval=$OPTARG ;;
|
|
v) vflag=${vflag:--}v ;;
|
|
*) usage ;;
|
|
esac
|
|
done
|
|
shift $(($OPTIND - 1))
|
|
if [ $# -ne 0 ]; then
|
|
usage
|
|
fi
|
|
|
|
if [ ! -f "$PATH_CA_SERIAL" ]; then
|
|
echo 1 >"$PATH_CA_SERIAL"
|
|
fi
|
|
read -r serial <"$PATH_CA_SERIAL"
|
|
|
|
if [ ! -d "$PATH_PUBKEYS_DIR" ]; then
|
|
exit 0
|
|
fi
|
|
find "$PATH_PUBKEYS_DIR" -type f -name '*.pub' ! -name '*-cert.pub' |
|
|
sort | {
|
|
ca_comment=$(get_ca_sk_comment_from_pk "$PATH_CA_PUB")
|
|
: ${ca_comment:=cassh}
|
|
|
|
while read -r pk; do
|
|
pkname=${pk%.pub}
|
|
pkname=${pkname#$PATH_PUBKEYS_DIR/}
|
|
|
|
id=$(format "$key_id_fmt" C "$ca_comment" f "$pkname")
|
|
set -- -I "$id" -Us "$PATH_CA_PUB" \
|
|
$hflag $qflag $vflag \
|
|
-V "$validity_interval" -z "$serial"
|
|
|
|
if $nflag; then
|
|
principals=$(format "$principals_fmt" \
|
|
f "$pkname")
|
|
ssh-keygen "$@" -n "$principals" "$pk"
|
|
else
|
|
ssh-keygen "$@" "$pk"
|
|
fi
|
|
if [ $? -ne 0 ]; then
|
|
exit 1
|
|
fi
|
|
|
|
serial=$(($serial + 1))
|
|
echo $serial >"$PATH_CA_SERIAL"
|
|
done
|
|
}
|
|
}
|
|
|
|
main_mkfile()
|
|
{
|
|
while getopts : flag; do
|
|
case $flag in
|
|
*) usage ;;
|
|
esac
|
|
done
|
|
shift $(($OPTIND - 1))
|
|
if [ $# -lt 1 ]; then
|
|
usage
|
|
fi
|
|
file=$1
|
|
shift
|
|
|
|
if [ ! -f "$PATH_CA_PUB" ]; then
|
|
err "no $PATH_CA_PUB found"
|
|
fi
|
|
|
|
case $file in
|
|
authorized_keys)
|
|
printf "%s " "$(strjoin , cert-authority "$@")"
|
|
;;
|
|
known_hosts)
|
|
if [ $# -gt 0 ]; then
|
|
printf "@cert-authority %s " "$(strjoin , "$@")"
|
|
else
|
|
printf "@cert-authority "
|
|
fi
|
|
;;
|
|
*)
|
|
err "unknown file \"$file\""
|
|
;;
|
|
esac
|
|
|
|
cat "$PATH_CA_PUB"
|
|
}
|
|
|
|
main_revoke()
|
|
{
|
|
qflag=
|
|
vflag=
|
|
while getopts fqv flag; do
|
|
case $flag in
|
|
q) qflag=-q ;;
|
|
v) vflag=${vflag:--}v ;;
|
|
*) usage ;;
|
|
esac
|
|
done
|
|
shift $(($OPTIND - 1))
|
|
|
|
if [ ! -f "$PATH_KRL_SERIAL" ]; then
|
|
echo 1 >"$PATH_KRL_SERIAL"
|
|
fi
|
|
read -r serial <"$PATH_KRL_SERIAL"
|
|
|
|
uflag=
|
|
if [ -f "$PATH_KRL" ]; then
|
|
uflag=-u
|
|
fi
|
|
|
|
ssh-keygen -kf "$PATH_KRL" -Us "$PATH_CA_PUB" -z "$serial" \
|
|
$qflag $vflag $uflag "$@" || exit 1
|
|
|
|
serial=$(($serial + 1))
|
|
echo $serial >"$PATH_KRL_SERIAL"
|
|
}
|
|
|
|
set -u
|
|
|
|
PATH_CA_PUB=./ca.pub
|
|
PATH_CA_SERIAL=./ca_serial.txt
|
|
PATH_KRL=./krl
|
|
PATH_KRL_SERIAL=./krl_serial.txt
|
|
PATH_PUBKEYS_DIR=./pubkeys
|
|
|
|
if [ $# -lt 1 ]; then
|
|
usage
|
|
fi
|
|
cmd=$1
|
|
shift
|
|
|
|
case $cmd in
|
|
issue) main_issue "$@" ;;
|
|
mkfile) main_mkfile "$@" ;;
|
|
revoke) main_revoke "$@" ;;
|
|
*) usage ;;
|
|
esac
|