File Coverage

script/sslmaker
Criterion Covered Total %
statement 96 113 84.9
branch 11 26 42.3
condition 7 27 25.9
subroutine 24 29 82.7
pod n/a
total 138 195 70.7


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