Initial import
This commit is contained in:
commit
df41cacbba
5 changed files with 572 additions and 0 deletions
257
cassh.sh
Normal file
257
cassh.sh
Normal file
|
@ -0,0 +1,257 @@
|
|||
#!/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 [-n principals] authorized_keys | known_hosts
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
err()
|
||||
{
|
||||
printf "%s: %s\n" "${0##*/}" "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
strip_leading_zeros()
|
||||
{
|
||||
_s=$1
|
||||
if [ -z "$_s" ]; then
|
||||
return
|
||||
fi
|
||||
while [ X"${_s#0}" != X"$_s" ]; do
|
||||
_s=${_s#0}
|
||||
done
|
||||
echo "${_s:-0}"
|
||||
}
|
||||
|
||||
strcmp()
|
||||
{
|
||||
_r=$(expr "X$1" "$2" "X$3")
|
||||
[ "${_r:-0}" -eq 1 ]
|
||||
}
|
||||
|
||||
# Returns comment from the ssh-agent if any is returned, otherwise it
|
||||
# returns the public key's fingerprint.
|
||||
get_ca_comment_from_sk()
|
||||
{
|
||||
ssh-keygen -lf "$1" | {
|
||||
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:-${pk_fp#*:}}"
|
||||
}
|
||||
}
|
||||
|
||||
_template_fmt()
|
||||
{
|
||||
_allowed_chars=$1
|
||||
_char=$2
|
||||
if [ "X$_char" = X% ]; then
|
||||
echo %
|
||||
return $?
|
||||
fi
|
||||
|
||||
case $_char in
|
||||
[$_allowed_chars])
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
_v=$(eval echo '${_template_fmt_'"$_char"':-}')
|
||||
if [ -z "$_v" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$_v"
|
||||
}
|
||||
|
||||
template()
|
||||
{
|
||||
_allowed=$1
|
||||
_s=$2
|
||||
_out=
|
||||
|
||||
while [ "${_s#*%}" != "$_s" ]; do
|
||||
_t=${_s#*%}
|
||||
_out=$_out${_s%"%"$_t}
|
||||
_s=$_t
|
||||
_c=${_s%${_s#?}}
|
||||
|
||||
_t=$(_template_fmt "$_allowed" "$_c")
|
||||
if [ $? -ne 0 ]; then
|
||||
return 1
|
||||
fi
|
||||
_out=$_out$_t
|
||||
|
||||
_s=${_s#$_c}
|
||||
done
|
||||
_out=$_out$_s
|
||||
|
||||
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 ca.pub ]; then
|
||||
err "no ca.pub found"
|
||||
fi
|
||||
if ! ssh-add $qflag $vflag -T ca.pub; then
|
||||
err "can't use CA key"
|
||||
fi
|
||||
if [ ! -d pubkeys/ ]; then
|
||||
err "no pubkeys directory found"
|
||||
fi
|
||||
|
||||
if [ ! -f serial.txt ]; then
|
||||
date -u +%Y%m%d000000000 >serial.txt
|
||||
fi
|
||||
read -r serial <serial.txt
|
||||
# Remove NNNNNNNNN suffix
|
||||
serial_date=${serial%?????????}
|
||||
current_date=$(date -u +%Y%m%d)
|
||||
if strcmp "$current_date" ">" "$serial_date"; then
|
||||
serial_date=$current_date
|
||||
serial_counter=0
|
||||
else
|
||||
# Remove YYYYmmdd prefix and leading
|
||||
serial_counter=$(strip_leading_zeros "${serial#????????}")
|
||||
fi
|
||||
serial=$(printf "%s%09u\n" "$serial_date" "$serial_counter")
|
||||
|
||||
_template_fmt_C=$(get_ca_comment_from_sk ca.pub)
|
||||
find pubkeys/ -type f -name '*.pub' ! -name '*-cert.pub' | {
|
||||
rc=0
|
||||
while read -r pk; do
|
||||
pkname=${pk%.pub}
|
||||
pkname=${pkname#pubkeys/}
|
||||
_template_fmt_f=$pkname
|
||||
|
||||
id=$(template Cf "$key_id_fmt")
|
||||
set -- -I "$id" -Us ca.pub $hflag $qflag $vflag \
|
||||
-V "$validity_interval" -z "$serial"
|
||||
|
||||
if $nflag; then
|
||||
principals=$(template f "$principals_fmt")
|
||||
ssh-keygen "$@" -n "$principals" "$pk"
|
||||
else
|
||||
ssh-keygen "$@" "$pk"
|
||||
fi || rc=1
|
||||
|
||||
serial_counter=$(($serial_counter + 1))
|
||||
if [ $serial_counter -ge 1000000000 ]; then
|
||||
err "can't issue more certificates today"
|
||||
fi
|
||||
serial=$(printf "%s%09u\n" "$serial_date" \
|
||||
"$serial_counter" | tee serial.txt)
|
||||
|
||||
if [ $rc -ne 0 ]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
return $rc
|
||||
}
|
||||
}
|
||||
|
||||
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 ca.pub ]; then
|
||||
err "no ca.pub found"
|
||||
fi
|
||||
|
||||
case $file in
|
||||
authorized_keys)
|
||||
if [ $# -gt 1 ]; then
|
||||
usage
|
||||
fi
|
||||
options=cert-authority${1:+,$1}
|
||||
printf "%s " "$options"
|
||||
;;
|
||||
known_hosts)
|
||||
if [ $# -gt 1 ]; then
|
||||
usage
|
||||
fi
|
||||
hostnames=${1:-}
|
||||
if [ -n "$hostnames" ]; then
|
||||
printf "@cert-authority %s " "$hostnames"
|
||||
else
|
||||
printf "@cert-authority "
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
err "unknown file \"$file\""
|
||||
;;
|
||||
esac
|
||||
|
||||
cat ca.pub
|
||||
}
|
||||
|
||||
set -u
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
usage
|
||||
fi
|
||||
cmd=$1
|
||||
shift
|
||||
|
||||
case $cmd in
|
||||
issue) main_issue "$@" ;;
|
||||
mkfile) main_mkfile "$@" ;;
|
||||
*) usage ;;
|
||||
esac
|
Loading…
Add table
Add a link
Reference in a new issue