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