diff --git a/README.md b/README.md index 4c5b617..af243e9 100644 --- a/README.md +++ b/README.md @@ -13,28 +13,29 @@ install -m 0755 shellpki.sh /usr/local/sbin/shellpki ## Usage -Initialize PKI creating CA key and certificate : - -~~~ -shellpki init ~~~ +Usage: ./shellpki.sh [options] [CommonName] -Create a certificate and key on the server : +Initialize PKI (create CA key and self-signed cert) : -~~~ -shellpki create -~~~ + ./shellpki.sh init -Create a certificate without key from a CSR : +Create a client cert with key and CSR directly generated on server +(use -p for set a password on client key) : -~~~ -shellpki fromcsr -~~~ + ./shellpki.sh create [-p] -Revoke a certificate : +Create a client cert from a CSR (doesn't need key) : -~~~ -shellpki revoke + ./shellpki.sh create -f + +Revoke a client cert with is commonName (CN) : + + ./shellpki.sh revoke + +List all actually valid commonName (CN) : + + ./shellpki.sh list ~~~ ## License diff --git a/shellpki.sh b/shellpki.sh index 2fe1e14..91d9327 100755 --- a/shellpki.sh +++ b/shellpki.sh @@ -3,6 +3,8 @@ # shellpki is a wrapper around openssl to manage a small PKI # +set -eu + init() { umask 0177 @@ -27,64 +29,200 @@ init() { -out "${CADIR}/cacert.pem" } -check_cn() { - cn="${1}" - if [ -f "${CADIR}/certs/${cn}.crt" ]; then - echo "Please revoke actual ${cn} cert before creating one" - echo - echo "Press return to continue..." - read -r REPLY - exit 1 - fi +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 + +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() { - umask 0137 - echo "Please enter your CN (Common Name)" - read -r cn - echo - echo "Your CN is '${cn}'" - echo "Press return to continue..." - read -r REPLY - echo + from_csr=1 + with_pass=1 - # check if CN already exist - check_cn "${cn}" + 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 - # generate private key - echo "Should private key be protected by a passphrase? [y/N] " - read -r REPLY - if [ "$REPLY" = "y" ] || [ "$REPLY" = "Y" ]; then - "$OPENSSL" genrsa -aes256 -out "${KEYDIR}/${cn}-${TIMESTAMP}.key" 2048 + 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 - "$OPENSSL" genrsa -out "${KEYDIR}/${cn}-${TIMESTAMP}.key" 2048 - fi + [ -z "${cn}" ] && usage >&2 && exit 1 - # generate csr req - "$OPENSSL" req -batch \ - -new \ - -key "${KEYDIR}/${cn}-${TIMESTAMP}.key" \ - -out "${CSRDIR}/${cn}-${TIMESTAMP}.csr" \ - -config /dev/stdin </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 <> "${PEMDIR}/${cn}-${TIMESTAMP}.pem" + # check if CRT is a valid + "${OPENSSL}" x509 \ + -noout -subject \ + -in "${CADIR}/certs/${cn}.crt" \ + >/dev/null 2>&1 \ + || rm -f "${CADIR}/certs/${cn}.crt" - # generate pkcs12 format - openssl pkcs12 -export -nodes -passout pass: -inkey "${KEYDIR}/${cn}-${TIMESTAMP}.key" -in "${CADIR}/certs/${cn}.crt" -out "${PKCS12DIR}/${cn}-${TIMESTAMP}.p12" + [ -f "${CADIR}/certs/${cn}.crt" ] || error "Error in CSR creation" - # generate openvpn format - if [ -e "${PREFIX}/ovpn.conf" ]; then - cat "${PREFIX}/ovpn.conf" > "${OVPNDIR}/${cn}-${TIMESTAMP}.ovpn" <> "${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") @@ -97,67 +235,41 @@ $(cat "${CADIR}/certs/${cn}.crt") $(cat "${KEYDIR}/${cn}-${TIMESTAMP}.key") EOF - echo "The configuration file is available in ${OVPNDIR}/${cn}.ovpn" + echo "The configuration file is available in ${OVPNDIR}/${cn}.ovpn" + fi fi } -fromcsr() { - echo "Please enter path for your CSR request file" - read -r path - echo - - if [ ! -e "${path}" ]; then - echo "Error in path..." >&2 - echo >&2 - echo "Press return to continue..." >&2 - read -r REPLY - exit 1 - fi - - path=$(readlink -f "${path}") - - # get CN from CSR - cn=$(openssl req -noout -subject -in "${path}"|grep -Eo "CN=[^/]*"|cut -d'=' -f2) - - # check if CN already exist - check_cn "${cn}" - - # copy CSR to CSRDIR - cp "$path" "${CSRDIR}/${cn}-${TIMESTAMP}.csr" - - # ca sign and generate cert - "${OPENSSL}" ca \ - -config "${CONFFILE}" \ - -in "${CSRDIR}/${cn}-${TIMESTAMP}.csr" \ - -out "${CADIR}/certs/${cn}.crt" -} - revoke() { - echo "Please enter CN (Common Name) to revoke" - read -r cn - echo - echo "CN '${cn}' will be revoked" - echo "Press return to continue..." - read -r REPLY - echo + [ "${1}" = "" ] && usage >&2 && exit 1 - [ ! -f "${CADIR}/certs/${cn}.crt" ] && echo "Unknow CN : ${cn}" >&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 :" - "$OPENSSL" ca \ - -config "${CONFFILE}" \ - -revoke "${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" - echo "Update CRL :" - "$OPENSSL" ca \ - -config "${CONFFILE}" \ + CA_PASSWORD="${CA_PASSWORD}" "$OPENSSL" ca \ + -config "${CONFFILE}" \ + -passin env:CA_PASSWORD \ -gencrl -out "${CADIR}/crl.pem" } list() { - echo "* List of allowed CN :" - ls -1 "${CADIR}/certs" + [ -f /etc/shellpki/ca/index.txt ] && grep -Eo "CN\s*=[^,/]*" "${CADIR}/index.txt" | cut -d'=' -f2 | xargs -n1 } main() { @@ -207,29 +319,31 @@ main() { # fix right find "${PREFIX}" ! -path "${CADIR}" -exec chown "${PKIUSER}":"${PKIUSER}" {} \; -exec chmod u=rwX,g=rX,o= {} \; - case "$1" in + command=${1:-help} + + case "${command}" in init) + shift init ;; create) - create - ;; - - fromcsr) - fromcsr + shift + create "$@" ;; revoke) - revoke + shift + revoke "$@" ;; list) - list + shift + list "$@" ;; *) - echo "Usage: ${0} {init|create|fromcsr|revoke|list}" >&2 + usage >&2 exit 1 ;; esac