#!/bin/sh
#
# make-self-cert -- Generate a self-signed certificate
#
# Written by Russ Allbery <rra@stanford.edu>
# Copyright 2008, 2009, 2011, 2013
#     The Board of Trustees of the Leland Stanford Junior University
# Modified by Bill MacAllister <whm@dropbox.com>
#     Renamed the script and replace Stanford identifiers with Dropbox.
# Copyright 2015 Dropbox
# Modified by Bill MacAllister <bill@ca-zephyr.org>
# Copyright 2016 Bill MacAllister

# 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

# Get the local hostname and parse command-line options.
strip=
multicert=
while getopts 'c:k:hmp' option ; do
    case $option in
        h)  help=true         ;;
        c)  certdir="$OPTARG" ;;
        k)  keydir="$OPTARG"  ;;
        m)  multicert=true    ;;
        p)  strip=true        ;;
        \?) exit 1            ;;
    esac
done

# display help
if [ -n "$help" ] ; then
    /usr/bin/pod2text $0
    exit 1
fi

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

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

# 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=Half Moon Bay/O=MacAllister Software/OU=IT/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__

=head1 NAME

make-self-cert - Generate a self-signed certificate

=head1 SYNOPSIS

B<make-local-cert> [B<-c> I<certdir>] [B<-k> I<keydir> [B<-hmp>] [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=Half Moon Bay/O=MacAllister Software/OU=IT/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.

=item B<-h>

Display this text.

=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 AUTHORS

Russ Allbery <rra@stanford.edu>, Bill MacAllister <whm@dropbox.com>,
Bill MacAllister <bill@ca-zephyr.org>

=cut

__END_OF_DOCS__
