From fe858efc53a1d2e70daf5e88d40312482b4a14d1 Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 19 Apr 2022 02:09:39 +0000 Subject: [PATCH 01/16] Reimplement in Perl --- cassh.pl | 299 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 cassh.pl diff --git a/cassh.pl b/cassh.pl new file mode 100644 index 0000000..e5b558e --- /dev/null +++ b/cassh.pl @@ -0,0 +1,299 @@ +#!/usr/bin/env perl +# 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 +# . + +use v5.14; +use strict; +use warnings; + +package SerialFile v1.0.0; + +use overload '0+' => \&value; + +sub new ($$) +{ + my $class = shift; + my $self = {}; + bless $self, $class; + return $self->_init(@_); +} + +sub _init ($$) +{ + my ($self, $file) = @_; + + my $mode = -f $file ? "+<" : ">"; + open(my $fh, $mode, $file) or die "can't open $file: $!"; + my @lines = <$fh>; + + my $counter; + if (@lines == 0) { + $counter = 0; + } elsif (@lines == 1 && $lines[0] =~ /^(\d+)$/) { + $counter = $1; + } else { + return undef; + } + + $self->{_fh} = $fh; + $self->{_counter} = $counter; + + return $self; +} + +sub inc ($;$) +{ + use integer; + my ($self, $inc) = @_; + $self->{_counter} += $inc // 1; + return $self; +} + +sub value ($) +{ + my $self = shift; + return $self->{_counter}; +} + +sub commit ($) +{ + my $self = shift; + + truncate($self->{_fh}, 0) or return 0; + seek($self->{_fh}, 0, 0) or return 0; + + return say {$self->{_fh}} $self->value(); +} + +sub DESTROY ($) +{ + local ($., $@, $!, $^E, $?); + my $self = shift; + close($self->{_fh}); +} + +package FormatToken v1.0.0; + +use List::Util qw(all); + +sub new ($) +{ + return bless {_tokens => {"%" => "%"}}, shift; +} + +sub register ($;%) +{ + my ($self, %pairs) = @_; + for (keys %pairs) { + die "can't register $_" if !/^[A-Za-z]$/; + $self->{_tokens}->{$_} = $pairs{$_}; + } + return $self; +} + +sub deregister ($;%) +{ + my ($self, %pairs) = @_; + delete $self->{_tokens}->{$_} for (keys %pairs); + return $self; +} + +sub format ($$) +{ + my ($self, $in) = @_; + die "can't format \"$in\"" unless + all {defined $self->{_tokens}->{$_}} ($in =~ /%([A-Za-z%])/g); + return $in =~ s/%([A-Za-z%])/$self->{_tokens}->{$1}/egr; +} + +package main; + +use Getopt::Long qw(:config posix_default bundling no_ignore_case); + +my $PATH_CA_PUB = "./ca.pub"; +my $PATH_CA_SERIAL = "./serial.txt"; +my $PATH_PUBKEYS_DIR = "./pubkeys"; + +my %COMMANDS = ( + issue => \&main_issue, + mkfile => \&main_mkfile, +); + +my $PROGNAME = ($0 =~ s,.*/,,r); + +sub usage () +{ + print STDERR <; + close($fh); + + return $s; +} + +# Returns comment from the ssh-agent if any is returned, otherwise it +# returns the public key's fingerprint. +sub get_ca_sk_comment_from_pk ($) +{ + my $f = shift; + + my ($fh, @ssh_keygen_lines, @ssh_add_lines); + + open($fh, "-|", "ssh-keygen", "-l", "-f", $f) or err "can't fork: $!"; + @ssh_keygen_lines = <$fh>; + close($fh); + + open($fh, "-|", "ssh-add", "-l") or err "can't fork: $!"; + @ssh_add_lines = <$fh>; + close($fh); + + my $comment; + OUTER: foreach my $ssh_keygen_line (@ssh_keygen_lines) { + chomp($ssh_keygen_line); + my @ssh_keygen_parts = split(/ /, $ssh_keygen_line, 3); + foreach my $ssh_add_line (@ssh_add_lines) { + chomp($ssh_add_line); + my @ssh_add_parts = split(/ /, $ssh_add_line, 3); + if ($ssh_keygen_parts[1] eq $ssh_add_parts[1]) { + my $s = $ssh_add_parts[2]; + $comment = substr($s, 0, rindex($s, " ")); + last OUTER; + } + } + } + + return $comment; +} + +sub get_pubkeys_files ($) +{ + my $d = shift; + + opendir(my $dh, "$d") or err "can't open directory $d"; + my @files = map {"$d/$_"} + grep {-f "$d/$_" && /\.pub$/ && !/-cert\.pub/} readdir($dh); + closedir($dh); + + return @files; +} + + +sub main_issue () +{ + my ($host, $key_id_fmt, $principals_fmt, $quiet, $verbose, + $validity_interval); + + $validity_interval = "always:forever"; + $key_id_fmt = "%C/%f"; + $verbose = 0; + GetOptions( + "h" => \$host, + "I=s" => \$key_id_fmt, + "n=s" => \$principals_fmt, + "q" => \$quiet, + "v+" => \$verbose, + "V=s" => \$validity_interval, + ) or usage; + + usage if @ARGV != 0; + + my ($hflag, $qflag, $vflag); + $hflag = "-h" if defined($host); + $qflag = "-q" if defined($quiet); + $vflag = "-" . ("v" x $verbose) if $verbose > 0; + + my @files = get_pubkeys_files($PATH_PUBKEYS_DIR); + exit 0 if @files == 0; + + my $formatter = FormatToken->new(); + my $ca_sk_comment = get_ca_sk_comment_from_pk($PATH_CA_PUB) // "cassh"; + $formatter->register(C => $ca_sk_comment); + + my $serial = SerialFile->new($PATH_CA_SERIAL); + + # Doing individual calls to ssh-keygen allows for using the filename as + # a token in principals and key_id format. + foreach my $file (@files) { + $formatter->register(f => $file =~ s,.*/(.*)\.pub$,$1,r); + + my ($key_id, $principals); + $key_id = $formatter->format($key_id_fmt); + $principals = $formatter->format($principals_fmt) if + defined($principals_fmt); + + my @cmdopts = ("-I", $key_id, "-U", "-s", $PATH_CA_PUB, + "-V", $validity_interval, "-z", $serial); + push(@cmdopts, "-h") if defined($host); + push(@cmdopts, $qflag) if defined($qflag); + push(@cmdopts, $vflag) if defined($vflag); + push(@cmdopts, "-n", $principals) if defined($principals); + + system("ssh-keygen", @cmdopts, $file); + if ($? == -1) { + err "ssh-keygen: $!"; + } elsif ($? != 0) { + exit $? >> 8; + } + + $serial->inc()->commit() or err "can't save serial"; + } +} + +sub main_mkfile () +{ + usage if @ARGV < 1; + my $file = shift @ARGV; + + err "no $PATH_CA_PUB found" if ! -f $PATH_CA_PUB; + + if ($file eq "authorized_keys") { + print join(" ", join(",", "cert-authority", @ARGV), + slurp($PATH_CA_PUB)); + } elsif ($file eq "known_hosts") { + print join(" ", grep {$_ ne ""} ('@cert-authority', + join(",", @ARGV), slurp($PATH_CA_PUB))); + } else { + err "unknown file $file"; + } +} + +sub main () +{ + usage if @ARGV < 1; + + my $cmd = shift @ARGV; + + usage if !defined($COMMANDS{$cmd}); + $COMMANDS{$cmd}->(); +} + +main(); From 497363b31b240a4b3788bd889398e237e93cc09d Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 19 Apr 2022 03:41:28 +0000 Subject: [PATCH 02/16] Make serial a simple counter While at it, error out when ssh-keygen fails, not after writing the serial. --- cassh.sh | 48 ++++++------------------------------------------ 1 file changed, 6 insertions(+), 42 deletions(-) diff --git a/cassh.sh b/cassh.sh index 209b2b7..f3ea2b0 100644 --- a/cassh.sh +++ b/cassh.sh @@ -29,24 +29,6 @@ err() 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() @@ -149,24 +131,12 @@ main_issue() fi if [ ! -f "$PATH_CA_SERIAL" ]; then - date -u +%Y%m%d000000000 >"$PATH_CA_SERIAL" + echo 0 >"$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 while read -r pk; do pkname=${pk%.pub} pkname=${pkname#$PATH_PUBKEYS_DIR/} @@ -182,20 +152,14 @@ main_issue() 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 } } From e7aeadfa326368ce7b10e149144dccad1012f9e1 Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 19 Apr 2022 03:41:36 +0000 Subject: [PATCH 03/16] 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 --- cassh.1 | 8 ++++++-- cassh.sh | 11 ++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/cassh.1 b/cassh.1 index aa4400d..4582abc 100644 --- a/cassh.1 +++ b/cassh.1 @@ -11,7 +11,7 @@ .\" along with this software. If not, see .\" . .\" -.Dd March 01, 2022 +.Dd April 19, 2022 .Dt CASSH 1 .Os .Sh NAME @@ -84,7 +84,11 @@ 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. .El diff --git a/cassh.sh b/cassh.sh index f3ea2b0..845d9ac 100644 --- a/cassh.sh +++ b/cassh.sh @@ -29,9 +29,9 @@ err() exit 1 } -# Returns comment from the ssh-agent if any is returned, otherwise it -# returns the public key's fingerprint. -get_ca_comment_from_sk() +# 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" | { read -r pk_sz pk_fp pk_extra @@ -41,7 +41,7 @@ get_ca_comment_from_sk() break fi done) - echo "${_comment:-${pk_fp#*:}}" + echo "${_comment:-}" } } @@ -135,7 +135,8 @@ main_issue() fi read -r serial <"$PATH_CA_SERIAL" - _template_fmt_C=$(get_ca_comment_from_sk "$PATH_CA_PUB") + ca_comment=$(get_ca_sk_comment_from_pk "$PATH_CA_PUB") + _template_fmt_C=${ca_comment:-cassh} find "$PATH_PUBKEYS_DIR/" -type f -name '*.pub' ! -name '*-cert.pub' | { while read -r pk; do pkname=${pk%.pub} From cfd97ff74d75bf23d11d3c89e722d889c948c8ec Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 19 Apr 2022 03:41:36 +0000 Subject: [PATCH 04/16] Rewrite token formatter engine --- cassh.sh | 61 +++++++++++++++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/cassh.sh b/cassh.sh index 845d9ac..52d79ce 100644 --- a/cassh.sh +++ b/cassh.sh @@ -45,53 +45,50 @@ get_ca_sk_comment_from_pk() } } -_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" } @@ -136,20 +133,20 @@ main_issue() read -r serial <"$PATH_CA_SERIAL" ca_comment=$(get_ca_sk_comment_from_pk "$PATH_CA_PUB") - _template_fmt_C=${ca_comment:-cassh} + : ${ca_comment:=cassh} find "$PATH_PUBKEYS_DIR/" -type f -name '*.pub' ! -name '*-cert.pub' | { 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" From d630c83ade3e2f5e0b7ab457daed398a124bd3e1 Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 19 Apr 2022 03:49:02 +0000 Subject: [PATCH 05/16] Make mkfile accept multiple parameters --- cassh.1 | 14 +++++++------- cassh.sh | 14 ++++---------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/cassh.1 b/cassh.1 index 4582abc..d6220ab 100644 --- a/cassh.1 +++ b/cassh.1 @@ -30,13 +30,13 @@ .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 .Sh DESCRIPTION .Nm @@ -104,25 +104,25 @@ 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 commans and copied verbatim to the output. See .Xr sshd 8 SSH_KNOWN_HOSTS FILE FORMAT for details. diff --git a/cassh.sh b/cassh.sh index 52d79ce..7a2be9e 100644 --- a/cassh.sh +++ b/cassh.sh @@ -17,8 +17,8 @@ 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 ...] EOF exit 1 } @@ -181,17 +181,11 @@ main_mkfile() case $file in authorized_keys) - if [ $# -gt 1 ]; then - usage - fi - options=cert-authority${1:+,$1} + options=$(printf "%s\n" cert-authority "$@" | paste -sd , -) printf "%s " "$options" ;; known_hosts) - if [ $# -gt 1 ]; then - usage - fi - hostnames=${1:-} + hostnames=$(printf "%s\n" "$@" | paste -sd , -) if [ -n "$hostnames" ]; then printf "@cert-authority %s " "$hostnames" else From 06b4ec535420d70671f99bda3fbb1347a1353515 Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 19 Apr 2022 04:04:54 +0000 Subject: [PATCH 06/16] Tell that .pub is stripped in cassh.1 Also fix a small typo introduced in previous commit. --- cassh.1 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cassh.1 b/cassh.1 index d6220ab..d316ed7 100644 --- a/cassh.1 +++ b/cassh.1 @@ -90,7 +90,9 @@ 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 @@ -122,7 +124,7 @@ Write a file on standard output corresponding to the current Certification Authority. .Ar hostnames -are concatenated with commans and 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. From e0bc09679e0a82f568689ccf78178291f05338d6 Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 19 Apr 2022 04:05:49 +0000 Subject: [PATCH 07/16] - 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 --- cassh.sh | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/cassh.sh b/cassh.sh index 7a2be9e..d297165 100644 --- a/cassh.sh +++ b/cassh.sh @@ -33,7 +33,7 @@ err() # present. get_ca_sk_comment_from_pk() { - 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 @@ -117,24 +117,18 @@ 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 echo 0 >"$PATH_CA_SERIAL" fi read -r serial <"$PATH_CA_SERIAL" - ca_comment=$(get_ca_sk_comment_from_pk "$PATH_CA_PUB") - : ${ca_comment:=cassh} + if [ ! -d "$PATH_PUBKEYS_DIR" ]; then + exit 0 + fi find "$PATH_PUBKEYS_DIR/" -type f -name '*.pub' ! -name '*-cert.pub' | { + 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/} From 7b1e34f6259353a121dce485c56ffe635f84262d Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 20 Apr 2022 12:47:40 +0000 Subject: [PATCH 08/16] Refactor: join strings in shell instead of relying on paste --- cassh.sh | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/cassh.sh b/cassh.sh index d297165..1b19f91 100644 --- a/cassh.sh +++ b/cassh.sh @@ -92,6 +92,19 @@ format() echo "$_out" } +strjoin() +{ + _c=$1 + shift + + _out= + for _s; do + _out=${_out:+$_out$_c}$_s + done + + echo "$_out" +} + main_issue() { hflag= @@ -175,13 +188,11 @@ main_mkfile() case $file in authorized_keys) - options=$(printf "%s\n" cert-authority "$@" | paste -sd , -) - printf "%s " "$options" + printf "%s " "$(strjoin , cert-authority "$@")" ;; known_hosts) - hostnames=$(printf "%s\n" "$@" | paste -sd , -) - if [ -n "$hostnames" ]; then - printf "@cert-authority %s " "$hostnames" + if [ $# -gt 0 ]; then + printf "@cert-authority %s " "$(strjoin , "$@")" else printf "@cert-authority " fi From 24522541ade031455cc23de518ac733bd230aac3 Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 20 Apr 2022 16:11:34 +0000 Subject: [PATCH 09/16] Start serial from 1, as 0 can't be revoked --- cassh.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cassh.sh b/cassh.sh index 1b19f91..298806e 100644 --- a/cassh.sh +++ b/cassh.sh @@ -131,7 +131,7 @@ main_issue() fi if [ ! -f "$PATH_CA_SERIAL" ]; then - echo 0 >"$PATH_CA_SERIAL" + echo 1 >"$PATH_CA_SERIAL" fi read -r serial <"$PATH_CA_SERIAL" From 9e75968accb8b82f7ab0715e9d4528896256cf8f Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 20 Apr 2022 16:44:37 +0000 Subject: [PATCH 10/16] Add revoke command --- cassh-keyfile.sh | 2 +- cassh.1 | 38 ++++++++++++++++++++++++++++++++++---- cassh.sh | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/cassh-keyfile.sh b/cassh-keyfile.sh index 4490a09..5094b8e 100644 --- a/cassh-keyfile.sh +++ b/cassh-keyfile.sh @@ -27,7 +27,7 @@ fi cassh_command=$2 needs_agent=false case $cassh_command in -issue) +issue|revoke) needs_agent=true ;; esac diff --git a/cassh.1 b/cassh.1 index d316ed7..897072a 100644 --- a/cassh.1 +++ b/cassh.1 @@ -38,6 +38,12 @@ .Ic known_hosts .Op hostnames ... .Ek +.Nm +.Bk -words +.Cm revoke +.Op Fl qv +.Ar +.Ek .Sh DESCRIPTION .Nm is a small utility for issuing and revoking OpenSSH Certificates. @@ -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 +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 ./serial.txt -file holding the current serial number for the issued certificates. +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 : @@ -128,15 +139,34 @@ 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 ./krl +Key Revocation List .It Pa ./serial.txt -Last issued serial +Last issued serial for certificates +.It Pa ./krl_serial.txt +Last issued serial for KRLs .El .Sh EXIT STATUS .Ex -std diff --git a/cassh.sh b/cassh.sh index 298806e..881cec7 100644 --- a/cassh.sh +++ b/cassh.sh @@ -205,10 +205,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_KRL=./krl +PATH_KRL_SERIAL=./krl_serial.txt PATH_PUBKEYS_DIR=./pubkeys if [ $# -lt 1 ]; then @@ -220,5 +252,6 @@ shift case $cmd in issue) main_issue "$@" ;; mkfile) main_mkfile "$@" ;; +revoke) main_revoke "$@" ;; *) usage ;; esac From 70f4a1b190fba0ff6151ac8b1d647e17b78e5e34 Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 20 Apr 2022 16:45:47 +0000 Subject: [PATCH 11/16] Rename serial.txt -> ca_serial.txt --- cassh.1 | 4 ++-- cassh.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cassh.1 b/cassh.1 index 897072a..ca184fb 100644 --- a/cassh.1 +++ b/cassh.1 @@ -65,7 +65,7 @@ file corresponding to the public key of it, a 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 ./serial.txt +.Pa ./ca_serial.txt and .Pa ./krl_serial.txt files corresponding to the current serial number for the issued certificates @@ -163,7 +163,7 @@ Certification Authority public key Directory containing the public keys to be signed .It Pa ./krl Key Revocation List -.It Pa ./serial.txt +.It Pa ./ca_serial.txt Last issued serial for certificates .It Pa ./krl_serial.txt Last issued serial for KRLs diff --git a/cassh.sh b/cassh.sh index 881cec7..f48ee96 100644 --- a/cassh.sh +++ b/cassh.sh @@ -238,7 +238,7 @@ main_revoke() 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 From 2e9d469fde6f7a51e4bd81b260f580d6a73c8ca3 Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 20 Apr 2022 16:47:37 +0000 Subject: [PATCH 12/16] Update manpages date --- cassh-keyfile.1 | 2 +- cassh.1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cassh-keyfile.1 b/cassh-keyfile.1 index bd1e777..570d2d2 100644 --- a/cassh-keyfile.1 +++ b/cassh-keyfile.1 @@ -11,7 +11,7 @@ .\" along with this software. If not, see .\" . .\" -.Dd April 07, 2022 +.Dd April 20, 2022 .Dt CASSH-KEYFILE 1 .Os .Sh NAME diff --git a/cassh.1 b/cassh.1 index ca184fb..24529ac 100644 --- a/cassh.1 +++ b/cassh.1 @@ -11,7 +11,7 @@ .\" along with this software. If not, see .\" . .\" -.Dd April 19, 2022 +.Dd April 20, 2022 .Dt CASSH 1 .Os .Sh NAME From 3e3db133650b03a98f0222bb45d50db04aa843d8 Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 20 Apr 2022 16:48:53 +0000 Subject: [PATCH 13/16] Release v1 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 09e943b..497f07c 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ chmod a+x $@ P = cassh -V = 0 +V = 1 PREFIX = /usr/local MANPREFIX = ${PREFIX}/man From 09e8f9650deecb44254a51e9f7a1f18622d2d3b5 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 6 Jun 2022 23:55:28 +0000 Subject: [PATCH 14/16] Add revoke to usage, missed in 9e75968accb8b82f7ab0715e9d4528896256cf8f --- cassh.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/cassh.sh b/cassh.sh index f48ee96..37c8978 100644 --- a/cassh.sh +++ b/cassh.sh @@ -19,6 +19,7 @@ Usage: [-V validity_interval] ${0##*/} mkfile authorized_keys [options ...] ${0##*/} mkfile known_hosts [hostnames ...] + ${0##*/} revoke [-qv] file ... EOF exit 1 } From fb6454a300e3fa094501d7b18ca95f8f81da4f4e Mon Sep 17 00:00:00 2001 From: Lucas Date: Sat, 3 Sep 2022 13:57:43 +0000 Subject: [PATCH 15/16] Sort find output and avoid duplicated / Some find implementations will produce results like "dir//a" if given "find dir/". --- cassh.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cassh.sh b/cassh.sh index 37c8978..c1456c8 100644 --- a/cassh.sh +++ b/cassh.sh @@ -139,7 +139,8 @@ main_issue() if [ ! -d "$PATH_PUBKEYS_DIR" ]; then exit 0 fi - find "$PATH_PUBKEYS_DIR/" -type f -name '*.pub' ! -name '*-cert.pub' | { + 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} From cbdede3433ac49df8eef8f6bdaadc5b826341ee6 Mon Sep 17 00:00:00 2001 From: Lucas Date: Sat, 3 Sep 2022 14:02:37 +0000 Subject: [PATCH 16/16] Release v2 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 497f07c..2cd3f04 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ chmod a+x $@ P = cassh -V = 1 +V = 2 PREFIX = /usr/local MANPREFIX = ${PREFIX}/man