Compare commits

...

15 Commits
perl ... main

Author SHA1 Message Date
Lucas cbdede3433 Release v2 2022-09-03 14:02:37 +00:00
Lucas fb6454a300 Sort find output and avoid duplicated /
Some find implementations will produce results like "dir//a" if given
"find dir/".
2022-09-03 13:57:43 +00:00
Lucas 09e8f9650d Add revoke to usage, missed in 9e75968acc 2022-06-06 23:55:28 +00:00
Lucas 3e3db13365 Release v1 2022-04-20 16:48:53 +00:00
Lucas 2e9d469fde Update manpages date 2022-04-20 16:47:37 +00:00
Lucas 70f4a1b190 Rename serial.txt -> ca_serial.txt 2022-04-20 16:45:47 +00:00
Lucas 9e75968acc Add revoke command 2022-04-20 16:44:37 +00:00
Lucas 24522541ad Start serial from 1, as 0 can't be revoked 2022-04-20 16:11:34 +00:00
Lucas 7b1e34f625 Refactor: join strings in shell instead of relying on paste 2022-04-20 12:47:40 +00:00
Lucas e0bc09679e - ssh-keygen will fail hard if it doesn't find what it needs, even when
quiet
- Don't find "$PATH_PUBKEYS_DIR/" if it doesn't exist
- Retrieve CA comment only inside find, when it's really needed
- Quiet down ssh-keygen in get_ca_sk_comment_from_pk if ca.pub is not
  present
2022-04-19 04:05:49 +00:00
Lucas 06b4ec5354 Tell that .pub is stripped in cassh.1
Also fix a small typo introduced in previous commit.
2022-04-19 04:04:54 +00:00
Lucas d630c83ade Make mkfile accept multiple parameters 2022-04-19 03:49:02 +00:00
Lucas cfd97ff74d Rewrite token formatter engine 2022-04-19 03:41:36 +00:00
Lucas e7aeadfa32 Rename get_ca_comment_from_sk -> get_ca_sk_comment_from_pk
- Better explain what it does
- Don't fallback to public key fingerprint in function
- Use "cassh" as the fallback if the comment is empty
- Adjust manpage
2022-04-19 03:41:36 +00:00
Lucas 497363b31b Make serial a simple counter
While at it, error out when ssh-keygen fails, not after writing the
serial.
2022-04-19 03:41:28 +00:00
5 changed files with 155 additions and 123 deletions

View File

@ -19,7 +19,7 @@
chmod a+x $@
P = cassh
V = 0
V = 2
PREFIX = /usr/local
MANPREFIX = ${PREFIX}/man

View File

@ -11,7 +11,7 @@
.\" along with this software. If not, see
.\" <http://creativecommons.org/publicdomain/zero/1.0/>.
.\"
.Dd April 07, 2022
.Dd April 20, 2022
.Dt CASSH-KEYFILE 1
.Os
.Sh NAME

View File

@ -27,7 +27,7 @@ fi
cassh_command=$2
needs_agent=false
case $cassh_command in
issue)
issue|revoke)
needs_agent=true
;;
esac

68
cassh.1
View File

@ -11,7 +11,7 @@
.\" along with this software. If not, see
.\" <http://creativecommons.org/publicdomain/zero/1.0/>.
.\"
.Dd March 01, 2022
.Dd April 20, 2022
.Dt CASSH 1
.Os
.Sh NAME
@ -30,13 +30,19 @@
.Bk -words
.Cm mkfile
.Ic authorized_keys
.Op options
.Op options ...
.Ek
.Nm
.Bk -words
.Cm mkfile
.Ic known_hosts
.Op hostnames
.Op hostnames ...
.Ek
.Nm
.Bk -words
.Cm revoke
.Op Fl qv
.Ar
.Ek
.Sh DESCRIPTION
.Nm
@ -56,9 +62,14 @@ A Certification Authority directory consists of a
.Pa ./ca.pub
file corresponding to the public key of it, a
.Pa ./pubkeys/
directory which holds the public keys to be signed, and an optional
.Pa ./serial.txt
file holding the current serial number for the issued certificates.
directory which holds the public keys to be signed, an optional
.Pa ./krl
file corresponding to the last issued Key Revocation List, and optional
.Pa ./ca_serial.txt
and
.Pa ./krl_serial.txt
files corresponding to the current serial number for the issued certificates
and Key Revocation Lists.
.Pp
The following commands are available to
.Nm :
@ -84,9 +95,15 @@ The recognized tokens are:
A literal
.Sq % .
.It \&%C
The Certification Authority private key comment.
The Certification Authority private key comment field as reported by
.Xr ssh-add 1 ,
or the string
.Sq cassh
if there is no comment reported.
.It %f
The basename of the public key being signed.
The basename of the public key being signed, without
.Sq .pub
suffix.
.El
.Pp
.Ar key_id
@ -100,37 +117,56 @@ accepts the tokens %% and %f.
After token expansion, all recognized options are passed down to
.Xr ssh-keygen 1
process.
.It Cm mkfile Ic authorized_keys Op Ar options
.It Cm mkfile Ic authorized_keys Op Ar options ...
Write an
.Ic authorized_keys
file on standard output corresponding to the current Certification
Authority.
.Ar options
is copied verbatim to the output, and
are concatenated with commas and copied verbatim to the output.
.Cm cert-authority
is always added.
is always added to the options list.
See
.Xr sshd 8 AUTHORIZED_KEYS FILE FORMAT
for details.
.It Cm mkfile Ic known_hosts Op Ar hostnames
.It Cm mkfile Ic known_hosts Op Ar hostnames ...
Write a
.Ic known_hosts
file on standard output corresponding to the current Certification
Authority.
.Ar hostnames
is copied verbatim to the output.
are concatenated with commas and copied verbatim to the output.
See
.Xr sshd 8 SSH_KNOWN_HOSTS FILE FORMAT
for details.
.It Cm revoke Oo Fl qv Oc Ar
Generates a Key Revocation List for the current Certification Authority.
All recognized options are passed down to
.Xr ssh-keygen 1
process.
See
.Xr ssh-keygen 1 KEY REVOCATION LISTS
for details on the file format for input files.
If
.Pa ./krl
exists,
.Cm revoke
will update.
.Pa ./krl
can be synced back with the input files by first removing it.
.El
.Sh FILES
.Bl -tag -width MMMMMMMMMMMMMM -compact
.Bl -tag -width MMMMMMMMMMMMMMMMMM -compact
.It Pa ./ca.pub
Certification Authority public key
.It Pa ./pubkeys/
Directory containing the public keys to be signed
.It Pa ./serial.txt
Last issued serial
.It Pa ./krl
Key Revocation List
.It Pa ./ca_serial.txt
Last issued serial for certificates
.It Pa ./krl_serial.txt
Last issued serial for KRLs
.El
.Sh EXIT STATUS
.Ex -std

