File Coverage

script/sslmaker
Criterion Covered Total %
statement 90 114 78.9
branch 10 26 38.4
condition 7 23 30.4
subroutine 23 28 82.1
pod n/a
total 130 191 68.0


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2             BEGIN {
3 1 50 33 1   12759 $ENV{SSLMAKER_DEBUG} //= grep {/^--silent/} @ARGV ? 0 : 1;
  1         385  
4             }
5 1     1   708 use Applify;
  1         7614  
  1         10  
6 1     1   1498 use File::Spec::Functions qw(catdir);
  1         972  
  1         89  
7 1     1   9 use Path::Tiny;
  1         1  
  1         2547  
8              
9             option int => bits => 'SSL key bit size';
10             option int => days => 'Number of days the cert should be valid';
11             option str => home => 'sslmaker working directory';
12             option str => root => 'Path to root key.pem. Required for making intermediate key+cert';
13             option str => subject =>
14             'Example: /C=US/ST=Texas/L=Dallas/O=Company/OU=Department/CN=example.com/emailAddress=admin@example.com',
15             $ENV{SSLMAKER_SUBJECT};
16             option bool => silent => 'Only output data on failure';
17              
18             documentation 'App::sslmaker';
19             version 'App::sslmaker';
20              
21             my $wrapper = sub {
22             my ($sslmaker, $method, @args) = @_;
23             return $ENV{OPENSSL_CONF} ? $sslmaker->$method(@args) : $sslmaker->with_config($method, @args);
24             };
25              
26             sub d {
27 2     2   9 my $d = {@_};
28 2   50     57 $_ and $_ = "$_" for values %$d;
29 2         87 my $json = Data::Dumper->new([$d])->Indent(1)->Sortkeys(1)->Terse(1)->Useqq(1)->Dump;
30 2         435 $json =~ s/" => /": /g; # ugly
31 2         15 $json;
32             }
33              
34             sub action_revoke {
35 2     2   10 my ($self, $cert) = @_;
36 2         30 my $sslmaker = App::sslmaker->new;
37 2         10 my $passphrase = $self->home->child(qw(private passphrase));
38              
39 2 50       192 $sslmaker->$wrapper(
40             revoke_cert => {
41             cert => $self->home->child(qw(certs ca.cert.pem)),
42             key => $self->home->child(qw(private ca.key.pem)),
43             passphrase => -e $passphrase ? $passphrase : undef,
44             revoke => $cert,
45             }
46             );
47              
48 2         268 $self->_warn("Done.\n");
49             }
50              
51             sub action_root {
52 1     1   2 my $self = shift;
53 1 50       6 die "--subject is required\n" unless $self->subject;
54              
55 1   50     14 my $args = {
      50        
56             bits => $self->bits || 8192,
57             cert => $self->_root_file('cert'),
58             days => $self->days || 365 * 30,
59             home => $self->root->parent,
60             key => $self->_root_file('key'),
61             passphrase => $self->root->parent->child(qw(passphrase)),
62             };
63              
64 1         143 $self->_print(d %$args);
65              
66 1         12 my $sslmaker = App::sslmaker->new;
67 1         4 $sslmaker->subject($self->subject);
68 1         6 $sslmaker->make_directories({home => $args->{home}, templates => 1});
69 1     1   43 $self->run_maybe($args->{key}, sub { $sslmaker->$wrapper(make_key => $args) });
  1         4  
70 1     1   66 $self->run_maybe($args->{cert}, sub { $sslmaker->$wrapper(make_cert => $args) });
  1         5  
71 1         45 $self->_warn("Done.\n");
72             }
73              
74             sub action_dhparam {
75 0     0   0 my $self = shift;
76 0   0     0 my $path = shift || $self->home->child('dhparam.pem');
77 0   0     0 my $bits = shift || 2048;
78              
79 0     0   0 $self->run_maybe($path, sub { App::sslmaker::openssl(qw(dhparam -out) => $path, $bits) });
  0         0  
80             }
81              
82             sub action_generate {
83 2     2   6 my $self = shift;
84 2   50     8 my $cn = shift || die "Usage: $0 generate \n";
85              
86 2         29 my $args = {
87             bits => $self->bits,
88             csr => "$cn.csr.pem",
89             days => $self->days,
90             home => $self->home,
91             key => "$cn.key.pem",
92             subject => "/CN=$cn",
93             };
94              
95 2         107 my $sslmaker = App::sslmaker->new;
96 2         9 my $intermediate_cert = $self->home->child(qw(certs ca.cert.pem));
97 2 50       158 $sslmaker->subject(
98             -r $intermediate_cert ? ($intermediate_cert, $self->subject) : ($self->subject));
99 2     2   98 $self->run_maybe($args->{key}, sub { $sslmaker->make_key($args) });
  2         32  
100 2     2   160 $self->run_maybe($args->{csr}, sub { $sslmaker->make_csr($args) });
  2         39  
101 2         127 $self->_print("// Next: Need to send $args->{csr} to SSL admin for signing.\n");
102 2         22 $self->_warn("Done.\n");
103             }
104              
105             sub action_intermediate {
106 1     1   3 my $self = shift;
107              
108 1         4 my $home = $self->home;
109 1   50     9 my $args = {
110             bits => $self->bits,
111             ca_cert => $self->_root_file('cert'),
112             ca_key => $self->_root_file('key'),
113             cert => $home->child(qw(certs ca.cert.pem)),
114             csr => $home->child(qw(certs ca.csr.pem)),
115             days => $self->days || 365 * 28,
116             extensions => 'v3_ca',
117             home => $home,
118             key => $home->child(qw(private ca.key.pem)),
119             passphrase => $home->child(qw(private passphrase)),
120             };
121              
122 1         285 $self->_print(d %$args);
123              
124 1         19 my $sslmaker = App::sslmaker->new;
125 1         11 $sslmaker->subject($args->{ca_cert}, $self->subject);
126 1         37 $sslmaker->make_directories({home => $home, templates => 1});
127 1     1   37 $self->run_maybe($args->{key}, sub { $sslmaker->$wrapper(make_key => $args) });
  1         59  
128 1     1   88 $self->run_maybe($args->{csr}, sub { $sslmaker->$wrapper(make_csr => $args) });
  1         16  
129              
130 1         64 $args->{home} = $self->root->parent;
131 1         146 $args->{passphrase} = $self->root->parent->child(qw(passphrase));
132 1     1   134 $self->run_maybe($args->{cert}, sub { $sslmaker->$wrapper(sign_csr => $args) });
  1         10  
133              
134 1         67 $args->{chain_cert} = $home->child(qw(certs ca-chain.cert.pem));
135 1         124 $sslmaker->_cat(@$args{qw( cert ca_cert chain_cert )});
136 1         6 $self->_print("// Generated $args->{chain_cert} from CA and intermediate certificate\n");
137              
138             $sslmaker->openssl(
139             verify => -CAfile => @$args{qw( ca_cert cert )},
140             sub {
141 1     1   25 my ($sslmaker, $output) = @_;
142 1 50       47 die $output if $output =~ /error/;
143             }
144 1         30 );
145              
146 1         42 $self->_warn("Done.\n");
147             }
148              
149             sub action_nginx {
150 0     0   0 my $self = shift;
151 0   0     0 my $domain = shift || die "Usage: $0 nginx \n";
152              
153 0         0 $self->_print(App::sslmaker->_render_template(
154             'nginx.config',
155             {
156             domain => $domain,
157             key => "/etc/nginx/ssl/$domain.key.pem",
158             cert => "/etc/nginx/ssl/$domain.cert.pem",
159             ca_cert => $self->home->child(qw(certs ca-chain.cert.pem)),
160             },
161             ));
162             }
163              
164             sub action_man {
165 0     0   0 exec perldoc => 'App::sslmaker';
166             }
167              
168             sub action_sign {
169 2     2   15 my ($self, $csr, $cert) = @_;
170 2         32 my $sslmaker = App::sslmaker->new;
171 2         10 my $home = $self->home;
172              
173 2 50       16 unless ($cert) {
174 2         5 $cert = $csr;
175 2         36 $cert =~ s!(\.csr)?\.pem$!\.cert.pem!;
176             }
177              
178 2         26 $sslmaker->$wrapper(
179             sign_csr => {
180             home => $home,
181             ca_cert => $home->child(qw(certs ca.cert.pem)),
182             ca_key => $home->child(qw(private ca.key.pem)),
183             cert => $cert,
184             csr => $csr,
185             extensions => 'usr_cert',
186             passphrase => $home->child(qw(private passphrase)),
187             }
188             );
189              
190 2         318 $self->_print("// Generated $cert\n");
191 2         41 $self->_warn("Done.\n");
192 2         44 $self->_warn("Run this command for more details: openssl x509 -in $cert -noout -text\n");
193             }
194              
195             sub catch {
196 0     0   0 my $self = shift;
197 0         0 my $errno = $!;
198 0         0 my $errstr = $@;
199              
200             # remove stacktrace and rewrite invalid input
201 0 0       0 $errstr =~ s!\sat\s\S+\sline.*!!s unless $ENV{HARNESS_ACTIVE};
202 0         0 $errstr =~ s!"subject"!--subject!s;
203              
204             # parse openssl exception
205 0 0       0 if ($errstr =~ s!\sFAIL\s\((\d+)\)\s\((.*)\)$!!s) {
206 0         0 $errno = $1;
207 0         0 $errstr = $2;
208             }
209              
210 0         0 $! = $errno;
211 0         0 die $errstr;
212             }
213              
214             sub run_maybe {
215 9     9   94 my ($self, $file, $cb) = @_;
216              
217 9 50       120 if (-e $file) {
218 0         0 $self->_print("// File $file exists.\n");
219             }
220             else {
221 9         241 $self->$cb;
222 9         715 $self->_print("// Generated $_[1]\n");
223             }
224             }
225              
226             sub _build_home {
227 8     8   43 my $self = shift;
228              
229 8   33     115 my $path = $self->home || $ENV{SSLMAKER_HOME};
230 8 50       191 return $path if $path;
231              
232 0         0 for (qw(pki ssl)) {
233 0         0 my $path = "/etc/$_";
234 0 0       0 return "$path/sslmaker" if -w $path;
235             }
236              
237 0         0 die "Cannot detect default --home. Maybe you have to run as root?\n";
238             }
239              
240             sub _root_file {
241 4     4   331 my ($self, $ext) = @_;
242 4         12 my $base = $self->root->basename;
243 4         123 $base =~ s!\b(cert|key)\b!$ext!;
244 4         14 return $self->root->parent->child($base);
245             }
246              
247 16 50   16   274 sub _print { shift->silent or print @_ }
248 10 50   10   87 sub _warn { shift->silent or warn @_ }
249              
250             app {
251             my ($self, $action, @args) = @_;
252             $action ||= 'man';
253             $action = 'man' if grep { $action eq $_ } qw(help pod);
254              
255             if ($action ne 'man') {
256             $self->home(Path::Tiny->new($self->_build_home)->absolute);
257             $self->root(Path::Tiny->new($self->root || $self->home->child(qw(root ca.key.pem)))->absolute);
258             }
259              
260             unless ($action and $self->can("action_$action")) {
261             $self->_script->print_help;
262             return 0;
263             }
264              
265             eval {
266             require App::sslmaker;
267             $self->can("action_$action")->($self, @args);
268             1;
269             } or $self->catch;
270              
271             return 0;
272             };