diff -r 000000000000 -r 730be7994b86 bin/ca --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bin/ca Tue Jan 26 23:26:08 2010 +0100 @@ -0,0 +1,206 @@ +#! /usr/bin/perl +use strict; +use warnings; +use Template; +use IO::File; +use File::Path; +use File::Temp qw(tempdir); +use File::Basename; +use Getopt::Long qw(GetOptionsFromArray); +use Pod::Usage; + +my $CA_CRT = "CA/ca-crt.pem"; +my $CA_KEY = "CA/private/ca-key.pem"; +my $CA_DIR = "./var"; + +my %TEMPLATE = ( + ca => "templates/ca", + req => "templates/req", +); + +my $TMP = tempdir("/tmp/$ENV{USER}.ca.XXXXXX", CLEANUP => 1); + +my $opt_days = undef; # see the templates/ca for a default +my $opt_type = undef; # see the templates/ca for a default +my $opt_policy = "de"; # see the templates/ca for a default +my $opt_outfile = undef; +my $opt_force = undef; + +sub init_ca(); +sub ask_pass($); + +MAIN: { + my $csrfile; + + GetOptions( + "d|days=i" => \$opt_days, + "t|type=s" => \$opt_type, + "p|policy=s" => \$opt_policy, + "o|outfile=s" => \$opt_outfile, + "force" => \$opt_force, + "init" => sub { init_ca(); exit 0; }, + "h|help" => sub { pod2usage(-verbose => 1, -exit => 0) }, + "m|man" => sub { pod2usage(-verbose => 2, -exit => 0) }, + ) or pod2usage; + + pod2usage if @ARGV > 1; + $csrfile = $ARGV[0]; # don't shift, we'll need it later! + + my $csr = new IO::File "$TMP/csr" => "w+" + or die "Can't open +>$TMP/csr: $!\n"; + my $cnf = new IO::File "$TMP/cnf" => "w" + or die "Can't open >$TMP/cnf: $!\n"; + my $crt = new IO::File "$TMP/crt" => "w+" + or die "Can't open +>$TMP/crt: $!\n"; + my $tt2 = new Template or die $Template::ERROR; + + # get a private copy of the request + print { IO::File->new("|openssl req -out $TMP/csr") } <>; + open(STDIN, "process( + $TEMPLATE{ca}, + { + type => $opt_type, + days => $opt_days, + policy => "policy_$opt_policy", + cacrt => $CA_CRT, + cakey => $CA_KEY, + cadir => $CA_DIR, + } => "$TMP/cnf" + ) or die $tt2->error, "\n"; + + system( "openssl ca -config $TMP/cnf -in $TMP/csr -out $TMP/crt" + . " -utf8 \${CA_PASS:+-passin env:CA_PASS}"); + + die "ERR: Cert is zero size\n" if not -s $crt; + + # get the name of the output crt file + my $outfile = $opt_outfile; + if (not defined $outfile and defined($_ = $csrfile)) { + if (/(.*[\W_])(?:req|csr).pem$/) { $outfile = "$1crt.pem" } + elsif (/(.*[\W_])req$/) { $outfile = "$1crt" } + else { $outfile .= ".crt.pem" } + } + + # to be sure not to have an invalid/dangerous file name + fork() or do { + open(STDOUT, ">$outfile") + if defined $outfile + or die "Can't open >$outfile: $!\n"; + exec "openssl x509 -in $TMP/crt"; + die "Can't exec openssl x509: $!\n"; + }; + wait; + exit; +} + +sub verbose($) { + warn $_[0], " \n "; +} + +sub ask_pass($) { + my $prompt = shift; + my @keys = ("x", "y"); + + while (1) { + print $prompt; + my $stty = `stty -g`; + system("stty -echo"); + chomp($keys[0] = IO::File->new("/dev/tty")->getline()); + print "\n"; + system("stty $stty"); + print "please again for verification: "; + system("stty -echo"); + chomp($keys[1] = IO::File->new("/dev/tty")->getline()); + print "\n"; + system("stty $stty"); + return $keys[0] if $keys[0] eq $keys[1]; + print "keys mismatch, again\n"; + } +} + +sub init_ca() { + # initialize the CA directory structure. This should + # correspond to the values found in templates/ca + die "$CA_DIR already exists" if -d $CA_DIR and not $opt_force; + mkpath(map { "$CA_DIR/$_" } qw(newcerts)); + mkpath(map { dirname $_ } $CA_CRT, $CA_KEY); + (new IO::File ">$CA_DIR/index"); + (new IO::File ">$CA_DIR/serial")-> print("01\n"); + + # now + my $tt2 = new Template or die $Template::ERROR; + $tt2->process($TEMPLATE{req}, + { + # not used yet + } => "$TMP/cnf") or die $tt2->error; + + $ENV{CA_PASS} = ask_pass("passphrase for CA key: "); + system("openssl req -config $TMP/cnf -x509 -days 3650 -new -passout env:CA_PASS -keyout $TMP/ca-key.pem -out $TMP/ca-crt.pem") + and exit; + + system("openssl x509 -in $TMP/ca-crt.pem -out $CA_CRT") and exit; + $_ = umask(077); + system("openssl rsa -in $TMP/ca-key.pem -des3 -passin env:CA_PASS -passout env:CA_PASS -out $CA_KEY") and exit; + umask($_); + + +} + +__END__ + +=head1 NAME + + ca - the ultimative CA tool + +=head1 SYNOPSIS + + ca [--force] --init + ca --type=TYPE --days=DAYS [request.pem] + + (not yet: request c=COUNTRY ST=STATE l=LOCATION o=ORGANIZATION OU=ORG-UNIT cn=COMMON-NAME) + +=head1 DESCRIPTION + +This B tool signs the request file. If no file is given, it +expects the request on STDIN + +=head1 OPTIONS + +=over 4 + +=item B<-d>|B<--days> I + +The number of days the certificate should be valid. (default: 365) + +=item B<-h>|B<--help> + +Print the reference help and exit. (default: off) + +=item B<-i>|B<--init> + +Initialize the CA (keys, directories). This may be enforce with +B<--force>. (default: off) + +=item B<-m>|B<--man> + +Open the reference manual and exit. (default: off) + +=item B<-o>|B<--out> I + +The name of the output file. If not set (the default), the output goes +to I if the CSR came from stdin and it goes to a file named +similar to the CSR, if the request came from a file. + +=item B<-t>|B<--type> I + +The (NSCertType) type of the certificate. Should be client or server. +(default: none) + +=back + +=cut +## Please see file perltidy.ERR