|
|
|
#!/bin/sh
|
|
|
|
# Filename: gencert.sh
|
|
|
|
# Description: This script generates x509 server certificate (with all IPs in
|
|
|
|
# SAN) signed by a self-signed CA.
|
|
|
|
# Version: 1.6 - 2018 December 27
|
|
|
|
# Author: Andrey Arapov <andrey.arapov@nixaid.com>
|
|
|
|
# License: GPLv3
|
|
|
|
|
|
|
|
ME=$(printf '%s\n' "${0##*/}")
|
|
|
|
|
|
|
|
print_help() {
|
|
|
|
printf "[${ME}] HELP: I accept following arguments:
|
|
|
|
--help - show this message
|
|
|
|
--cn - certificate's CN name\t\t(MANDATORY)
|
|
|
|
--key - server key name\t\t\t(default: private.key)
|
|
|
|
--cert - server cert name\t\t\t(default: public.crt)
|
|
|
|
--days - server cert expiration in days\t(default: 365)
|
|
|
|
--cakey - CA key name\t\t\t(default: ca.key)
|
|
|
|
--ca - CA cert name\t\t\t(default: ca.crt)
|
|
|
|
--cadays - CA cert expiration in days\t(default: 3650)\n
|
|
|
|
--noautosan - do not automatically discover IPs for SAN records\n
|
|
|
|
--san-ip - specify custom SAN IP records manually. Implies --noautosan\n
|
|
|
|
--san-dns - specify custom SAN DNS records manually.\n
|
|
|
|
--debug - show extra information\n
|
|
|
|
--rsa - generate RSA keys instead of ECDSA\n
|
|
|
|
--rsa-size - set RSA key size\n"
|
|
|
|
}
|
|
|
|
|
|
|
|
# Parse command line arguments
|
|
|
|
##
|
|
|
|
|
|
|
|
parse_arguments() {
|
|
|
|
# A POSIX variable
|
|
|
|
OPTIND=1 # Reset in case getopts has been used previously in the shell.
|
|
|
|
|
|
|
|
# read arguments
|
|
|
|
opts=$(getopt \
|
|
|
|
--longoptions "help,cn:,key:,cert:,days:,cakey:,ca:,cadays:,noautosan,san-ip:,san-dns:,debug,rsa,rsa-size:" \
|
|
|
|
--name "$(basename "$0")" \
|
|
|
|
--options "" \
|
|
|
|
-- "$@"
|
|
|
|
)
|
|
|
|
|
|
|
|
eval set --$opts
|
|
|
|
|
|
|
|
while [ $# -gt 0 ]; do
|
|
|
|
case "$1" in
|
|
|
|
--help)
|
|
|
|
print_help;
|
|
|
|
exit 0
|
|
|
|
;;
|
|
|
|
|
|
|
|
--cn)
|
|
|
|
ARG_CN=$2
|
|
|
|
shift 2
|
|
|
|
;;
|
|
|
|
|
|
|
|
--key)
|
|
|
|
ARG_KEY=$2
|
|
|
|
shift 2
|
|
|
|
;;
|
|
|
|
|
|
|
|
--cert)
|
|
|
|
ARG_CERT=$2
|
|
|
|
shift 2
|
|
|
|
;;
|
|
|
|
|
|
|
|
--days)
|
|
|
|
ARG_DAYS=$2
|
|
|
|
shift 2
|
|
|
|
;;
|
|
|
|
|
|
|
|
--cakey)
|
|
|
|
ARG_CAKEY=$2
|
|
|
|
shift 2
|
|
|
|
;;
|
|
|
|
|
|
|
|
--ca)
|
|
|
|
ARG_CA=$2
|
|
|
|
shift 2
|
|
|
|
;;
|
|
|
|
|
|
|
|
--cadays)
|
|
|
|
ARG_CADAYS=$2
|
|
|
|
shift 2
|
|
|
|
;;
|
|
|
|
|
|
|
|
--noautosan)
|
|
|
|
ARG_NOAUTOSAN=1
|
|
|
|
shift 1
|
|
|
|
;;
|
|
|
|
|
|
|
|
--san-ip)
|
|
|
|
ARG_NOAUTOSAN=1
|
|
|
|
ARG_SAN_IP=$2
|
|
|
|
shift 2
|
|
|
|
;;
|
|
|
|
|
|
|
|
--san-dns)
|
|
|
|
ARG_SAN_DNS=$2
|
|
|
|
shift 2
|
|
|
|
;;
|
|
|
|
|
|
|
|
--debug)
|
|
|
|
ARG_DEBUG=1
|
|
|
|
shift 1
|
|
|
|
;;
|
|
|
|
|
|
|
|
--rsa)
|
|
|
|
ARG_RSA=1
|
|
|
|
shift 1
|
|
|
|
;;
|
|
|
|
|
|
|
|
--rsa-size)
|
|
|
|
ARG_RSA_SIZE=$2
|
|
|
|
shift 2
|
|
|
|
;;
|
|
|
|
|
|
|
|
*)
|
|
|
|
break
|
|
|
|
;;
|
|
|
|
esac
|
|
|
|
done
|
|
|
|
|
|
|
|
# prepare common variables
|
|
|
|
##
|
|
|
|
|
|
|
|
OPENSSL_CONFIG="openssl.cnf"
|
|
|
|
CN="${ARG_CN}"
|
|
|
|
CA_KEY="${ARG_CAKEY:-ca.key}"
|
|
|
|
CA_CERT="${ARG_CA:-ca.crt}"
|
|
|
|
CA_DAYS="${ARG_CADAYS:-3650}"
|
|
|
|
SERVER_KEY="${ARG_KEY:-private.key}"
|
|
|
|
SERVER_CERT="${ARG_CERT:-public.crt}"
|
|
|
|
DAYS="${ARG_DAYS:-365}"
|
|
|
|
NOAUTOSAN="${ARG_NOAUTOSAN}"
|
|
|
|
SAN_IP="${ARG_SAN_IP}"
|
|
|
|
SAN_DNS="${ARG_SAN_DNS}"
|
|
|
|
DEBUG="${ARG_DEBUG}"
|
|
|
|
RSA="${ARG_RSA}"
|
|
|
|
RSA_SIZE="${ARG_RSA_SIZE:-2048}"
|
|
|
|
|
|
|
|
if [ -z "${CN}" ]; then
|
|
|
|
echo "[${ME}] ERROR: Please specify CN, example \"--cn your.site.com\""
|
|
|
|
print_help;
|
|
|
|
exit 1
|
|
|
|
fi
|
|
|
|
|
|
|
|
# For debugging purposes
|
|
|
|
if [ "${DEBUG}" -eq 1 ]; then
|
|
|
|
echo CN=$CN
|
|
|
|
echo KEY=$KEY
|
|
|
|
echo CERT=$CERT
|
|
|
|
echo DAYS=$DAYS
|
|
|
|
echo CAKEY=$CAKEY
|
|
|
|
echo CA=$CA
|
|
|
|
echo CADAYS=$CADAYS
|
|
|
|
echo NOAUTOSAN=$NOAUTOSAN
|
|
|
|
echo SAN_IP=$SAN_IP
|
|
|
|
echo SAN_DNS=$SAN_DNS
|
|
|
|
echo DEBUG=$DEBUG
|
|
|
|
echo RSA=$RSA
|
|
|
|
echo RSA_SIZE=$RSA_SIZE
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
# install openssl
|
|
|
|
##
|
|
|
|
|
|
|
|
has_openssl() {
|
|
|
|
type openssl >/dev/null 2>&1
|
|
|
|
if [ $? -eq 0 ]; then
|
|
|
|
return;
|
|
|
|
fi
|
|
|
|
|
|
|
|
if [ $(id -u) -ne 0 ]; then
|
|
|
|
echo "This script must be run as root in order to install openssl package."
|
|
|
|
echo "If you cannot run this script as root, then make sure you have the openssl package."
|
|
|
|
exit 1
|
|
|
|
fi
|
|
|
|
|
|
|
|
if [ -f /etc/debian_version ]; then
|
|
|
|
echo "[${ME}] Installing openssl in Debian/Ubuntu"
|
|
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
|
|
apt-get update
|
|
|
|
apt-get -y install openssl
|
|
|
|
elif [ -f /etc/alpine-release ]; then
|
|
|
|
echo "[${ME}] Installing openssl in Alpine"
|
|
|
|
apk add --update openssl
|
|
|
|
elif [ -f /etc/centos-release ]; then
|
|
|
|
echo "[${ME}] Installing openssl in CentOS"
|
|
|
|
yum -y install openssl
|
|
|
|
fi
|
|
|
|
|
|
|
|
type openssl >/dev/null
|
|
|
|
if [ $? -ne 0 ]; then
|
|
|
|
echo "[${ME}] ERROR: Could not install openssl. Exitting."
|
|
|
|
exit 1
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
# install getopt
|
|
|
|
##
|
|
|
|
|
|
|
|
has_getopt() {
|
|
|
|
type getopt >/dev/null 2>&1
|
|
|
|
if [ $? -eq 0 ]; then
|
|
|
|
return;
|
|
|
|
fi
|
|
|
|
|
|
|
|
if [ $(id -u) -ne 0 ]; then
|
|
|
|
echo "This script must be run as root in order to install getopt tool."
|
|
|
|
echo "If you cannot run this script as root, then make sure you have the getopt tool."
|
|
|
|
exit 1
|
|
|
|
fi
|
|
|
|
|
|
|
|
if [ -f /etc/debian_version ]; then
|
|
|
|
echo "[${ME}] Installing getopt in Debian/Ubuntu"
|
|
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
|
|
apt-get update
|
|
|
|
apt-get -y install util-linux
|
|
|
|
elif [ -f /etc/alpine-release ]; then
|
|
|
|
echo "[${ME}] Installing getopt in Alpine"
|
|
|
|
apk add --update busybox
|
|
|
|
ln -sv $(type -p busybox) /usr/bin/getopt
|
|
|
|
elif [ -f /etc/centos-release ]; then
|
|
|
|
echo "[${ME}] Installing getopt in CentOS"
|
|
|
|
yum -y install util-linux
|
|
|
|
fi
|
|
|
|
|
|
|
|
type getopt >/dev/null
|
|
|
|
if [ $? -ne 0 ]; then
|
|
|
|
echo "[${ME}] ERROR: Could not install getopt. Exitting."
|
|
|
|
exit 1
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
# generate openssl config
|
|
|
|
##
|
|
|
|
|
|
|
|
gen_openssl_config() {
|
|
|
|
OPENSSL_CONFIG_CONTENT="[ req ]
|
|
|
|
distinguished_name = req_distinguished_name
|
|
|
|
[req_distinguished_name]
|
|
|
|
[ v3_ca ]
|
|
|
|
basicConstraints = critical, CA:TRUE
|
|
|
|
keyUsage = critical, digitalSignature, keyEncipherment, keyCertSign
|
|
|
|
[ v3_req_server ]
|
|
|
|
basicConstraints = CA:FALSE
|
|
|
|
keyUsage = critical, digitalSignature, keyEncipherment
|
|
|
|
extendedKeyUsage = serverAuth
|
|
|
|
subjectAltName = @alt_names
|
|
|
|
[ alt_names ]
|
|
|
|
DNS.1=${CN}"
|
|
|
|
|
|
|
|
if [ ! -z "$SAN_IP" ]; then
|
|
|
|
echo "[${ME}] Using user-provided SAN records: " ${SAN_IP}
|
|
|
|
i=1
|
|
|
|
IFS=,
|
|
|
|
PAYLOAD="$(for IP in $SAN_IP; do echo "IP.${i} = ${IP}" ; i=$((i + 1)); done)"
|
|
|
|
unset IFS
|
|
|
|
fi
|
|
|
|
if [ ! -z "$SAN_DNS" ]; then
|
|
|
|
echo "[${ME}] Using user-provided SAN records: " ${SAN_DNS}
|
|
|
|
i=1
|
|
|
|
IFS=,
|
|
|
|
PAYLOAD="${PAYLOAD}\n$(for DNS in $SAN_DNS; do echo "DNS.${i} = ${DNS}" ; i=$((i + 1)); done)"
|
|
|
|
unset IFS
|
|
|
|
fi
|
|
|
|
if [ -z "$NOAUTOSAN" ]; then
|
|
|
|
# Gather IPs for SAN
|
|
|
|
|
|
|
|
i=1
|
|
|
|
IPS="$( (getent ahostsv4 $(hostname) 2>/dev/null || getent hosts $(hostname) 2>/dev/null) | awk '{print $1}' |sort | uniq)"
|
|
|
|
|
|
|
|
echo "[${ME}] Found these IPs: " ${IPS}
|
|
|
|
PAYLOAD="${PAYLOAD}\n$(for IP in $IPS; do echo "IP.${i} = ${IP}" ; i=$((i + 1)); done)"
|
|
|
|
fi
|
|
|
|
|
|
|
|
printf "${OPENSSL_CONFIG_CONTENT}\n${PAYLOAD}\n" > "${OPENSSL_CONFIG}"
|
|
|
|
}
|
|
|
|
|
|
|
|
# generate CA certificate
|
|
|
|
##
|
|
|
|
|
|
|
|
gen_ca() {
|
|
|
|
echo "[${ME}] Generating new CA: ${CA_KEY} / ${CA_CERT} ..."
|
|
|
|
if [ -z "${RSA}" ]; then
|
|
|
|
openssl ecparam -name prime256v1 -genkey -noout -out "${CA_KEY}"
|
|
|
|
else
|
|
|
|
openssl genrsa -out "${CA_KEY}" "${RSA_SIZE}"
|
|
|
|
fi
|
|
|
|
chmod 0600 "${CA_KEY}"
|
|
|
|
openssl req -x509 -new -sha256 -nodes -key "${CA_KEY}" -days "${CA_DAYS}" -out "${CA_CERT}" \
|
|
|
|
-subj "/CN=my-CA" -extensions v3_ca -config "${OPENSSL_CONFIG}"
|
|
|
|
}
|
|
|
|
|
|
|
|
# generate server certificate
|
|
|
|
##
|
|
|
|
|
|
|
|
gen_server_x509() {
|
|
|
|
echo "[${ME}] Generating new server x509: ${SERVER_KEY} / ${SERVER_CERT} ..."
|
|
|
|
if [ -z "${RSA}" ]; then
|
|
|
|
openssl ecparam -name prime256v1 -genkey -noout -out "${SERVER_KEY}"
|
|
|
|
else
|
|
|
|
openssl genrsa -out "${SERVER_KEY}" "${RSA_SIZE}"
|
|
|
|
fi
|
|
|
|
chmod 0600 "${SERVER_KEY}"
|
|
|
|
openssl req -new -sha256 -key "${SERVER_KEY}" -subj "/CN=${CN}" \
|
|
|
|
| openssl x509 -req -sha256 -CA "${CA_CERT}" -CAkey "${CA_KEY}" -CAcreateserial \
|
|
|
|
-out ${SERVER_CERT} -days "${DAYS}" \
|
|
|
|
-extensions v3_req_server -extfile "${OPENSSL_CONFIG}"
|
|
|
|
}
|
|
|
|
|
|
|
|
start() {
|
|
|
|
echo "[${ME}] Started in ${PWD} directory."
|
|
|
|
|
|
|
|
has_getopt;
|
|
|
|
has_openssl;
|
|
|
|
|
|
|
|
parse_arguments "$@";
|
|
|
|
|
|
|
|
gen_openssl_config;
|
|
|
|
|
|
|
|
if [ ! -f "${CA_KEY}" ]; then
|
|
|
|
echo "[${ME}] Could not find ${CA_KEY} file so I will generate a new one."
|
|
|
|
gen_ca;
|
|
|
|
fi
|
|
|
|
|
|
|
|
openssl x509 -in "${CA_CERT}" -noout -checkend 2592000 >/dev/null
|
|
|
|
if [ $? -ne 0 ]; then
|
|
|
|
echo "[${ME}] WARNING! Your CA certificate will expire in less than 30 days."
|
|
|
|
fi
|
|
|
|
|
|
|
|
openssl x509 -in "${CA_CERT}" -noout -checkend 1 >/dev/null
|
|
|
|
if [ $? -ne 0 ]; then
|
|
|
|
echo "[${ME}] WARNING! Your CA certificate has expired, so we will generate a new one."
|
|
|
|
gen_ca;
|
|
|
|
fi
|
|
|
|
|
|
|
|
# Generate a new server certificate with the detected IPs.
|
|
|
|
gen_server_x509;
|
|
|
|
|
|
|
|
echo "[${ME}] The certificates have been generated in ${PWD} directory."
|
|
|
|
|
|
|
|
CERT_INFO="$(openssl x509 -in "${SERVER_CERT}" -noout -text)"
|
|
|
|
echo "${CERT_INFO}" | grep -E "CN=|DNS:|IP Address|Not\ "
|
|
|
|
}
|
|
|
|
|
|
|
|
# script starts here
|
|
|
|
##
|
|
|
|
|
|
|
|
start "$@";
|