6 use File::Path; |
6 use File::Path; |
7 use File::Temp qw(tempdir); |
7 use File::Temp qw(tempdir); |
8 use File::Basename; |
8 use File::Basename; |
9 use Getopt::Long qw(GetOptionsFromArray); |
9 use Getopt::Long qw(GetOptionsFromArray); |
10 use Pod::Usage; |
10 use Pod::Usage; |
|
11 use feature qw(switch); |
11 |
12 |
12 my $CA_CRT = "CA/ca-crt.pem"; |
13 my $CA_CRT = "CA/ca-crt.pem"; |
13 my $CA_KEY = "CA/private/ca-key.pem"; |
14 my $CA_KEY = "CA/private/ca-key.pem"; |
14 my $CA_DIR = "./var"; |
15 my $CA_DIR = "./var"; |
15 |
16 |
16 my %TEMPLATE = ( |
17 my %TEMPLATE = ( |
17 ca => "templates/ca", |
18 ca => "lib/templates/ca", |
18 req => "templates/req", |
19 req => "lib/templates/req", |
19 ); |
20 ); |
20 |
21 |
21 my $TMP = tempdir("/tmp/$ENV{USER}.ca.XXXXXX", CLEANUP => 1); |
22 my $TMP = tempdir("/tmp/$ENV{USER}.ca.XXXXXX", CLEANUP => 1); |
22 |
23 |
23 my $opt_days = undef; # see the templates/ca for a default |
24 my $opt_days = undef; # see the templates/ca for a default |
35 GetOptions( |
36 GetOptions( |
36 "d|days=i" => \$opt_days, |
37 "d|days=i" => \$opt_days, |
37 "t|type=s" => \$opt_type, |
38 "t|type=s" => \$opt_type, |
38 "p|policy=s" => \$opt_policy, |
39 "p|policy=s" => \$opt_policy, |
39 "o|outfile=s" => \$opt_outfile, |
40 "o|outfile=s" => \$opt_outfile, |
40 "force" => \$opt_force, |
41 "f|force" => \$opt_force, |
41 "init" => sub { init_ca(); exit 0; }, |
42 "i|init" => sub { eval { init_ca() }; if ($@) { warn $@; exit 1 }; exit 0 }, |
42 "h|help" => sub { pod2usage(-verbose => 1, -exit => 0) }, |
43 "h|help" => sub { pod2usage(-verbose => 1, -exit => 0) }, |
43 "m|man" => sub { pod2usage(-verbose => 2, -exit => 0) }, |
44 "m|man" => sub { pod2usage(-verbose => 2, -exit => 0) }, |
44 ) or pod2usage; |
45 ) or pod2usage; |
45 |
46 |
46 pod2usage if @ARGV > 1; |
47 pod2usage if @ARGV > 1; |
47 $csrfile = $ARGV[0]; # don't shift, we'll need it later! |
48 $csrfile = $ARGV[0]; # don't shift, we'll need it later! |
48 |
49 |
49 my $csr = new IO::File "$TMP/csr" => "w+" |
50 my $cnf = new IO::File ">$TMP/cnf" or die "Can't open >$TMP/cnf: $!\n"; |
50 or die "Can't open +>$TMP/csr: $!\n"; |
51 my $csr = new IO::File "+>$TMP/csr" or die "Can't open +>$TMP/csr: $!\n"; |
51 my $cnf = new IO::File "$TMP/cnf" => "w" |
52 my $crt = new IO::File "+>$TMP/crt" or die "Can't open +>$TMP/crt: $!\n"; |
52 or die "Can't open >$TMP/cnf: $!\n"; |
53 my $tt2 = new Template or die $Template::ERROR; |
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 |
54 |
57 # get a private copy of the request |
55 # get a private copy of the request |
58 print { IO::File->new("|openssl req -out $TMP/csr") } <>; |
56 print { IO::File->new("|openssl req -out $TMP/csr") } <>; |
59 open(STDIN, "</dev/tty") if not defined $csrfile; |
57 open(STDIN, "</dev/tty") if not defined $csrfile; |
60 |
58 |
84 elsif (/(.*[\W_])req$/) { $outfile = "$1crt" } |
82 elsif (/(.*[\W_])req$/) { $outfile = "$1crt" } |
85 else { $outfile .= ".crt.pem" } |
83 else { $outfile .= ".crt.pem" } |
86 } |
84 } |
87 |
85 |
88 # to be sure not to have an invalid/dangerous file name |
86 # to be sure not to have an invalid/dangerous file name |
89 fork() or do { |
87 if (fork() == 0) { |
90 open(STDOUT, ">$outfile") |
88 if (defined $outfile) { |
91 if defined $outfile |
89 open(STDOUT, ">$outfile") |
92 or die "Can't open >$outfile: $!\n"; |
90 or die "Can't open >$outfile: $!\n"; |
|
91 } |
93 exec "openssl x509 -in $TMP/crt"; |
92 exec "openssl x509 -in $TMP/crt"; |
94 die "Can't exec openssl x509: $!\n"; |
93 die "Can't exec openssl x509: $!\n"; |
95 }; |
94 } |
96 wait; |
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 |
97 exit; |
113 exit; |
98 } |
|
99 |
|
100 sub verbose($) { |
|
101 warn $_[0], " \n "; |
|
102 } |
114 } |
103 |
115 |
104 sub ask_pass($) { |
116 sub ask_pass($) { |
105 my $prompt = shift; |
117 my $prompt = shift; |
106 my @keys = ("x", "y"); |
118 my @keys = ("x", "y"); |
125 sub init_ca() { |
137 sub init_ca() { |
126 |
138 |
127 # initialize the CA directory structure. This should |
139 # initialize the CA directory structure. This should |
128 # correspond to the values found in templates/ca |
140 # correspond to the values found in templates/ca |
129 die "$CA_DIR already exists" if -d $CA_DIR and not $opt_force; |
141 die "$CA_DIR already exists" if -d $CA_DIR and not $opt_force; |
130 mkpath(map { "$CA_DIR/$_" } qw(newcerts)); |
142 mkpath(map { "$CA_DIR/$_" } qw(newcerts requests)); |
131 mkpath(map { dirname $_ } $CA_CRT, $CA_KEY); |
143 mkpath(map { dirname $_ } $CA_CRT, $CA_KEY); |
132 (new IO::File ">$CA_DIR/index"); |
144 (new IO::File ">$CA_DIR/index"); |
133 (new IO::File ">$CA_DIR/serial")->print("01\n"); |
145 (new IO::File ">$CA_DIR/serial")->print("01\n"); |
134 |
146 |
135 # now |
147 # now |