|
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 |