Compare commits
15 Commits
Author | SHA1 | Date |
---|---|---|
Lucas | cbdede3433 | |
Lucas | fb6454a300 | |
Lucas | 09e8f9650d | |
Lucas | 3e3db13365 | |
Lucas | 2e9d469fde | |
Lucas | 70f4a1b190 | |
Lucas | 9e75968acc | |
Lucas | 24522541ad | |
Lucas | 7b1e34f625 | |
Lucas | e0bc09679e | |
Lucas | 06b4ec5354 | |
Lucas | d630c83ade | |
Lucas | cfd97ff74d | |
Lucas | e7aeadfa32 | |
Lucas | 497363b31b |
2
Makefile
2
Makefile
|
@ -19,7 +19,7 @@
|
||||||
chmod a+x $@
|
chmod a+x $@
|
||||||
|
|
||||||
P = cassh
|
P = cassh
|
||||||
V = 0
|
V = 2
|
||||||
|
|
||||||
PREFIX = /usr/local
|
PREFIX = /usr/local
|
||||||
MANPREFIX = ${PREFIX}/man
|
MANPREFIX = ${PREFIX}/man
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
.\" along with this software. If not, see
|
.\" along with this software. If not, see
|
||||||
.\" <http://creativecommons.org/publicdomain/zero/1.0/>.
|
.\" <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
.\"
|
.\"
|
||||||
.Dd April 07, 2022
|
.Dd April 20, 2022
|
||||||
.Dt CASSH-KEYFILE 1
|
.Dt CASSH-KEYFILE 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
|
|
@ -27,7 +27,7 @@ fi
|
||||||
cassh_command=$2
|
cassh_command=$2
|
||||||
needs_agent=false
|
needs_agent=false
|
||||||
case $cassh_command in
|
case $cassh_command in
|
||||||
issue)
|
issue|revoke)
|
||||||
needs_agent=true
|
needs_agent=true
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
68
cassh.1
68
cassh.1
|
@ -11,7 +11,7 @@
|
||||||
.\" along with this software. If not, see
|
.\" along with this software. If not, see
|
||||||
.\" <http://creativecommons.org/publicdomain/zero/1.0/>.
|
.\" <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
.\"
|
.\"
|
||||||
.Dd March 01, 2022
|
.Dd April 20, 2022
|
||||||
.Dt CASSH 1
|
.Dt CASSH 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
@ -30,13 +30,19 @@
|
||||||
.Bk -words
|
.Bk -words
|
||||||
.Cm mkfile
|
.Cm mkfile
|
||||||
.Ic authorized_keys
|
.Ic authorized_keys
|
||||||
.Op options
|
.Op options ...
|
||||||
.Ek
|
.Ek
|
||||||
.Nm
|
.Nm
|
||||||
.Bk -words
|
.Bk -words
|
||||||
.Cm mkfile
|
.Cm mkfile
|
||||||
.Ic known_hosts
|
.Ic known_hosts
|
||||||
.Op hostnames
|
.Op hostnames ...
|
||||||
|
.Ek
|
||||||
|
.Nm
|
||||||
|
.Bk -words
|
||||||
|
.Cm revoke
|
||||||
|
.Op Fl qv
|
||||||
|
.Ar
|
||||||
.Ek
|
.Ek
|
||||||
.Sh DESCRIPTION
|
.Sh DESCRIPTION
|
||||||
.Nm
|
.Nm
|
||||||
|
@ -56,9 +62,14 @@ A Certification Authority directory consists of a
|
||||||
.Pa ./ca.pub
|
.Pa ./ca.pub
|
||||||
file corresponding to the public key of it, a
|
file corresponding to the public key of it, a
|
||||||
.Pa ./pubkeys/
|
.Pa ./pubkeys/
|
||||||
directory which holds the public keys to be signed, and an optional
|
directory which holds the public keys to be signed, an optional
|
||||||
.Pa ./serial.txt
|
.Pa ./krl
|
||||||
file holding the current serial number for the issued certificates.
|
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
|
.Pp
|
||||||
The following commands are available to
|
The following commands are available to
|
||||||
.Nm :
|
.Nm :
|
||||||
|
@ -84,9 +95,15 @@ The recognized tokens are:
|
||||||
A literal
|
A literal
|
||||||
.Sq % .
|
.Sq % .
|
||||||
.It \&%C
|
.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
|
.It %f
|
||||||
The basename of the public key being signed.
|
The basename of the public key being signed, without
|
||||||
|
.Sq .pub
|
||||||
|
suffix.
|
||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
.Ar key_id
|
.Ar key_id
|
||||||
|
@ -100,37 +117,56 @@ accepts the tokens %% and %f.
|
||||||
After token expansion, all recognized options are passed down to
|
After token expansion, all recognized options are passed down to
|
||||||
.Xr ssh-keygen 1
|
.Xr ssh-keygen 1
|
||||||
process.
|
process.
|
||||||
.It Cm mkfile Ic authorized_keys Op Ar options
|
.It Cm mkfile Ic authorized_keys Op Ar options ...
|
||||||
Write an
|
Write an
|
||||||
.Ic authorized_keys
|
.Ic authorized_keys
|
||||||
file on standard output corresponding to the current Certification
|
file on standard output corresponding to the current Certification
|
||||||
Authority.
|
Authority.
|
||||||
.Ar options
|
.Ar options
|
||||||
is copied verbatim to the output, and
|
are concatenated with commas and copied verbatim to the output.
|
||||||
.Cm cert-authority
|
.Cm cert-authority
|
||||||
is always added.
|
is always added to the options list.
|
||||||
See
|
See
|
||||||
.Xr sshd 8 AUTHORIZED_KEYS FILE FORMAT
|
.Xr sshd 8 AUTHORIZED_KEYS FILE FORMAT
|
||||||
for details.
|
for details.
|
||||||
.It Cm mkfile Ic known_hosts Op Ar hostnames
|
.It Cm mkfile Ic known_hosts Op Ar hostnames ...
|
||||||
Write a
|
Write a
|
||||||
.Ic known_hosts
|
.Ic known_hosts
|
||||||
file on standard output corresponding to the current Certification
|
file on standard output corresponding to the current Certification
|
||||||
Authority.
|
Authority.
|
||||||
.Ar hostnames
|
.Ar hostnames
|
||||||
is copied verbatim to the output.
|
are concatenated with commas and copied verbatim to the output.
|
||||||
See
|
See
|
||||||
.Xr sshd 8 SSH_KNOWN_HOSTS FILE FORMAT
|
.Xr sshd 8 SSH_KNOWN_HOSTS FILE FORMAT
|
||||||
for details.
|
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
|
.El
|
||||||
.Sh FILES
|
.Sh FILES
|
||||||
.Bl -tag -width MMMMMMMMMMMMMM -compact
|
.Bl -tag -width MMMMMMMMMMMMMMMMMM -compact
|
||||||
.It Pa ./ca.pub
|
.It Pa ./ca.pub
|
||||||
Certification Authority public key
|
Certification Authority public key
|
||||||
.It Pa ./pubkeys/
|
.It Pa ./pubkeys/
|
||||||
Directory containing the public keys to be signed
|
Directory containing the public keys to be signed
|
||||||
.It Pa ./serial.txt
|
.It Pa ./krl
|
||||||
Last issued serial
|
Key Revocation List
|
||||||
|
.It Pa ./ca_serial.txt
|
||||||
|
Last issued serial for certificates
|
||||||
|
.It Pa ./krl_serial.txt
|
||||||
|
Last issued serial for KRLs
|
||||||
.El
|
.El
|
||||||
.Sh EXIT STATUS
|
.Sh EXIT STATUS
|
||||||
.Ex -std
|
.Ex -std
|
||||||
|
|
204
cassh.sh
204
cassh.sh
|
@ -17,8 +17,9 @@ usage()
|
||||||
Usage:
|
Usage:
|
||||||
${0##*/} issue [-hqv] [-I key_id] [-n principals]
|
${0##*/} issue [-hqv] [-I key_id] [-n principals]
|
||||||
[-V validity_interval]
|
[-V validity_interval]
|
||||||
${0##*/} mkfile authorized_keys [options]
|
${0##*/} mkfile authorized_keys [options ...]
|
||||||
${0##*/} mkfile known_hosts [hostnames]
|
${0##*/} mkfile known_hosts [hostnames ...]
|
||||||
|
${0##*/} revoke [-qv] file ...
|
||||||
EOF
|
EOF
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
@ -29,29 +30,11 @@ err()
|
||||||
exit 1
|
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
|
ssh-keygen -lf "$1" 2>/dev/null | {
|
||||||
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
|
read -r pk_sz pk_fp pk_extra
|
||||||
_comment=$(ssh-add -l | while read -r sk_sz sk_fp sk_extra; do
|
_comment=$(ssh-add -l | while read -r sk_sz sk_fp sk_extra; do
|
||||||
if [ "X$sk_fp" = "X$pk_fp" ]; then
|
if [ "X$sk_fp" = "X$pk_fp" ]; then
|
||||||
|
@ -59,57 +42,67 @@ get_ca_comment_from_sk()
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
done)
|
done)
|
||||||
echo "${_comment:-${pk_fp#*:}}"
|
echo "${_comment:-}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_template_fmt()
|
format()
|
||||||
{
|
{
|
||||||
_allowed_chars=$1
|
_s=$1
|
||||||
_char=$2
|
shift
|
||||||
if [ "X$_char" = X% ]; then
|
|
||||||
echo %
|
|
||||||
return $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
case $_char in
|
_cleanup=unset
|
||||||
[$_allowed_chars])
|
while [ $# -ge 2 ]; do
|
||||||
;;
|
_k=$1 _v=$2
|
||||||
*)
|
shift 2
|
||||||
return 1
|
case $_k in
|
||||||
;;
|
[A-Za-z])
|
||||||
esac
|
;;
|
||||||
|
*)
|
||||||
_v=$(eval echo '${_template_fmt_'"$_char"':-}')
|
return 1
|
||||||
if [ -z "$_v" ]; then
|
;;
|
||||||
|
esac
|
||||||
|
eval "_token_${_k}=\$_v"
|
||||||
|
_cleanup=$_cleanup" _token_${_k}"
|
||||||
|
done
|
||||||
|
if [ $# -ne 0 ]; then
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "$_v"
|
|
||||||
}
|
|
||||||
|
|
||||||
template()
|
|
||||||
{
|
|
||||||
_allowed=$1
|
|
||||||
_s=$2
|
|
||||||
_out=
|
_out=
|
||||||
|
|
||||||
while [ "${_s#*%}" != "$_s" ]; do
|
while [ "${_s#*%}" != "$_s" ]; do
|
||||||
_t=${_s#*%}
|
_t=${_s#*%}
|
||||||
_out=$_out${_s%"%"$_t}
|
_out=$_out${_s%"%"$_t}
|
||||||
_s=$_t
|
_s=$_t
|
||||||
_c=${_s%${_s#?}}
|
_c=${_s%${_s#?}}
|
||||||
|
|
||||||
_t=$(_template_fmt "$_allowed" "$_c")
|
if [ -z "${_c:-}" ]; then
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
return 1
|
return 1
|
||||||
|
elif [ X"${_c}" = X% ]; then
|
||||||
|
_out=$_out%
|
||||||
|
else
|
||||||
|
eval "_out=$_out\$_token_${_c}" || return 1
|
||||||
fi
|
fi
|
||||||
_out=$_out$_t
|
|
||||||
|
|
||||||
_s=${_s#$_c}
|
_s=${_s#$_c}
|
||||||
done
|
done
|
||||||
_out=$_out$_s
|
_out=$_out$_s
|
||||||
|
|
||||||
|
eval "$_cleanup"
|
||||||
|
|
||||||
|
echo "$_out"
|
||||||
|
}
|
||||||
|
|
||||||
|
strjoin()
|
||||||
|
{
|
||||||
|
_c=$1
|
||||||
|
shift
|
||||||
|
|
||||||
|
_out=
|
||||||
|
for _s; do
|
||||||
|
_out=${_out:+$_out$_c}$_s
|
||||||
|
done
|
||||||
|
|
||||||
echo "$_out"
|
echo "$_out"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,64 +131,42 @@ main_issue()
|
||||||
usage
|
usage
|
||||||
fi
|
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
|
if [ ! -f "$PATH_CA_SERIAL" ]; then
|
||||||
date -u +%Y%m%d000000000 >"$PATH_CA_SERIAL"
|
echo 1 >"$PATH_CA_SERIAL"
|
||||||
fi
|
fi
|
||||||
read -r serial <"$PATH_CA_SERIAL"
|
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")
|
if [ ! -d "$PATH_PUBKEYS_DIR" ]; then
|
||||||
find "$PATH_PUBKEYS_DIR/" -type f -name '*.pub' ! -name '*-cert.pub' | {
|
exit 0
|
||||||
rc=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
|
while read -r pk; do
|
||||||
pkname=${pk%.pub}
|
pkname=${pk%.pub}
|
||||||
pkname=${pkname#$PATH_PUBKEYS_DIR/}
|
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" \
|
set -- -I "$id" -Us "$PATH_CA_PUB" \
|
||||||
$hflag $qflag $vflag \
|
$hflag $qflag $vflag \
|
||||||
-V "$validity_interval" -z "$serial"
|
-V "$validity_interval" -z "$serial"
|
||||||
|
|
||||||
if $nflag; then
|
if $nflag; then
|
||||||
principals=$(template f "$principals_fmt")
|
principals=$(format "$principals_fmt" \
|
||||||
|
f "$pkname")
|
||||||
ssh-keygen "$@" -n "$principals" "$pk"
|
ssh-keygen "$@" -n "$principals" "$pk"
|
||||||
else
|
else
|
||||||
ssh-keygen "$@" "$pk"
|
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
|
fi
|
||||||
serial=$(printf "%s%09u\n" "$serial_date" \
|
if [ $? -ne 0 ]; then
|
||||||
"$serial_counter" | tee "$PATH_CA_SERIAL")
|
exit 1
|
||||||
|
|
||||||
if [ $rc -ne 0 ]; then
|
|
||||||
break
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
serial=$(($serial + 1))
|
||||||
|
echo $serial >"$PATH_CA_SERIAL"
|
||||||
done
|
done
|
||||||
return $rc
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,19 +190,11 @@ main_mkfile()
|
||||||
|
|
||||||
case $file in
|
case $file in
|
||||||
authorized_keys)
|
authorized_keys)
|
||||||
if [ $# -gt 1 ]; then
|
printf "%s " "$(strjoin , cert-authority "$@")"
|
||||||
usage
|
|
||||||
fi
|
|
||||||
options=cert-authority${1:+,$1}
|
|
||||||
printf "%s " "$options"
|
|
||||||
;;
|
;;
|
||||||
known_hosts)
|
known_hosts)
|
||||||
if [ $# -gt 1 ]; then
|
if [ $# -gt 0 ]; then
|
||||||
usage
|
printf "@cert-authority %s " "$(strjoin , "$@")"
|
||||||
fi
|
|
||||||
hostnames=${1:-}
|
|
||||||
if [ -n "$hostnames" ]; then
|
|
||||||
printf "@cert-authority %s " "$hostnames"
|
|
||||||
else
|
else
|
||||||
printf "@cert-authority "
|
printf "@cert-authority "
|
||||||
fi
|
fi
|
||||||
|
@ -244,10 +207,42 @@ main_mkfile()
|
||||||
cat "$PATH_CA_PUB"
|
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
|
set -u
|
||||||
|
|
||||||
PATH_CA_PUB=./ca.pub
|
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
|
PATH_PUBKEYS_DIR=./pubkeys
|
||||||
|
|
||||||
if [ $# -lt 1 ]; then
|
if [ $# -lt 1 ]; then
|
||||||
|
@ -259,5 +254,6 @@ shift
|
||||||
case $cmd in
|
case $cmd in
|
||||||
issue) main_issue "$@" ;;
|
issue) main_issue "$@" ;;
|
||||||
mkfile) main_mkfile "$@" ;;
|
mkfile) main_mkfile "$@" ;;
|
||||||
|
revoke) main_revoke "$@" ;;
|
||||||
*) usage ;;
|
*) usage ;;
|
||||||
esac
|
esac
|
||||||
|
|
Loading…
Reference in New Issue