204
cassh.sh
View File

@ -17,8 +17,9 @@ usage()
Usage:
${0##*/} issue [-hqv] [-I key_id] [-n principals]
[-V validity_interval]
${0##*/} mkfile authorized_keys [options]
${0##*/} mkfile known_hosts [hostnames]
${0##*/} mkfile authorized_keys [options ...]
${0##*/} mkfile known_hosts [hostnames ...]
${0##*/} revoke [-qv] file ...
EOF
exit 1
}
@ -29,29 +30,11 @@ err()
exit 1
}
strip_leading_zeros()
# Returns the comment from the loaded secret key in ssh-agent, if any is
# present.
get_ca_sk_comment_from_pk()
{
_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" | {
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
@ -59,57 +42,67 @@ get_ca_comment_from_sk()
break
fi
done)
echo "${_comment:-${pk_fp#*:}}"
echo "${_comment:-}"
}
}
_template_fmt()
format()
{
_allowed_chars=$1
_char=$2
if [ "X$_char" = X% ]; then
echo %
return $?
fi
_s=$1
shift
case $_char in
[$_allowed_chars])
;;
*)
return 1
;;
esac
_v=$(eval echo '${_template_fmt_'"$_char"':-}')
if [ -z "$_v" ]; then
_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
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
if [ -z "${_c:-}" ]; then
return 1
elif [ X"${_c}" = X% ]; then
_out=$_out%
else
eval "_out=$_out\$_token_${_c}" || return 1
fi
_out=$_out$_t
_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"
}
@ -138,64 +131,42 @@ main_issue()
usage
fi
if [ ! -f "$PATH_CA_PUB" ]; then
err "no $PATH_CA_PUB found"
fi
if ! ssh-add $qflag $vflag -T "$PATH_CA_PUB"; then
err "can't use CA key"
fi
if [ ! -d "$PATH_PUBKEYS_DIR/" ]; then
err "no pubkeys directory found"
fi
if [ ! -f "$PATH_CA_SERIAL" ]; then
date -u +%Y%m%d000000000 >"$PATH_CA_SERIAL"
echo 1 >"$PATH_CA_SERIAL"
fi
read -r serial <"$PATH_CA_SERIAL"
# 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 "$PATH_CA_PUB")
find "$PATH_PUBKEYS_DIR/" -type f -name '*.pub' ! -name '*-cert.pub' | {
rc=0
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/}
_template_fmt_f=$pkname
id=$(template Cf "$key_id_fmt")
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=$(template f "$principals_fmt")
principals=$(format "$principals_fmt" \
f "$pkname")
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 "$PATH_CA_SERIAL")
if [ $rc -ne 0 ]; then
break
if [ $? -ne 0 ]; then
exit 1
fi
serial=$(($serial + 1))
echo $serial >"$PATH_CA_SERIAL"
done
return $rc
}
}
@ -219,19 +190,11 @@ main_mkfile()
case $file in
authorized_keys)
if [ $# -gt 1 ]; then
usage
fi
options=cert-authority${1:+,$1}
printf "%s " "$options"
printf "%s " "$(strjoin , cert-authority "$@")"
;;
known_hosts)
if [ $# -gt 1 ]; then
usage
fi
hostnames=${1:-}
if [ -n "$hostnames" ]; then
printf "@cert-authority %s " "$hostnames"
if [ $# -gt 0 ]; then
printf "@cert-authority %s " "$(strjoin , "$@")"
else
printf "@cert-authority "
fi
@ -244,10 +207,42 @@ main_mkfile()
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=./serial.txt
PATH_CA_SERIAL=./ca_serial.txt
PATH_KRL=./krl
PATH_KRL_SERIAL=./krl_serial.txt
PATH_PUBKEYS_DIR=./pubkeys
if [ $# -lt 1 ]; then
@ -259,5 +254,6 @@ shift
case $cmd in
issue) main_issue "$@" ;;
mkfile) main_mkfile "$@" ;;
revoke) main_revoke "$@" ;;
*) usage ;;
esac