bin/ca
changeset 0 730be7994b86
child 1 f44419b55cf0
--- /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, "</dev/tty") if not defined $csrfile;
+
+    die "CSR is empty" if not -s $csr;
+
+    $tt2->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<ca> 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<days>
+
+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<outfile>
+
+The name of the output file. If not set (the default), the output goes
+to I<stdout> 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<type>
+
+The (NSCertType) type of the certificate. Should be client or server.
+(default: none)
+
+=back
+
+=cut
+## Please see file perltidy.ERR