File Coverage

script/sslmaker
Criterion Covered Total %
statement 102 121 84.3
branch 17 42 40.4
condition 8 29 27.5
subroutine 24 29 82.7
pod n/a
total 151 221 68.3


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2             BEGIN {
3 1 50 33 1   246084 $ENV{SSLMAKER_DEBUG} //= (grep {/^--silent/} @ARGV) ? 0 : 1;
  1         94  
4             }
5              
6             package App::sslmaker::script;
7 1     1   638 use Getopt::App -complete;
  1         25436  
  1         6  
8              
9 1     1   2332 use if -e 'lib/App/sslmaker.pm', qw(lib lib);
  1         2  
  1         529  
10 1     1   1259 use App::sslmaker;
  1         3  
  1         45  
11 1     1   460 use File::Spec::Functions qw(catdir);
  1         630  
  1         101  
12 1     1   7 use Path::Tiny;
  1         1  
  1         247  
13              
14             my @options = (
15             'ext=s@ # Add extensions such as "subjectAltName=DNS:example.com" (default)',
16             'subject=s # /C=US/ST=Texas/L=Dallas/O=Company/OU=Department/CN=example.com/emailAddress=user@domain',
17             'bits=s # SSL key bit size',
18             'days=s # Number of days the cert should be valid',
19             'home=s # sslmaker working directory',
20             'root=s # Path to root key.pem. Required for making intermediate key+cert',
21             'silent # Only output data on failure',
22             'h|help # Show this help text',
23             );
24              
25             my $wrapper = sub {
26             my ($sslmaker, $method, @args) = @_;
27              
28             eval { return $ENV{OPENSSL_CONF} ? $sslmaker->$method(@args) : $sslmaker->with_config($method, @args); } or do {
29             $@ =~ s/\s+at\s+\S+\s+line.*/\n/s unless $ENV{TRACE};
30             print STDERR $@;
31             exit($? || $!);
32             };
33             };
34              
35             sub getopt_subcommands {
36 0     0   0 my ($self) = @_;
37              
38 1     1   6 no warnings qw(once);
  1         2  
  1         3226  
39 0 0       0 return undef if $Getopt::App::SUBCOMMAND;
40              
41             return [
42             [generate => run(@options, $self->can('subcommand_generate')), 'Generate a client certificate'],
43             [sign => run(@options, $self->can('subcommand_sign')), 'Sign a certificate'],
44             [revoke => run(@options, $self->can('subcommand_revoke')), 'Revoke a certificate'],
45             [intermediate => run(@options, $self->can('subcommand_intermediate')), 'Generate intermediate CA'],
46             [root => run(@options, $self->can('subcommand_root')), 'Generate root CA'],
47             [dhparam => run(@options, $self->can('subcommand_dhparam')), 'Create a dhparam file'],
48             [nginx => run(@options, $self->can('subcommand_nginx')), 'Print example nginx config'],
49 0     0   0 [man => run(@options, sub { exec perldoc => 'App::sslmaker' }), 'Show the sslmaker manual'],
  0         0  
50             ];
51             }
52              
53             sub home {
54 14 50   14   240 return $_[0]->{home} if ref $_[0]->{home}; # Built
55              
56 0         0 my ($self) = @_;
57 0   0     0 my $home = $self->{home} || $ENV{SSLMAKER_HOME};
58 0 0       0 return $self->{home} = Path::Tiny->new($home)->absolute if $home;
59 0   0     0 -w "/etc/$_" && return ($self->{home} = Path::Tiny->new("/etc/$_/sslmaker")->absolute) for qw(pki ssl);
60 0         0 die "Cannot detect default --home. Maybe you have to run as root?\n";
61             }
62              
63             sub root {
64 12 100   12   257 return $_[0]->{root} if ref $_[0]->{root}; # Built
65              
66 1         2 my ($self) = @_;
67 1   33     7 return $self->{root} = Path::Tiny->new($self->{root} || $self->home->child(qw(root ca.key.pem)))->absolute;
68             }
69              
70             sub subcommand_revoke {
71 2     2   10197 my ($self, $cert) = @_;
72 2 50       18 return print extract_usage if $self->{h};
73 2 50       28 die "Usage: sslmaker revoke /path/to/file.cert\n" unless $cert;
74              
75 2         37 my $sslmaker = App::sslmaker->new;
76 2         28 my $passphrase = $self->home->child(qw(private passphrase));
77              
78 2 50       228 $sslmaker->$wrapper(
79             revoke_cert => {
80             cert => $self->home->child(qw(certs ca.cert.pem)),
81             key => $self->home->child(qw(private ca.key.pem)),
82             passphrase => -e $passphrase ? $passphrase : undef,
83             revoke => $cert,
84             }
85             );
86              
87 2         364 return 0;
88             }
89              
90             sub subcommand_root {
91 1     1   5437 my ($self) = @_;
92 1 50       8 return print extract_usage if $self->{h};
93 1 50       4 die "--subject is required\n" unless $self->{subject};
94              
95             my $args = {
96             bits => $self->{bits} || 8192,
97             cert => $self->_root_file('cert'),
98 1   50     43 days => $self->{days} || 365 * 30,
      50        
99             home => $self->root->parent,
100             key => $self->_root_file('key'),
101             passphrase => $self->root->parent->child(qw(passphrase)),
102             };
103              
104 1         58 my $sslmaker = App::sslmaker->new;
105 1         5 $sslmaker->_d('# Root CA settings');
106 1         13 $sslmaker->_d(sprintf '- %-12s %s', "$_:", "$args->{$_}") for sort keys %$args;
107 1         6 $sslmaker->subject($self->{subject});
108 1         5 $sslmaker->make_directories({home => $args->{home}, templates => 1});
109 1     1   12 $self->run_maybe($args->{key}, sub { $sslmaker->$wrapper(make_key => $args) });
  1         6  
110 1     1   29 $self->run_maybe($args->{cert}, sub { $sslmaker->$wrapper(make_cert => $args) });
  1         21  
111             }
112              
113             sub subcommand_dhparam {
114 0     0   0 my ($self, $path, $bits) = @_;
115 0 0       0 return print extract_usage if $self->{h};
116 0   0     0 $path ||= $self->home->child('dhparam.pem');
117 0   0 0   0 $self->run_maybe($path, sub { App::sslmaker::openssl(qw(dhparam -out) => $path, $bits || 2048) });
  0         0  
118             }
119              
120             sub subcommand_generate {
121 2     2   17850 my ($self, $cn) = @_;
122 2 50       16 return print extract_usage if $self->{h};
123 2 50       10 die "Usage: sslmaker generate \n" unless $cn;
124              
125             my $args = {
126             bits => $self->{bits},
127             csr => "$cn.csr.pem",
128             days => $self->{days},
129 2   50     130 ext => $self->{ext} || ["subjectAltName=DNS:$cn"],
130             home => $self->home,
131             key => "$cn.key.pem",
132             subject => "/CN=$cn",
133             };
134              
135 2         31 my $sslmaker = App::sslmaker->new;
136 2         8 my $intermediate_cert = $self->home->child(qw(certs ca.cert.pem));
137 2 50       258 $sslmaker->subject(-r $intermediate_cert ? ($intermediate_cert, $self->{subject}) : ($self->{subject}));
138 2     2   92 $self->run_maybe($args->{key}, sub { $sslmaker->make_key($args) });
  2         41  
139 2     2   188 $self->run_maybe($args->{csr}, sub { $sslmaker->make_csr($args) });
  2         75  
140 2         64 $sslmaker->_d("# It is safe to send $args->{csr} to SSL admin for signing.");
141             }
142              
143             sub subcommand_intermediate {
144 1     1   8446 my ($self) = @_;
145 1 50       8 return print extract_usage if $self->{h};
146 1   50     11 $self->{bits} ||= 8192;
147              
148 1         6 my $home = $self->home;
149             my $args = {
150             bits => $self->{bits},
151             ca_cert => $self->_root_file('cert'),
152             ca_key => $self->_root_file('key'),
153             cert => $home->child(qw(certs ca.cert.pem)),
154             csr => $home->child(qw(certs ca.csr.pem)),
155 1   50     10 days => $self->{days} || 365 * 28,
156             extensions => 'v3_ca',
157             home => $home,
158             key => $home->child(qw(private ca.key.pem)),
159             passphrase => $home->child(qw(private passphrase)),
160             };
161              
162 1         491 my $sslmaker = App::sslmaker->new;
163 1         6 $sslmaker->_d('# Intermediate CA settings');
164 1         15 $sslmaker->_d(sprintf '- %-12s %s', "$_:", "$args->{$_}") for sort keys %$args;
165 1         7 $sslmaker->_d('');
166 1         4 $sslmaker->subject($args->{ca_cert}, $self->{subject});
167 1         24 $sslmaker->make_directories({home => $home, templates => 1});
168 1     1   72 $self->run_maybe($args->{key}, sub { $sslmaker->$wrapper(make_key => $args) });
  1         16  
169 1     1   44 $self->run_maybe($args->{csr}, sub { $sslmaker->$wrapper(make_csr => $args) });
  1         12  
170              
171 1         36 $args->{home} = $self->root->parent;
172 1         171 $args->{passphrase} = $self->root->parent->child(qw(passphrase));
173 1     1   183 $self->run_maybe($args->{cert}, sub { $sslmaker->$wrapper(sign_csr => $args) });
  1         17  
174              
175 1         47 $args->{chain_cert} = $home->child(qw(certs ca-chain.cert.pem));
176 1         165 $sslmaker->_cat(@$args{qw( cert ca_cert chain_cert )});
177 1         8 $sslmaker->_d("# Generated $args->{chain_cert} from CA and intermediate certificate");
178              
179             $sslmaker->openssl(
180             verify => -CAfile => @$args{qw( ca_cert cert )},
181             sub {
182 1     1   18 my ($sslmaker, $output) = @_;
183 1 50       60 die $output if $output =~ /error/;
184             }
185 1         22 );
186              
187 1         133 return 0;
188             }
189              
190             sub subcommand_nginx {
191 0     0   0 my ($self, $domain) = @_;
192 0 0       0 return print extract_usage if $self->{h};
193 0 0       0 die "Usage: sslmaker nginx \n" unless $domain;
194              
195 0         0 print +App::sslmaker->_render_template(
196             'nginx.config',
197             {
198             domain => $domain,
199             key => "/etc/nginx/ssl/$domain.key.pem",
200             cert => "/etc/nginx/ssl/$domain.cert.pem",
201             ca_cert => $self->home->child(qw(certs ca-chain.cert.pem)),
202             },
203             );
204              
205 0         0 return 0;
206             }
207              
208             sub subcommand_sign {
209 2     2   17004 my ($self, $csr, $cert) = @_;
210 2         26 my $home = $self->home;
211 2 50       26 return print extract_usage if $self->{h};
212 2 50       58 die "Usage: sslmaker sign [cert]\n" unless $csr;
213              
214 2   33     23 $cert ||= do { local $_ = $csr; s!(\.csr)?\.pem$!\.cert.pem!; $_ };
  2         190  
  2         37  
  2         12  
215 2         24 my $sslmaker = App::sslmaker->new;
216             $sslmaker->$wrapper(
217             sign_csr => {
218             home => $home,
219             ca_cert => $home->child(qw(certs ca.cert.pem)),
220             ca_key => $home->child(qw(private ca.key.pem)),
221             cert => $cert,
222             csr => $csr,
223             days => $self->{days},
224 2         40 extensions => 'usr_cert',
225             passphrase => $home->child(qw(private passphrase)),
226             }
227             );
228              
229 2         395 $sslmaker->_d("# Generated $cert");
230 2         17 $sslmaker->_d("# Run this command for more details: openssl x509 -in $cert -noout -text");
231             }
232              
233             sub run_maybe {
234 9     9   38 my ($self, $file, $cb) = @_;
235 9 50       185 return App::sslmaker->_d("! File $file exists.") if -e $file;
236 9         324 $self->$cb;
237 9         1134 App::sslmaker->_d("# Generated $_[1]");
238             }
239              
240             sub _root_file {
241 4     4   104 my ($self, $ext) = @_;
242 4         9 my $base = $self->root->basename;
243 4         269 $base =~ s!\b(cert|key)\b!$ext!;
244 4         17 return $self->root->parent->child($base);
245             }
246              
247             run(
248             @options,
249             sub {
250             my ($self) = @_;
251             return print extract_usage if $self->{h};
252             return print extract_usage;
253             },
254             );