| 179 |
rodolico |
1 |
#! /usr/bin/env perl
|
|
|
2 |
|
| 182 |
rodolico |
3 |
# Creates a private key, Signing Request, and signed certificate file
|
|
|
4 |
# for a target service. Tested on Apache2
|
|
|
5 |
#
|
|
|
6 |
# Run with the primary domain name as the first parameter, optionally
|
|
|
7 |
# followed by one or more alias names. The certificate will be valid
|
|
|
8 |
# for all names passed on command line
|
|
|
9 |
#
|
|
|
10 |
# CA (key and crt) are in the variables $caCRT and $caKey and new files
|
|
|
11 |
# are placed in $serverCertDir and named based on the first parameter
|
|
|
12 |
#
|
|
|
13 |
# An ext file is created, if it doesn't exist, from $sslConfig and
|
|
|
14 |
# used to set defaults for the actual csr and crt file creation
|
|
|
15 |
|
| 179 |
rodolico |
16 |
use strict;
|
|
|
17 |
use warnings;
|
|
|
18 |
|
| 182 |
rodolico |
19 |
use FindBin;
|
|
|
20 |
use File::Spec;
|
|
|
21 |
use Cwd 'abs_path';
|
|
|
22 |
use File::Basename;
|
| 179 |
rodolico |
23 |
|
| 182 |
rodolico |
24 |
my $binDir = dirname( abs_path( __FILE__ ) ) . '/';
|
|
|
25 |
my $config = $binDir . "makeCert.conf";
|
|
|
26 |
my $sslConfig = $binDir . 'openssl.cnf';
|
|
|
27 |
|
|
|
28 |
my $configFile; # prototype for the domain specific config file
|
|
|
29 |
my $caCRT; # location of the CA crt file
|
|
|
30 |
my $caKey; # location of the CA Key file
|
|
|
31 |
my $serverCertDir; # where to put the server certs
|
|
|
32 |
my $certDays; # number of days a certificate is valid for
|
|
|
33 |
my $caDays; # number of days a CA is good for (not used in this script)
|
|
|
34 |
|
|
|
35 |
|
|
|
36 |
die "Config File $config not found\n" unless -f $config;
|
|
|
37 |
die "openssl config file $sslConfig not found\n" unless -f $sslConfig;
|
|
|
38 |
|
|
|
39 |
eval `cat $config`;
|
|
|
40 |
|
|
|
41 |
die "Can not find CA Cert $caCRT\n" unless -f $caCRT;
|
|
|
42 |
die "Can not find CA Key $caKey\n" unless -f $caKey;
|
|
|
43 |
|
| 185 |
rodolico |
44 |
# this is a sloppy IPv4 recognizer, but it is faster than the more accurate
|
|
|
45 |
# ^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$
|
|
|
46 |
# See https://www.oreilly.com/library/view/regular-expressions-cookbook/9780596802837/ch07s16.html
|
|
|
47 |
my $ipv4Regex = '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$';
|
| 182 |
rodolico |
48 |
|
| 179 |
rodolico |
49 |
# they must pass in at least a domain. All other cli params taken as aliases
|
|
|
50 |
# this will also be the filename for each file created, ie $DOMAIN.extension
|
|
|
51 |
my $DOMAIN = shift;
|
|
|
52 |
die "Usage: $0 domain [alias alias]\n" unless $DOMAIN;
|
|
|
53 |
|
| 185 |
rodolico |
54 |
my $extFile = $serverCertDir . "$DOMAIN.ext";
|
|
|
55 |
my $crtFile = $serverCertDir . "$DOMAIN.crt";
|
|
|
56 |
my $keyFile = $serverCertDir . "$DOMAIN.key";
|
|
|
57 |
my $csrFile = $serverCertDir . "$DOMAIN.csr";
|
|
|
58 |
|
| 179 |
rodolico |
59 |
# if the domain doesn't have an ext file, create it
|
| 185 |
rodolico |
60 |
if ( ! -f $extFile ) {
|
|
|
61 |
print "EXT File not found, creating new one\n";
|
| 182 |
rodolico |
62 |
my @newLines;
|
| 179 |
rodolico |
63 |
# read in the default config file
|
|
|
64 |
open CNF, "<$configFile" or die "Could not read $configFile: $!\n";
|
|
|
65 |
my @config = <CNF>;
|
|
|
66 |
close CNF;
|
|
|
67 |
# remove all line endings
|
|
|
68 |
chomp @config;
|
| 182 |
rodolico |
69 |
my $line = 0;
|
|
|
70 |
my $inAltNames = 0;
|
|
|
71 |
for my $line ( @config ) {
|
| 185 |
rodolico |
72 |
if ( $line =~ m/^CN\s*=/ ) { # here is the common name; change it
|
|
|
73 |
$line = "CN = $DOMAIN";
|
|
|
74 |
} elsif ( $line =~ m/^\[\s*alt_names\s*\]/ ) {
|
| 182 |
rodolico |
75 |
$inAltNames = 1;
|
|
|
76 |
next;
|
|
|
77 |
}
|
|
|
78 |
if ( $inAltNames ) {
|
|
|
79 |
next if $line !~ m/^\[/;
|
|
|
80 |
$inAltNames = 0;
|
|
|
81 |
}
|
|
|
82 |
push @newLines, $line;
|
|
|
83 |
}
|
|
|
84 |
# start the alt_names section
|
|
|
85 |
push @newLines, '[ alt_names ]';
|
| 179 |
rodolico |
86 |
# the first DNS entry is the actual domain.
|
| 185 |
rodolico |
87 |
# it will work, but is mislabeled, if $DOMAIN is actually an IP
|
| 182 |
rodolico |
88 |
push @newLines, "DNS.1=$DOMAIN";
|
| 179 |
rodolico |
89 |
my $dns = 2;
|
|
|
90 |
# read in all aliases and add them as a separate DNS entry
|
| 185 |
rodolico |
91 |
# pretty sloppy in that we don't track IP and DNS separately
|
|
|
92 |
# and we are using a sloppy regex to detect IP's, but it
|
|
|
93 |
# is pretty fast.
|
| 179 |
rodolico |
94 |
while ( my $alias = shift ) {
|
| 185 |
rodolico |
95 |
push @newLines, ($alias =~ m/$ipv4Regex/ ? 'IP' : 'DNS' ) . ".$dns=$alias";
|
| 179 |
rodolico |
96 |
$dns++;
|
|
|
97 |
}
|
|
|
98 |
# print the ext file back out
|
| 185 |
rodolico |
99 |
open CNF, ">$extFile" or die "Could not write to $extFile: $!\n";
|
| 182 |
rodolico |
100 |
print CNF join( "\n", @newLines ) . "\n";
|
| 179 |
rodolico |
101 |
close CNF;
|
|
|
102 |
}
|
|
|
103 |
|
| 185 |
rodolico |
104 |
die;
|
|
|
105 |
|
| 179 |
rodolico |
106 |
# Create an rsa key into $DOMAIN.key
|
| 185 |
rodolico |
107 |
`openssl genpkey -algorithm RSA -out $keyFile -pkeyopt rsa_keygen_bits:2048`;
|
| 179 |
rodolico |
108 |
# create a signing request, using $DOMAIN.ext for all the DN stuff saved in $DOMAIN.csr
|
| 185 |
rodolico |
109 |
`openssl req -config $extFile -key $keyFile -new -out $csrFile`;
|
| 179 |
rodolico |
110 |
# generate the actual crt file as $DOMAIN.crt, using the csr and ext file
|
| 185 |
rodolico |
111 |
`openssl x509 -req -in $csrFile -CA $caCRT -CAkey $caKey -CAcreateserial -out $crtFile -days $certDays -extensions req_ext -extfile $extFile`;
|
| 179 |
rodolico |
112 |
|
| 185 |
rodolico |
113 |
print "key and crt created. Use the following command to view the certificate\nopenssl x509 -in $crtFile -text -noout\n";
|
|
|
114 |
print "and the following to view CSR\nopenssl req -in $csrFile -text -noout\n";
|
| 179 |
rodolico |
115 |
1;
|