#!/bin/sh # # shellpki is a wrapper around openssl to manage a small PKI # set -eu init() { umask 0177 if [ -f "${CADIR}/private.key" ]; then echo "${CADIR}/private.key already exists, do you really want to erase it ?\n" echo "Press return to continue..." read -r REPLY fi [ -d "${CADIR}" ] || mkdir -pm 0700 "${CADIR}" [ -d "${CADIR}/certs" ] || mkdir -m 0777 "${CADIR}/certs" [ -d "${CADIR}/tmp" ] || mkdir -m 0700 "${CADIR}/tmp" [ -f "${CADIR}/index.txt" ] || touch "${CADIR}/index.txt" [ -f "${CADIR}/serial" ] || echo "01" > "${CADIR}/serial" "${OPENSSL}" req \ -config "${CONFFILE}" \ -newkey rsa:4096 -sha512 \ -x509 -days 3650 \ -extensions v3_ca \ -keyout "${CADIR}/private.key" \ -out "${CADIR}/cacert.pem" } usage() { cat < [options] [CommonName] Initialize PKI (create CA key and self-signed cert) : ${0} init Create a client cert with key and CSR directly generated on server (use -p for set a password on client key) : ${0} create [-p] Create a client cert from a CSR (doesn't need key) : ${0} create -f Revoke a client cert with is commonName (CN) : ${0} revoke List all actually valid commonName (CN) : ${0} list [-a|v|r] EOF } error() { echo "${1}" >&2 exit 1 } warning() { echo "${1}" >&2 } ask_ca_password() { [ ! -f "${CADIR}/private.key" ] && error "You must initialize your's PKI with shellpki init !" attempt=$((${1} + 1)) [ "${attempt}" -gt 1 ] && warning "Invalid password, retry." trap 'unset CA_PASSWORD' 0 stty -echo printf "Password for CA key : " read CA_PASSWORD stty echo printf "\n" [ "${CA_PASSWORD}" != "" ] || ask_ca_password "${attempt}" CA_PASSWORD="${CA_PASSWORD}" "${OPENSSL}" rsa \ -in "${CADIR}/private.key" \ -passin env:CA_PASSWORD \ >/dev/null 2>&1 \ || ask_ca_password "${attempt}" } create() { from_csr=1 with_pass=1 while getopts ":f:p" opt; do case "$opt" in f) [ ! -f "${OPTARG}" ] && error "${OPTARG} must be a file" from_csr=0 csr_file=$(readlink -f "${OPTARG}") shift 2;; p) with_pass=0 shift;; :) error "Option -$OPTARG requires an argument." esac done cn="${1:-}" [ "${cn}" = "--" ] && shift if [ "${from_csr}" -eq 0 ]; then [ "${with_pass}" -eq 0 ] && warning "Warning: -p made nothing with -f" # ask for CA passphrase ask_ca_password 0 # check if csr_file is a CSR "${OPENSSL}" req \ -noout -subject \ -in "${csr_file}" \ >/dev/null 2>&1 \ || error "${csr_file} is not a valid CSR !" # check if csr_file contain a CN "${OPENSSL}" req \ -noout -subject \ -in "${csr_file}" \ | grep -Eo "CN\s*=[^,/]*" \ >/dev/null 2>&1 \ || error "${csr_file} don't contain a CommonName !" # get CN from CSR cn=$("${OPENSSL}" req -noout -subject -in "${csr_file}"|grep -Eo "CN\s*=[^,/]*"|cut -d'=' -f2|xargs) # check if CN already exist [ -f "${CADIR}/certs/${cn}.crt" ] && error "${cn} already used !" # ca sign and generate cert CA_PASSWORD="${CA_PASSWORD}" "${OPENSSL}" ca \ -config "${CONFFILE}" \ -in "${csr_file}" \ -passin env:CA_PASSWORD \ -out "${CADIR}/certs/${cn}.crt" else [ -z "${cn}" ] && usage >&2 && exit 1 # check if CN already exist [ -f "${CADIR}/certs/${cn}.crt" ] && error "${cn} already used !" # ask for client key passphrase if [ "${with_pass}" -eq 0 ]; then trap 'unset PASSWORD' 0 stty -echo printf "Password for user key : " read PASSWORD stty echo printf "\n" fi # ask for CA passphrase ask_ca_password 0 # generate private key if [ "${with_pass}" -eq 0 ]; then PASSWORD="${PASSWORD}" "$OPENSSL" genrsa \ -aes256 -passout env:PASSWORD \ -out "${KEYDIR}/${cn}-${TIMESTAMP}.key" \ 2048 >/dev/null 2>&1 else "$OPENSSL" genrsa \ -out "${KEYDIR}/${cn}-${TIMESTAMP}.key" \ 2048 >/dev/null 2>&1 fi if [ "${with_pass}" -eq 0 ]; then # generate csr req PASSWORD="${PASSWORD}" "$OPENSSL" req \ -batch -new \ -key "${KEYDIR}/${cn}-${TIMESTAMP}.key" \ -passin env:PASSWORD \ -out "${CSRDIR}/${cn}-${TIMESTAMP}.csr" \ -config /dev/stdin </dev/null 2>&1 \ || rm -f "${CADIR}/certs/${cn}.crt" [ -f "${CADIR}/certs/${cn}.crt" ] || error "Error in CSR creation" # generate pem format cat "${CADIR}/certs/${cn}.crt" "${CADIR}/cacert.pem" "${KEYDIR}/${cn}-${TIMESTAMP}.key" >> "${PEMDIR}/${cn}-${TIMESTAMP}.pem" # generate pkcs12 format if [ "${with_pass}" -eq 0 ]; then PASSWORD="${PASSWORD}" "${OPENSSL}" pkcs12 -export -nodes -passin env:PASSWORD -passout env:PASSWORD -inkey "${KEYDIR}/${cn}-${TIMESTAMP}.key" -in "${CADIR}/certs/${cn}.crt" -out "${PKCS12DIR}/${cn}-${TIMESTAMP}.p12" else "${OPENSSL}" pkcs12 -export -nodes -passout pass: -inkey "${KEYDIR}/${cn}-${TIMESTAMP}.key" -in "${CADIR}/certs/${cn}.crt" -out "${PKCS12DIR}/${cn}-${TIMESTAMP}.p12" fi # generate openvpn format if [ -e "${PREFIX}/ovpn.conf" ]; then cat "${PREFIX}/ovpn.conf" > "${OVPNDIR}/${cn}-${TIMESTAMP}.ovpn" < $(cat "${CADIR}/cacert.pem") $(cat "${CADIR}/certs/${cn}.crt") $(cat "${KEYDIR}/${cn}-${TIMESTAMP}.key") EOF echo "The configuration file is available in ${OVPNDIR}/${cn}.ovpn" fi fi } revoke() { [ "${1}" = "" ] && usage >&2 && exit 1 # get CN from param cn="${1}" # check if CRT exists [ ! -f "${CADIR}/certs/${cn}.crt" ] && error "Unknow CN : ${cn}" # check if CRT is a valid "${OPENSSL}" x509 -noout -subject -in "${CADIR}/certs/${cn}.crt" >/dev/null 2>&1 || error "${CADIR}/certs/${cn}.crt is not a valid CRT, you msust delete it !" # ask for CA passphrase ask_ca_password 0 echo "Revoke certificate ${CADIR}/certs/${cn}.crt :" CA_PASSWORD="${CA_PASSWORD}" "$OPENSSL" ca \ -config "${CONFFILE}" \ -passin env:CA_PASSWORD \ -revoke "${CADIR}/certs/${cn}.crt" \ && rm "${CADIR}/certs/${cn}.crt" CA_PASSWORD="${CA_PASSWORD}" "$OPENSSL" ca \ -config "${CONFFILE}" \ -passin env:CA_PASSWORD \ -gencrl -out "${CADIR}/crl.pem" } list() { [ -f /etc/shellpki/ca/index.txt ] || exit 0 list_valid=0 list_revoked=1 while getopts "avr" opt; do case "$opt" in a) list_valid=0 list_revoked=0 shift;; v) list_valid=0 list_revoked=1 shift;; r) list_valid=1 list_revoked=0 shift;; esac done [ "${list_valid}" -eq 0 ] && certs=$(grep "^V" "${CADIR}/index.txt") [ "${list_revoked}" -eq 0 ] && certs=$(grep "^R" "${CADIR}/index.txt") [ "${list_valid}" -eq 0 ] && [ "${list_revoked}" -eq 0 ] && certs=$(cat "${CADIR}/index.txt") echo "${certs}" | grep -Eo "CN\s*=[^,/]*" | cut -d'=' -f2 | xargs -n1 } main() { [ "$(id -u)" -eq 0 ] || error "Please become root before running ${0} !" # main vars PREFIX="/etc/shellpki" PKIUSER="shellpki" CONFFILE="${PREFIX}/openssl.cnf" CADIR=$(grep -E "^dir" "${CONFFILE}" | cut -d'=' -f2|xargs -n1) OPENSSL=$(command -v openssl) TIMESTAMP=$(/bin/date +"%s") # directories for clients key, csr, crt KEYDIR="${PREFIX}/private" CSRDIR="${PREFIX}/requests" PEMDIR="${PREFIX}/pem" PKCS12DIR="${PREFIX}/pkcs12" OVPNDIR="${PREFIX}/openvpn" if ! getent passwd "${PKIUSER}" >/dev/null || ! getent group "${PKIUSER}" >/dev/null; then error "You must create ${PKIUSER} user and group !" fi [ -e "${CONFFILE}" ] || error "${CONFFILE} is missing" # create needed dir [ -d "${PREFIX}" ] || mkdir -p "${PREFIX}" [ -d "${KEYDIR}" ] || mkdir -m 0750 "${KEYDIR}" [ -d "${CSRDIR}" ] || mkdir -m 0755 "${CSRDIR}" [ -d "${PEMDIR}" ] || mkdir -m 0750 "${PEMDIR}" [ -d "${PKCS12DIR}" ] || mkdir -m 0750 "${PKCS12DIR}" [ -d "${OVPNDIR}" ] || mkdir -m 0750 "${OVPNDIR}" # fix right find "${PREFIX}" ! -path "${CADIR}" -exec chown "${PKIUSER}":"${PKIUSER}" {} \; -exec chmod u=rwX,g=rX,o= {} \; command=${1:-help} case "${command}" in init) shift init ;; create) shift create "$@" ;; revoke) shift revoke "$@" ;; list) shift list "$@" ;; *) usage >&2 exit 1 ;; esac } main "$@"