bin/ca
changeset 0 730be7994b86
child 1 f44419b55cf0
equal deleted inserted replaced
-1:000000000000 0:730be7994b86
       
     1 #! /usr/bin/perl
       
     2 use strict;
       
     3 use warnings;
       
     4 use Template;
       
     5 use IO::File;
       
     6 use File::Path;
       
     7 use File::Temp qw(tempdir);
       
     8 use File::Basename;
       
     9 use Getopt::Long qw(GetOptionsFromArray);
       
    10 use Pod::Usage;
       
    11 
       
    12 my $CA_CRT   = "CA/ca-crt.pem";
       
    13 my $CA_KEY   = "CA/private/ca-key.pem";
       
    14 my $CA_DIR   = "./var";
       
    15 
       
    16 my %TEMPLATE = (
       
    17     ca => "templates/ca",
       
    18     req => "templates/req",
       
    19 );
       
    20 
       
    21 my $TMP      = tempdir("/tmp/$ENV{USER}.ca.XXXXXX", CLEANUP => 1);
       
    22 
       
    23 my $opt_days    = undef;    # see the templates/ca for a default
       
    24 my $opt_type    = undef;    # see the templates/ca for a default
       
    25 my $opt_policy  = "de";     # see the templates/ca for a default
       
    26 my $opt_outfile = undef;
       
    27 my $opt_force = undef;
       
    28 
       
    29 sub init_ca();
       
    30 sub ask_pass($);
       
    31 
       
    32 MAIN: {
       
    33     my $csrfile;
       
    34 
       
    35     GetOptions(
       
    36         "d|days=i"    => \$opt_days,
       
    37         "t|type=s"    => \$opt_type,
       
    38         "p|policy=s"  => \$opt_policy,
       
    39         "o|outfile=s" => \$opt_outfile,
       
    40 	"force"	      => \$opt_force,
       
    41 	"init"	      => sub { init_ca(); exit 0; },
       
    42         "h|help"      => sub { pod2usage(-verbose => 1, -exit => 0) },
       
    43         "m|man"       => sub { pod2usage(-verbose => 2, -exit => 0) },
       
    44     ) or pod2usage;
       
    45 
       
    46     pod2usage if @ARGV > 1;
       
    47     $csrfile = $ARGV[0];    # don't shift, we'll need it later!
       
    48 
       
    49     my $csr = new IO::File "$TMP/csr" => "w+"
       
    50       or die "Can't open +>$TMP/csr: $!\n";
       
    51     my $cnf = new IO::File "$TMP/cnf" => "w"
       
    52       or die "Can't open >$TMP/cnf: $!\n";
       
    53     my $crt = new IO::File "$TMP/crt" => "w+"
       
    54       or die "Can't open +>$TMP/crt: $!\n";
       
    55     my $tt2 = new Template or die $Template::ERROR;
       
    56 
       
    57     # get a private copy of the request
       
    58     print { IO::File->new("|openssl req -out $TMP/csr") } <>;
       
    59     open(STDIN, "</dev/tty") if not defined $csrfile;
       
    60 
       
    61     die "CSR is empty" if not -s $csr;
       
    62 
       
    63     $tt2->process(
       
    64         $TEMPLATE{ca},
       
    65         {
       
    66             type   => $opt_type,
       
    67             days   => $opt_days,
       
    68             policy => "policy_$opt_policy",
       
    69             cacrt  => $CA_CRT,
       
    70 	    cakey  => $CA_KEY,
       
    71 	    cadir  => $CA_DIR,
       
    72         } => "$TMP/cnf"
       
    73     ) or die $tt2->error, "\n";
       
    74 
       
    75     system( "openssl ca -config $TMP/cnf -in $TMP/csr -out $TMP/crt"
       
    76           . " -utf8 \${CA_PASS:+-passin env:CA_PASS}");
       
    77 
       
    78     die "ERR: Cert is zero size\n" if not -s $crt;
       
    79 
       
    80     # get the name of the output crt file
       
    81     my $outfile = $opt_outfile;
       
    82     if (not defined $outfile and defined($_ = $csrfile)) {
       
    83         if    (/(.*[\W_])(?:req|csr).pem$/) { $outfile = "$1crt.pem" }
       
    84         elsif (/(.*[\W_])req$/)             { $outfile = "$1crt" }
       
    85         else                                { $outfile .= ".crt.pem" }
       
    86     }
       
    87 
       
    88     # to be sure not to have an invalid/dangerous file name
       
    89     fork() or do {
       
    90         open(STDOUT, ">$outfile")
       
    91           if defined $outfile
       
    92               or die "Can't open >$outfile: $!\n";
       
    93         exec "openssl x509 -in $TMP/crt";
       
    94         die "Can't exec openssl x509: $!\n";
       
    95     };
       
    96     wait;
       
    97     exit;
       
    98 }
       
    99 
       
   100 sub verbose($) {
       
   101     warn $_[0], " \n ";
       
   102 }
       
   103 
       
   104 sub ask_pass($) {
       
   105     my $prompt = shift;
       
   106     my @keys = ("x", "y");
       
   107 
       
   108     while (1) {
       
   109 	print $prompt;
       
   110 	my $stty = `stty -g`;
       
   111 	system("stty -echo");
       
   112 	chomp($keys[0] = IO::File->new("/dev/tty")->getline());
       
   113 	print "\n";
       
   114 	system("stty $stty");
       
   115 	print "please again for verification: ";
       
   116 	system("stty -echo");
       
   117 	chomp($keys[1] = IO::File->new("/dev/tty")->getline());
       
   118 	print "\n";
       
   119 	system("stty $stty");
       
   120 	return $keys[0] if $keys[0] eq $keys[1];
       
   121 	print "keys mismatch, again\n";
       
   122     }
       
   123 }
       
   124 
       
   125 sub init_ca() {
       
   126     # initialize the CA directory structure. This should
       
   127     # correspond to the values found in templates/ca
       
   128     die "$CA_DIR already exists" if -d $CA_DIR and not $opt_force;
       
   129     mkpath(map { "$CA_DIR/$_" } qw(newcerts));
       
   130     mkpath(map { dirname $_ } $CA_CRT, $CA_KEY);
       
   131     (new IO::File ">$CA_DIR/index");
       
   132     (new IO::File ">$CA_DIR/serial")-> print("01\n");
       
   133 
       
   134     # now 
       
   135     my $tt2 = new Template or die $Template::ERROR;
       
   136     $tt2->process($TEMPLATE{req},
       
   137     {
       
   138 	# not used yet
       
   139     } => "$TMP/cnf") or die $tt2->error;
       
   140 
       
   141     $ENV{CA_PASS} = ask_pass("passphrase for CA key: ");
       
   142     system("openssl req -config $TMP/cnf -x509 -days 3650 -new -passout env:CA_PASS -keyout $TMP/ca-key.pem -out $TMP/ca-crt.pem")
       
   143     and exit;
       
   144 
       
   145     system("openssl x509 -in $TMP/ca-crt.pem -out $CA_CRT") and exit;
       
   146     $_ = umask(077);
       
   147     system("openssl rsa -in $TMP/ca-key.pem -des3 -passin env:CA_PASS -passout env:CA_PASS -out $CA_KEY") and exit;
       
   148     umask($_);
       
   149 
       
   150 
       
   151 }
       
   152 
       
   153 __END__
       
   154 
       
   155 =head1 NAME
       
   156 
       
   157     ca - the ultimative CA tool
       
   158 
       
   159 =head1 SYNOPSIS
       
   160 
       
   161     ca [--force] --init
       
   162     ca --type=TYPE --days=DAYS [request.pem]
       
   163 
       
   164     (not yet: request c=COUNTRY ST=STATE l=LOCATION o=ORGANIZATION OU=ORG-UNIT cn=COMMON-NAME)
       
   165 
       
   166 =head1 DESCRIPTION
       
   167 
       
   168 This B<ca> tool signs the request file. If no file is given, it
       
   169 expects the request on STDIN
       
   170 
       
   171 =head1 OPTIONS
       
   172 
       
   173 =over 4
       
   174 
       
   175 =item B<-d>|B<--days> I<days>
       
   176 
       
   177 The number of days the certificate should be valid. (default: 365)
       
   178 
       
   179 =item B<-h>|B<--help>
       
   180 
       
   181 Print the reference help and exit. (default: off)
       
   182 
       
   183 =item B<-i>|B<--init>
       
   184 
       
   185 Initialize the CA (keys, directories). This may be enforce with
       
   186 B<--force>. (default: off)
       
   187 
       
   188 =item B<-m>|B<--man>
       
   189 
       
   190 Open the reference manual and exit. (default: off)
       
   191 
       
   192 =item B<-o>|B<--out> I<outfile>
       
   193 
       
   194 The name of the output file. If not set (the default), the output goes
       
   195 to I<stdout> if the CSR came from stdin and it goes to a file named
       
   196 similar to the CSR, if the request came from a file.
       
   197 
       
   198 =item B<-t>|B<--type> I<type>
       
   199 
       
   200 The (NSCertType) type of the certificate. Should be client or server.
       
   201 (default: none)
       
   202 
       
   203 =back
       
   204 
       
   205 =cut
       
   206 ## Please see file perltidy.ERR