#!/bin/sh
#
# make-local-cert -- Generate a self-signed certificate for Apache.
#
# Written by Russ Allbery <rra@stanford.edu>
# Copyright 2008, 2009, 2011, 2013
#     The Board of Trustees of the Leland Stanford Junior University

# Exit on any error.
set -e

# Determine the local operating system, which decides things like ownership.
if [ -f "/etc/debian_version" ]; then
    os="debian"
elif [ -f "/etc/redhat-release" ]; then
    os="redhat"
else
    echo "Unknown OS!" >&2
    exit 1
fi

# Default directory locations.
cd /etc/ssl
certdir="/etc/ssl/certs"
keydir="/etc/ssl/private"

# Get the local hostname and parse command-line options.
strip=
multicert=
while getopts 'c:k:mp' option ; do
    case $option in
        c)  certdir="$OPTARG" ;;
        k)  keydir="$OPTARG"  ;;
        m)  multicert=true    ;;
        p)  strip=true        ;;
        \?) exit 1            ;;
    esac
done
shift `expr $OPTIND - 1`
if [ -n "$1" ] ; then
    fqdn="$1"
    hostname=`echo "$fqdn" | sed 's/\..*//'`
else
    hostname=`hostname --short`
    fqdn=`hostname --fqdn`
fi
if [ -n "$strip" ] ; then
    hostname=`echo "$hostname" | sed 's/[0-9]*$//'`
    fqdn=`echo "$fqdn" | sed 's/[0-9]*\././'`
fi

# RHEL packages don't provide the /etc/ssl dir and subdirs, so create them if
# they're missing.
if [ ! -d /etc/ssl ] ; then
    mkdir -p /etc/ssl/certs
    mkdir -m 710 -p /etc/ssl/private
fi

# Don't overwrite an existing certificate.
if [ -f "$keydir/$hostname.key" ] || [ -f "$certdir/$hostname.pem" ] ; then
    echo "Certificate for $hostname already exists, remove if unwanted" >&2
    exit 1
fi
if [ -z "$multicert" ] ; then
    if [ -f "$keydir/server.key" ] || [ -f "$certdir/server.pem" ] ; then
        echo "Certificate for server already exists, remove if unwanted" >&2
        exit 1
    fi
fi

# Generate a private key and a self-signed certificate.
openssl req -x509 -nodes -days 3652 -newkey rsa:2048 \
    -subj "/C=US/ST=CA/L=Stanford/O=Stanford University/OU=ITS/CN=$fqdn" \
    -out "$certdir/$hostname.pem" -keyout "$keydir/$hostname.key"

# Fix the ownership and permissions on the private key.
chmod 640 "$keydir/$hostname.key"
if [ x"$os" = "xdebian" ]; then
    chgrp ssl-cert "$keydir/$hostname.key"
fi

# Create the links for server.key and server.pem, which are referenced in the
# Apache configuration, if -m wasn't given.
if [ -z "$multicert" ] ; then
    ln -s "$hostname.key" "$keydir/server.key"
    ln -s "$hostname.pem" "$certdir/server.pem"
fi

# Create the certificate hash, used to find the certificate for validation
# when given a certificate directory.  This probably is never used, but it
# doesn't hurt.
if [ x"$os" = "xdebian" ]; then
    hash=`openssl x509 -noout -subject_hash -in "$certdir/$hostname.pem"`
elif [ x"$os" = "xredhat" ]; then
    hash=`openssl x509 -noout -hash < "$certdir/$hostname.pem"`
fi
rm -f "$certdir/$hash.0"
ln -s "$hostname.pem" "$certdir/$hash.0"

exit 0


# Documentation.  Use a hack to hide this from the shell.  Because of the
# above exit line, this should never be executed.
DOCS=<<__END_OF_DOCS__

=for stopwords
Allbery IP OpenSSL SSL-enabled certdir fqdn hostname keydir

=head1 NAME

make-local-cert - Generate a self-signed certificate for Apache

=head1 SYNOPSIS

B<make-local-cert> [B<-c> I<certdir>] [B<-k> I<keydir> [B<-mp>] [I<fqdn>]

=head1 DESCRIPTION

B<make-local-cert> generates a random key and a self-signed server
certificate using OpenSSL, normally intended for use with a web server
that doesn't need to be publicly visible (for testing, for example) or
where the certificate warnings don't warrant spending money on a real
certificate.  The self-signed certificate will expire after ten years.

The subject of the certificate will be forced to:

    /C=US/ST=CA/L=Stanford/O=Stanford University/OU=ITS/CN=<fqdn>

where I<fqdn> is the fully qualified hostname, taken from the command-line
I<fqdn> argument if given and the results of C<hostname --fqdn> if not.

The resulting self-signed certificate will be placed in
F</etc/ssl/certs/I<hostname>.pem> by default.  The directory can be
overridden with the B<-c> command-line option.  I<hostname> is the I<fqdn>
argument with any domain stripped off or C<hostname --short> if I<fqdn>
wasn't given.

The private key will be placed in F</etc/ssl/private/I<hostname>.key> by
default.  The directory can be overridden with the B<-k> command-line
option.  The file will be mode 640, and on Debian it will be given a group
ownership of C<ssl-cert>.

Symlinks will then be created pointing to the newly generated key and
certificate at F</etc/ssl/private/server.key> and
F</etc/ssl/certs/server.pem> (with the directories overridden by B<-c> and
B<-k> as above).

Finally, the OpenSSL hash symlink for the new certificate will be created,
pointing to the newly generated certificate, in the certificate directory.
This allows the certificate to be located automatically by software
configured to look in that directory for certificates.

=head1 OPTIONS

=over 4

=item B<-c> I<certdir>

Override the default directory for the public certificate, which is
F</etc/ssl/certs> by default.  This option only overrides the directory,
not the file name.  The file name is forced to match the host name.

=item B<-k> I<keydir>

Override the default directory for the private key, which is
F</etc/ssl/private> by default.  This option only overrides the directory,
not the file name.  The file name is forced to match the host name.

=item B<-m>

If this option is given, the symlinks to F<server.key> and F<server.pem>
(or F<server.crt>) will not be made.  This allows multiple certificates to
be generated on the same system.  Perhaps the system has multiple IP
addresses with different SSL-enabled virtual hosts.

=item B<-p>

If this option is given, rather than base the certificate name on the
local system, the certificate name is instead based on the local system
with any trailing digits in the hostname stripped off.  This is intended
for use with systems that will be part of a testing load balance pool.

=back

=head1 FILES

The following locations are used:

=over 4

=item F</etc/ssl/certs/I<hostname>.pem>

=item F</etc/ssl/private/I<hostname>.key>

The self-signed certificate and private key created by this script.  The
directories can be overridden with B<-c> and B<-k> respectively.

=item F</etc/ssl/certs/server.pem>

=item F</etc/ssl/private/server.key>

Symbolic links created by this script, pointing to the generate
certificate and private key.  The directories can be overridden with B<-c>
and B<-k> respectively, and these symlinks can be suppressed with B<-m>.

=back

=head1 SEE ALSO

make-ssl-cert(8), req(1SSL), x509(1SSL)

=head1 AUTHOR

Russ Allbery <rra@stanford.edu>

=cut

__END_OF_DOCS__
