#!/bin/sh -e
DEFAULT_ORG="Asterisk"
DEFAULT_CA_CN="Asterisk Private CA"
DEFAULT_CLIENT_CN="asterisk"
DEFAULT_SERVER_CN=`hostname -f`
CA_ENCRYPTION_OPT="-des3"
VALIDITY_DAYS=365

# arguments
# $1 "ca" if we are to generate a CA cert
# $2 alternate config file name (for ca)
# $3 alternate common name
# $4 alternate org name
create_config () {
	if [ "$1" = "ca" ]
	then
castring="
[ext]
basicConstraints=CA:TRUE"
	fi

cat > ${2:-"${CONFIG_FILE}"} << EOF
[req]
distinguished_name = req_distinguished_name
prompt = no

[req_distinguished_name]
CN=${3:-"${COMMON_NAME}"}
O=${4:-"${ORG_NAME}"}
${castring}
EOF
}

create_ca () {
	echo "Creating CA key ${CAKEY}"
	openssl genrsa ${CA_ENCRYPTION_OPT} -out ${CAKEY} 4096 > /dev/null
	if [ $? -ne 0 ];
	then
		echo "Failed"
		exit 1
	fi
	echo "Creating CA certificate ${CACERT}"
	openssl req -new -config ${CACFG} -x509 -days ${VALIDITY_DAYS} -key ${CAKEY} -out ${CACERT} > /dev/null
	if [ $? -ne 0 ];
	then
		echo "Failed"
		exit 1
	fi
}

create_cert () {
	local base=${OUTPUT_DIR}/${OUTPUT_BASE}
	echo "Creating certificate ${base}.key"
	openssl genrsa -out ${base}.key ${KEYBITS:-2048} > /dev/null
	if [ $? -ne 0 ];
	then
		echo "Failed"
		exit 1
	fi
	echo "Creating signing request ${base}.csr"
	openssl req -batch -new -config ${CONFIG_FILE} -key ${base}.key -out ${base}.csr > /dev/null
	if [ $? -ne 0 ];
	then
		echo "Failed"
		exit 1
	fi
	echo "Creating certificate ${base}.crt"
	openssl x509 -req -days ${VALIDITY_DAYS} -in ${base}.csr -CA ${CACERT} -CAkey ${CAKEY} -set_serial 01 -out ${base}.crt > /dev/null
	if [ $? -ne 0 ];
	then
		echo "Failed"
		exit 1
	fi
	echo "Combining key and crt into ${base}.pem"
	cat ${base}.key > ${base}.pem
	cat ${base}.crt >> ${base}.pem
}

usage () {
cat << EOF
This script is useful for quickly generating self-signed CA, server, and client
certificates for use with Asterisk. It is still recommended to obtain
certificates from a recognized Certificate Authority and to develop an
understanding how SSL certificates work. Real security is hard work.

OPTIONS:
  -h  Show this message
  -m  Type of cert "client" or "server". Defaults to server.
  -f  Config filename (openssl config file format)
  -c  CA cert filename (creates new CA cert/key as ca.crt/ca.key if not passed)
  -k  CA key filename
  -e  Don't encrypt the CA private key with a passphrase (default is to use 3DES encryption)
  -b  The desired size of the private key in bits. Default is 2048.
  -C  Common name (cert field)
        This should be the fully qualified domain name or IP address for
        the client or server. Make sure your certs have unique common
        names.
  -O  Org name (cert field)
        An informational string (company name)
  -o  Output filename base (defaults to asterisk)
  -d  Output directory (defaults to the current directory)
  -v  CA/certificate validity in days (defaults to 365)

Example:

To create a CA and a server (pbx.mycompany.com) cert with output in /tmp:
  ast_tls_cert -C pbx.mycompany.com -O "My Company" -d /tmp

This will create a CA cert and key as well as asterisk.pem and the the two
files that it is made from: asterisk.crt and asterisk.key. Copy asterisk.pem
and ca.crt somewhere (like /etc/asterisk) and set tlscertfile=/etc/asterisk.pem
and tlscafile=/etc/ca.crt. Since this is a self-signed key, many devices will
require you to import the ca.crt file as a trusted cert.

To create a client cert using the CA cert created by the example above:
  ast_tls_cert -m client -c /tmp/ca.crt -k /tmp/ca.key -C phone1.mycompany.com \\
    -O "My Company" -d /tmp -o joe_user

This will create client.crt/key/pem in /tmp. Use this if your device supports
a client certificate. Make sure that you have the ca.crt file set up as
a tlscafile in the necessary Asterisk configs. Make backups of all .key files
in case you need them later.
EOF
}

if ! type openssl >/dev/null 2>&1
then
	echo "This script requires openssl to be in the path"
	exit 1
fi

OUTPUT_BASE=asterisk # Our default cert basename
CERT_MODE=server
ORG_NAME=${DEFAULT_ORG}

while getopts "hf:c:ek:o:d:m:C:O:b:v:" OPTION
do
	case ${OPTION} in
		h)
			usage
			exit 1
			;;
		f)
			CONFIG_FILE=${OPTARG}
			;;
		c)
			CACERT=${OPTARG}
			;;
		e)
			CA_ENCRYPTION_OPT=""
			;;
		k)
			CAKEY=${OPTARG}
			;;
		b)
			KEYBITS=${OPTARG}
			;;
		v)
			VALIDITY_DAYS=${OPTARG}
			;;
		o)
			OUTPUT_BASE=${OPTARG}
			;;
		d)
			OUTPUT_DIR=${OPTARG}
			;;
		m)
			CERT_MODE=${OPTARG}
			;;
		C)
			COMMON_NAME=${OPTARG}
			;;
		O)
			ORG_NAME=${OPTARG}
			;;
		?)
			usage
			exit
			;;
	esac
done

if [ -z "${OUTPUT_DIR}" ]
then
	OUTPUT_DIR=.
else
	mkdir -p "${OUTPUT_DIR}"
fi

umask 177

case "${CERT_MODE}" in
	server)
		COMMON_NAME=${COMMON_NAME:-"${DEFAULT_SERVER_CN}"}
		;;
	client)
		COMMON_NAME=${COMMON_NAME:-"${DEFAULT_CLIENT_CN}"}
		;;
	*)
		echo
		echo "Unknown mode. Exiting."
		exit 1
		;;
esac

if [ -z "${CONFIG_FILE}" ]
then
	CONFIG_FILE="${OUTPUT_DIR}/tmp.cfg"
	echo
	echo "No config file specified, creating '${CONFIG_FILE}'"
	echo "You can use this config file to create additional certs without"
	echo "re-entering the information for the fields in the certificate"
	create_config
fi

if [ -z ${CACERT} ]
then
	CAKEY=${OUTPUT_DIR}/ca.key
	CACERT=${OUTPUT_DIR}/ca.crt
	CACFG=${OUTPUT_DIR}/ca.cfg
	if [ ! -r "$CAKEY" ] &&  [ ! -r "$CACFG" ]; then
		create_config ca "${CACFG}" "${DEFAULT_CA_CN}" "${DEFAULT_CA_ORG}"
	fi
	if  [ ! -r "$CACERT" ]; then
		create_ca
	fi
else
	if [ -z ${CAKEY} ]
	then
		echo "-k must be specified if -c is"
		exit 1
	fi
fi

create_cert
