Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
Lucas | fe858efc53 |
2
Makefile
2
Makefile
|
@ -19,7 +19,7 @@
|
||||||
chmod a+x $@
|
chmod a+x $@
|
||||||
|
|
||||||
P = cassh
|
P = cassh
|
||||||
V = 2
|
V = 0
|
||||||
|
|
||||||
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 20, 2022
|
.Dd April 07, 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|revoke)
|
issue)
|
||||||
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 April 20, 2022
|
.Dd March 01, 2022
|
||||||
.Dt CASSH 1
|
.Dt CASSH 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
@ -30,19 +30,13 @@
|
||||||
.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
|
||||||
|
@ -62,14 +56,9 @@ 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, an optional
|
directory which holds the public keys to be signed, and an optional
|
||||||
.Pa ./krl
|
.Pa ./serial.txt
|
||||||
file corresponding to the last issued Key Revocation List, and optional
|
file holding the current serial number for the issued certificates.
|
||||||
.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 :
|
||||||
|
@ -95,15 +84,9 @@ The recognized tokens are:
|
||||||
A literal
|
A literal
|
||||||
.Sq % .
|
.Sq % .
|
||||||
.It \&%C
|
.It \&%C
|
||||||
The Certification Authority private key comment field as reported by
|
The Certification Authority private key comment.
|
||||||
.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, without
|
The basename of the public key being signed.
|
||||||
.Sq .pub
|
|
||||||
suffix.
|
|
||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
.Ar key_id
|
.Ar key_id
|
||||||
|
@ -117,56 +100,37 @@ 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
|
||||||
are concatenated with commas and copied verbatim to the output.
|
is copied verbatim to the output, and
|
||||||
.Cm cert-authority
|
.Cm cert-authority
|
||||||
is always added to the options list.
|
is always added.
|
||||||
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
|
||||||
are concatenated with commas and copied verbatim to the output.
|
is 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 MMMMMMMMMMMMMMMMMM -compact
|
.Bl -tag -width MMMMMMMMMMMMMM -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 ./krl
|
.It Pa ./serial.txt
|
||||||
Key Revocation List
|
Last issued serial
|
||||||
.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
|
||||||
|
|
|
@ -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
|
||||||
|
# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
|
||||||
|
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 <<EOF;
|
||||||
|
Usage:
|
||||||
|
$PROGNAME issue [-hqv] [-I key_id] [-n principals]
|
||||||
|
[-V validity_interval]
|
||||||
|
$PROGNAME mkfile authorized_keys [options ...]
|
||||||
|
$PROGNAME mkfile known_hosts [hostnames ...]
|
||||||
|
EOF
|
||||||
|
exit 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub err (@)
|
||||||
|
{
|
||||||
|
say STDERR "$PROGNAME: @_";
|
||||||
|
exit 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub slurp ($)
|
||||||
|
{
|
||||||
|
my $f = shift;
|
||||||
|
|
||||||
|
open(my $fh, "<", $f) or err "can't open $f";
|
||||||
|
local $/;
|
||||||
|
my $s = <$fh>;
|
||||||
|
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();
|
204
cassh.sh
204
cassh.sh
|
@ -17,9 +17,8 @@ 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
|
||||||
}
|
}
|
||||||
|
@ -30,11 +29,29 @@ err()
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Returns the comment from the loaded secret key in ssh-agent, if any is
|
strip_leading_zeros()
|
||||||
# present.
|
|
||||||
get_ca_sk_comment_from_pk()
|
|
||||||
{
|
{
|
||||||
ssh-keygen -lf "$1" 2>/dev/null | {
|
_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
|
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
|
||||||
|
@ -42,67 +59,57 @@ get_ca_sk_comment_from_pk()
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
done)
|
done)
|
||||||
echo "${_comment:-}"
|
echo "${_comment:-${pk_fp#*:}}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
format()
|
_template_fmt()
|
||||||
{
|
{
|
||||||
_s=$1
|
_allowed_chars=$1
|
||||||
shift
|
_char=$2
|
||||||
|
if [ "X$_char" = X% ]; then
|
||||||
|
echo %
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
_cleanup=unset
|
case $_char in
|
||||||
while [ $# -ge 2 ]; do
|
[$_allowed_chars])
|
||||||
_k=$1 _v=$2
|
;;
|
||||||
shift 2
|
*)
|
||||||
case $_k in
|
return 1
|
||||||
[A-Za-z])
|
;;
|
||||||
;;
|
esac
|
||||||
*)
|
|
||||||
return 1
|
_v=$(eval echo '${_template_fmt_'"$_char"':-}')
|
||||||
;;
|
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#?}}
|
||||||
|
|
||||||
if [ -z "${_c:-}" ]; then
|
_t=$(_template_fmt "$_allowed" "$_c")
|
||||||
|
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"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,42 +138,64 @@ 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
|
||||||
echo 1 >"$PATH_CA_SERIAL"
|
date -u +%Y%m%d000000000 >"$PATH_CA_SERIAL"
|
||||||
fi
|
fi
|
||||||
read -r serial <"$PATH_CA_SERIAL"
|
read -r serial <"$PATH_CA_SERIAL"
|
||||||
|
# Remove NNNNNNNNN suffix
|
||||||
if [ ! -d "$PATH_PUBKEYS_DIR" ]; then
|
serial_date=${serial%?????????}
|
||||||
exit 0
|
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
|
fi
|
||||||
find "$PATH_PUBKEYS_DIR" -type f -name '*.pub' ! -name '*-cert.pub' |
|
serial=$(printf "%s%09u\n" "$serial_date" "$serial_counter")
|
||||||
sort | {
|
|
||||||
ca_comment=$(get_ca_sk_comment_from_pk "$PATH_CA_PUB")
|
|
||||||
: ${ca_comment:=cassh}
|
|
||||||
|
|
||||||
|
_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
|
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=$(format "$key_id_fmt" C "$ca_comment" f "$pkname")
|
id=$(template Cf "$key_id_fmt")
|
||||||
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=$(format "$principals_fmt" \
|
principals=$(template f "$principals_fmt")
|
||||||
f "$pkname")
|
|
||||||
ssh-keygen "$@" -n "$principals" "$pk"
|
ssh-keygen "$@" -n "$principals" "$pk"
|
||||||
else
|
else
|
||||||
ssh-keygen "$@" "$pk"
|
ssh-keygen "$@" "$pk"
|
||||||
fi
|
fi || rc=1
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
serial=$(($serial + 1))
|
serial_counter=$(($serial_counter + 1))
|
||||||
echo $serial >"$PATH_CA_SERIAL"
|
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
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
return $rc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,11 +219,19 @@ main_mkfile()
|
||||||
|
|
||||||
case $file in
|
case $file in
|
||||||
authorized_keys)
|
authorized_keys)
|
||||||
printf "%s " "$(strjoin , cert-authority "$@")"
|
if [ $# -gt 1 ]; then
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
options=cert-authority${1:+,$1}
|
||||||
|
printf "%s " "$options"
|
||||||
;;
|
;;
|
||||||
known_hosts)
|
known_hosts)
|
||||||
if [ $# -gt 0 ]; then
|
if [ $# -gt 1 ]; then
|
||||||
printf "@cert-authority %s " "$(strjoin , "$@")"
|
usage
|
||||||
|
fi
|
||||||
|
hostnames=${1:-}
|
||||||
|
if [ -n "$hostnames" ]; then
|
||||||
|
printf "@cert-authority %s " "$hostnames"
|
||||||
else
|
else
|
||||||
printf "@cert-authority "
|
printf "@cert-authority "
|
||||||
fi
|
fi
|
||||||
|
@ -207,42 +244,10 @@ 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=./ca_serial.txt
|
PATH_CA_SERIAL=./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
|
||||||
|
@ -254,6 +259,5 @@ 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