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   14621 $ENV{SSLMAKER_DEBUG} //= grep {/^--silent/} @ARGV ? 0 : 1;
  1         58  
4             }
5 1     1   693 use Applify;
  1         6944  
  1         12  
6 1     1   1375 use File::Spec::Functions qw(catdir);
  1         985  
  1         82  
7 1     1   8 use Path::Tiny;
  1         2  
  1         2519  
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   10 my $d = {@_};
28 2   50     27 $_ and $_ = "$_" for values %$d;
29 2         91 my $json = Data::Dumper->new([$d])->Indent(1)->Sortkeys(1)->Terse(1)->Useqq(1)->Dump;
30 2         440 $json =~ s/" => /": /g; # ugly
31 2         15 $json;
32             }
33              
34             sub action_revoke {
35 2     2   15 my ($self, $cert) = @_;
36 2         35 my $sslmaker = App::sslmaker->new;
37 2         12 my $passphrase = $self->home->child(qw(private passphrase));
38              
39 2 50       189 $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         285 $self->_warn("Done.\n");
49             }
50              
51             sub action_root {
52 1     1   2 my $self = shift;
53 1 50       4 die "--subject is required\n" unless $self->subject;
54              
55 1   50     13 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         148 $self->_print(d %$args);
65              
66 1         13 my $sslmaker = App::sslmaker->new;
67 1         3 $sslmaker->subject($self->subject);
68 1         7 $sslmaker->make_directories({home => $args->{home}, templates => 1});
69 1     1   13 $self->run_maybe($args->{key}, sub { $sslmaker->$wrapper(make_key => $args) });
  1         4  
70 1     1   95 $self->run_maybe($args->{cert}, sub { $sslmaker->$wrapper(make_cert => $args) });
  1         9  
71 1         83 $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   16 my $self = shift;
84 2   50     9 my $cn = shift || die "Usage: $0 generate \n";
85              
86 2         14 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         100 my $sslmaker = App::sslmaker->new;
96 2         24 my $intermediate_cert = $self->home->child(qw(certs ca.cert.pem));
97 2 50       175 $sslmaker->subject(
98             -r $intermediate_cert ? ($intermediate_cert, $self->subject) : ($self->subject));
99 2     2   85 $self->run_maybe($args->{key}, sub { $sslmaker->make_key($args) });
  2         43  
100 2     2   159 $self->run_maybe($args->{csr}, sub { $sslmaker->make_csr($args) });
  2         21  
101 2         122 $self->_print("// Next: Need to send $args->{csr} to SSL admin for signing.\n");
102 2         28 $self->_warn("Done.\n");
103             }
104              
105             sub action_intermediate {
106 1     1   8 my $self = shift;
107              
108 1         9 my $home = $self->home;
109 1   50     12 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         253 $self->_print(d %$args);
123              
124 1         30 my $sslmaker = App::sslmaker->new;
125 1         6 $sslmaker->subject($args->{ca_cert}, $self->subject);
126 1         55 $sslmaker->make_directories({home => $home, templates => 1});
127 1     1   32 $self->run_maybe($args->{key}, sub { $sslmaker->$wrapper(make_key => $args) });
  1         73  
128 1     1   108 $self->run_maybe($args->{csr}, sub { $sslmaker->$wrapper(make_csr => $args) });
  1         42  
129              
130 1         55 $args->{home} = $self->root->parent;
131 1         169 $args->{passphrase} = $self->root->parent->child(qw(passphrase));
132 1     1   152 $self->run_maybe($args->{cert}, sub { $sslmaker->$wrapper(sign_csr => $args) });
  1         12  
133              
134 1         55 $args->{chain_cert} = $home->child(qw(certs ca-chain.cert.pem));
135 1         130 $sslmaker->_cat(@$args{qw( cert ca_cert chain_cert )});
136 1         7 $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   16 my ($sslmaker, $output) = @_;
142 1 50       61 die $output if $output =~ /error/;
143             }
144 1         33 );
145              
146 1         49 $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   7 my ($self, $csr, $cert) = @_;
170 2         32 my $sslmaker = App::sslmaker->new;
171 2         11 my $home = $self->home;
172              
173 2 50       25 unless ($cert) {
174 2         4 $cert = $csr;
175 2         41 $cert =~ s!(\.csr)?\.pem$!\.cert.pem!;
176             }
177              
178 2         19 $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             days => $self->days,
186             extensions => 'usr_cert',
187             passphrase => $home->child(qw(private passphrase)),
188             }
189             );
190              
191 2         261 $self->_print("// Generated $cert\n");
192 2         38 $self->_warn("Done.\n");
193 2         32 $self->_warn("Run this command for more details: openssl x509 -in $cert -noout -text\n");
194             }
195              
196             sub catch {
197 0     0   0 my $self = shift;
198 0         0 my $errno = $!;
199 0         0 my $errstr = $@;
200              
201             # remove stacktrace and rewrite invalid input
202 0 0       0 $errstr =~ s!\sat\s\S+\sline.*!!s unless $ENV{HARNESS_ACTIVE};
203 0         0 $errstr =~ s!"subject"!--subject!s;
204              
205             # parse openssl exception
206 0 0       0 if ($errstr =~ s!\sFAIL\s\((\d+)\)\s\((.*)\)$!!s) {
207 0         0 $errno = $1;
208 0         0 $errstr = $2;
209             }
210              
211 0         0 $! = $errno;
212 0         0 die $errstr;
213             }
214              
215             sub run_maybe {
216 9     9   76 my ($self, $file, $cb) = @_;
217              
218 9 50       126 if (-e $file) {
219 0         0 $self->_print("// File $file exists.\n");
220             }
221             else {
222 9         270 $self->$cb;
223 9         817 $self->_print("// Generated $_[1]\n");
224             }
225             }
226              
227             sub _build_home {
228 8     8   18 my $self = shift;
229              
230 8   33     56 my $path = $self->home || $ENV{SSLMAKER_HOME};
231 8 50       209 return $path if $path;
232              
233 0         0 for (qw(pki ssl)) {
234 0         0 my $path = "/etc/$_";
235 0 0       0 return "$path/sslmaker" if -w $path;
236             }
237              
238 0         0 die "Cannot detect default --home. Maybe you have to run as root?\n";
239             }
240              
241             sub _root_file {
242 4     4   331 my ($self, $ext) = @_;
243 4         29 my $base = $self->root->basename;
244 4         119 $base =~ s!\b(cert|key)\b!$ext!;
245 4         14 return $self->root->parent->child($base);
246             }
247              
248 16 50   16   352 sub _print { shift->silent or print @_ }
249 10 50   10   111 sub _warn { shift->silent or warn @_ }
250              
251             app {
252             my ($self, $action, @args) = @_;
253             $action ||= 'man';
254             $action = 'man' if grep { $action eq $_ } qw(help pod);
255              
256             if ($action ne 'man') {
257             $self->home(Path::Tiny->new($self->_build_home)->absolute);
258             $self->root(Path::Tiny->new($self->root || $self->home->child(qw(root ca.key.pem)))->absolute);
259             }
260              
261             unless ($action and $self->can("action_$action")) {
262             $self->_script->print_help;
263             return 0;
264             }
265              
266             eval {
267             require App::sslmaker;
268             $self->can("action_$action")->($self, @args);
269             1;
270             } or $self->catch;
271              
272             return 0;
273             };