| 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 |  |  |  |  |  |  | }; |