#! /usr/bin/env perl # Creates a private key, Signing Request, and signed certificate file # for a target service. Tested on Apache2 # # Run with the primary domain name as the first parameter, optionally # followed by one or more alias names. The certificate will be valid # for all names passed on command line # # CA (key and crt) are in the variables $caCRT and $caKey and new files # are placed in $serverCertDir and named based on the first parameter # # An ext file is created, if it doesn't exist, from $sslConfig and # used to set defaults for the actual csr and crt file creation use strict; use warnings; use FindBin; use File::Spec; use Cwd 'abs_path'; use File::Basename; my $binDir = dirname( abs_path( __FILE__ ) ) . '/'; my $config = $binDir . "makeCert.conf"; my $sslConfig = $binDir . 'openssl.cnf'; my $configFile; # prototype for the domain specific config file my $caCRT; # location of the CA crt file my $caKey; # location of the CA Key file my $serverCertDir; # where to put the server certs my $certDays; # number of days a certificate is valid for my $caDays; # number of days a CA is good for (not used in this script) die "Config File $config not found\n" unless -f $config; die "openssl config file $sslConfig not found\n" unless -f $sslConfig; eval `cat $config`; die "Can not find CA Cert $caCRT\n" unless -f $caCRT; die "Can not find CA Key $caKey\n" unless -f $caKey; # this is a sloppy IPv4 recognizer, but it is faster than the more accurate # ^(?:(?: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]?)$ # See https://www.oreilly.com/library/view/regular-expressions-cookbook/9780596802837/ch07s16.html my $ipv4Regex = '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$'; # they must pass in at least a domain. All other cli params taken as aliases # this will also be the filename for each file created, ie $DOMAIN.extension my $DOMAIN = shift; die "Usage: $0 domain [alias alias]\n" unless $DOMAIN; my $extFile = $serverCertDir . "$DOMAIN.ext"; my $crtFile = $serverCertDir . "$DOMAIN.crt"; my $keyFile = $serverCertDir . "$DOMAIN.key"; my $csrFile = $serverCertDir . "$DOMAIN.csr"; # if the domain doesn't have an ext file, create it if ( ! -f $extFile ) { print "EXT File not found, creating new one\n"; my @newLines; # read in the default config file open CNF, "<$configFile" or die "Could not read $configFile: $!\n"; my @config = ; close CNF; # remove all line endings chomp @config; my $line = 0; my $inAltNames = 0; for my $line ( @config ) { if ( $line =~ m/^CN\s*=/ ) { # here is the common name; change it $line = "CN = $DOMAIN"; } elsif ( $line =~ m/^\[\s*alt_names\s*\]/ ) { $inAltNames = 1; next; } if ( $inAltNames ) { next if $line !~ m/^\[/; $inAltNames = 0; } push @newLines, $line; } # start the alt_names section push @newLines, '[ alt_names ]'; # the first DNS entry is the actual domain. # it will work, but is mislabeled, if $DOMAIN is actually an IP push @newLines, "DNS.1=$DOMAIN"; my $dns = 2; # read in all aliases and add them as a separate DNS entry # pretty sloppy in that we don't track IP and DNS separately # and we are using a sloppy regex to detect IP's, but it # is pretty fast. while ( my $alias = shift ) { push @newLines, ($alias =~ m/$ipv4Regex/ ? 'IP' : 'DNS' ) . ".$dns=$alias"; $dns++; } # print the ext file back out open CNF, ">$extFile" or die "Could not write to $extFile: $!\n"; print CNF join( "\n", @newLines ) . "\n"; close CNF; } # Create an rsa key into $DOMAIN.key `openssl genpkey -algorithm RSA -out $keyFile -pkeyopt rsa_keygen_bits:2048` unless -f $keyFile; # create a signing request, using $DOMAIN.ext for all the DN stuff saved in $DOMAIN.csr `openssl req -config $extFile -key $keyFile -new -out $csrFile` unless -f $csrFile; # generate the actual crt file as $DOMAIN.crt, using the csr and ext file `openssl x509 -req -in $csrFile -CA $caCRT -CAkey $caKey -CAcreateserial -out $crtFile -days $certDays -extensions req_ext -extfile $extFile`; print "key and crt created. Use the following command to view the certificate\nopenssl x509 -in $crtFile -text -noout\n"; print "and the following to view CSR\nopenssl req -in $csrFile -text -noout\n"; 1;