From b1bc99510d8abf778aada352326f56753d5b5282 Mon Sep 17 00:00:00 2001 From: Nicolas MASSE Date: Wed, 11 Sep 2019 11:46:54 +0200 Subject: [PATCH] migration from svn --- nanoca | 714 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 714 insertions(+) create mode 100755 nanoca diff --git a/nanoca b/nanoca new file mode 100755 index 0000000..db21c46 --- /dev/null +++ b/nanoca @@ -0,0 +1,714 @@ +#!/bin/bash + +if [ $# -lt 1 ]; then + echo "Usage: $0 pki_path" + exit 1 +fi + +MSG=">>> " + +function main_menu () { + # Menu constants + local MENU_INITIALIZE_CA="Initialize CA" + local MENU_ISSUE_CERT="Issue a certificate" + local MENU_ISSUE_CRL="Issue a CRL" + local MENU_REVOKE="Revoke a certificate" + local MENU_DUMP_CACERT="Dump the CA Certificate" + local MENU_SHOW_CACERT="Show the CA Certificate" + local MENU_DUMP_CRL="Dump the CRL" + local MENU_SHOW_CRL="Show the CRL" + local MENU_SHOW_CADB="Show the CA Database" + local MENU_EXPORT_CERT="Export a certificate" + local MENU_EXPORT_CACERT="Export the CA certificate" + local MENU_QUIT="Quit" + + # Variables + local cmd + + clear + echo "+-----------------------------------------------------------------------+" + echo "| Main Menu |" + echo "+-----------------------------------------------------------------------+" + echo + + select cmd in "$MENU_INITIALIZE_CA" "$MENU_ISSUE_CERT" "$MENU_ISSUE_CRL" \ + "$MENU_REVOKE" "$MENU_EXPORT_CERT" "$MENU_SHOW_CADB" "$MENU_DUMP_CACERT" \ + "$MENU_SHOW_CACERT" "$MENU_EXPORT_CACERT" "$MENU_DUMP_CRL" "$MENU_SHOW_CRL" "$MENU_QUIT"; do + + if [ -z "$cmd" ]; then + cmd="$REPLY" + fi + + case "$cmd" in + "$MENU_INITIALIZE_CA") + if is_ca_initialized; then + echo "${MSG}Error: CA is already initialized !" + continue + else + initialize_ca_menu + fi + ;; + "$MENU_DUMP_CACERT") + if ! is_ca_initialized; then + echo "${MSG}Error: CA is not yet initialized !" + continue + else + dump_file "$pki_path/cacert.pem" + fi + ;; + "$MENU_EXPORT_CACERT") + if ! is_ca_initialized; then + echo "${MSG}Error: CA is not yet initialized !" + continue + else + export_file "$pki_path/cacert.pem" + fi + ;; + "$MENU_DUMP_CRL") + if ! is_ca_initialized; then + echo "${MSG}Error: CA is not yet initialized !" + continue + else + dump_file "$pki_path/crl.pem" + fi + ;; + "$MENU_ISSUE_CERT") + if ! is_ca_initialized; then + echo "${MSG}Error: CA is not yet initialized !" + continue + else + issue_cert_menu + fi + ;; + "$MENU_EXPORT_CERT") + if ! is_ca_initialized; then + echo "${MSG}Error: CA is not yet initialized !" + continue + else + export_cert_menu + fi + ;; + "$MENU_REVOKE") + if ! is_ca_initialized; then + echo "${MSG}Error: CA is not yet initialized !" + continue + else + revoke_menu + fi + ;; + "$MENU_ISSUE_CRL") + if ! is_ca_initialized; then + echo "${MSG}Error: CA is not yet initialized !" + continue + else + issue_crl + fi + ;; + "$MENU_SHOW_CACERT") + if ! is_ca_initialized; then + echo "${MSG}Error: CA is not yet initialized !" + continue + else + show_cert "$pki_path/cacert.pem" + fi + ;; + "$MENU_SHOW_CRL") + if ! is_ca_initialized; then + echo "${MSG}Error: CA is not yet initialized !" + continue + else + show_crl "$pki_path/crl.pem" + fi + ;; + "$MENU_SHOW_CADB") + if ! is_ca_initialized; then + echo "${MSG}Error: CA is not yet initialized !" + continue + else + show_cadb + fi + ;; + "$MENU_QUIT") + false + ;; + *) + echo "${MSG}Error: $cmd is not a valid command !" + continue + ;; + esac + + return + done +} + +function revoke_menu () { + clear + echo "+-----------------------------------------------------------------------+" + echo "| Revoke a certificate |" + echo "+-----------------------------------------------------------------------+" + echo + + local torevoke="" + local serial="" + + show_cadb + while [ -z "$torevoke" ]; do + read -p "${MSG}Serial number (\"l\" to show the CA DB, \"q\" to quit): " serial + case "$serial" in + l) + show_cadb + continue + ;; + q) + return 0 + ;; + *) + if [ -e "$certs_path/$serial.pem" ]; then + torevoke="$serial" + else + echo "${MSG}Unknown certificate with serial '$serial' !" + echo + fi + ;; + esac + + done + + trap "rm -f \"\$OPENSSL_CONF\"" RETURN + cat < "$OPENSSL_CONF" +$OPENSSL_CA_SECTION +EOF + my_openssl ca -revoke "$pki_path/certs/$serial.pem" + local ret="$?" + if [ "$ret" -eq 0 ]; then + pause + echo + echo "${MSG}Issuing CRL..." + issue_crl + ret="$?" + fi + + pause + return "$ret" +} + +function export_cert_menu () { + clear + echo "+-----------------------------------------------------------------------+" + echo "| Export a certificate |" + echo "+-----------------------------------------------------------------------+" + echo + + local toexport="" + local serial="" + + show_cadb + while [ -z "$toexport" ]; do + read -p "${MSG}Serial number (\"l\" to show the CA DB, \"q\" to quit): " serial + case "$serial" in + l) + show_cadb + continue + ;; + q) + return 0 + ;; + *) + if [ -e "$certs_path/$serial.pem" ]; then + toexport="$serial" + else + echo "${MSG}Unknown certificate with serial '$serial' !" + echo + fi + ;; + esac + done + + export_file "$certs_path/$serial.pem" +} + +function export_file () { + local ret="0" + echo + echo "${MSG}Where do you want to save the file (leave blank to cancel) ?" + read -e -p "${MSG}Path: " path + if [ -n "$path" ]; then + cp "$1" "$path" + ret="$?" + pause + fi + + return "$ret" +} + +function initialize_ca_menu () { + # Menu constants + local MENU_QUIT="Back" + local MENU_DOIT="Commit" + local MENU_SET_DN="Set the certificate DN" + local MENU_SET_VALIDITY="Set the validity period" + local MENU_SET_SIZE="Set the key size" + + # Variables + local cmd + local ca_dn="/C=FR/O=$OWNER_NAME/OU=$OWNER_DOMAIN/CN=Root CA of $OWNER_DOMAIN" + local validity="3650" + local key_size="2048" + + while true; do + clear + echo "+-----------------------------------------------------------------------+" + echo "| Initialize the CA |" + echo "+-----------------------------------------------------------------------+" + echo + echo "DN: $ca_dn" + echo "Validity (days): $validity" + echo "Key size: $key_size" + echo + + select cmd in "$MENU_SET_DN" "$MENU_SET_VALIDITY" "$MENU_SET_SIZE" "$MENU_DOIT" \ + "$MENU_QUIT"; do + + if [ -z "$cmd" ]; then + cmd="$REPLY" + fi + + case "$cmd" in + "$MENU_SET_DN") + read -p "${MSG}DN: " ca_dn + ;; + "$MENU_SET_VALIDITY") + read -p "${MSG}Validity (days): " validity + ;; + "$MENU_SET_SIZE") + read -p "${MSG}Key Size: " key_size + ;; + "$MENU_QUIT") + return + ;; + "$MENU_DOIT") + initialize_ca + return $? + ;; + *) + echo "${MSG}Error: $cmd is not a valid command !" + continue + ;; + esac + + break + done + done +} + +function initialize_ca () { + clear + trap "rm -f \"\$OPENSSL_CONF\"" RETURN + + local protect_opt="" + if [ -z "$CA_PROTECT" ]; then + protect_opt="-nodes" + fi + + cat < "$OPENSSL_CONF" +[ req ] +default_bits = $key_size +default_keyfile = $private_path/cakey.pem +default_md = $HASH_ALGO + +prompt = no +x509_extensions = rootca_extensions +distinguished_name = rootca_dn + +[ rootca_dn ] +$(openssl_dn "$ca_dn") + +[ rootca_extensions ] +basicConstraints = critical, CA:true, pathlen:0 +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid +keyUsage = critical, cRLSign, keyCertSign +EOF + + # Generate the keys and the certificate + my_openssl req -set_serial 0 -days "$validity" -x509 -newkey rsa: -outform PEM \ + $protect_opt -out "$pki_path/cacert.pem" + + local ret="$?" + if [ "$ret" -eq 0 ]; then + echo '01' > "$pki_path/serial" + echo '01' > "$pki_path/crlserial" + touch "$pki_path/index.txt" + touch "$pki_path/index.txt.attr" + issue_crl + fi + + pause + return "$ret" +} + +function openssl_dn () { + echo "$1" |sed 's/\//\n/g' +} + +function my_openssl () { + # Strip out the informative messages on stderr + openssl "$@" 2> >(egrep -v '^Using configuration from' 1>&2) + local ret=$? + # Ugly(tm): Wait for the "egrep" child process + sleep .3 + return $ret; +} + +function is_ca_initialized () { + test -e "$pki_path/cacert.pem" +} + +function pause () { + echo + read -p "Press enter to continue... " +} + +function dump_file () { + clear + cat "$1" + local ret="$?" + pause + return "$ret" +} + +function issue_cert_menu () { + # Menu constants + local MENU_QUIT="Back" + local MENU_DOIT="Commit" + local MENU_SET_CN="Set the certificate CN" + local MENU_SET_DNS="Set the certificate SAN (Subject Alt Name)" + local MENU_SET_VALIDITY="Set the validity period" + + # Variables + local cmd + local cn + local dn="/C=FR/O=$OWNER_NAME/OU=$OWNER_DOMAIN/CN=My Server" + local validity="730" + local san="" + + while true; do + clear + echo "+-----------------------------------------------------------------------+" + echo "| Issue a Certificate (1/2) |" + echo "+-----------------------------------------------------------------------+" + echo + echo "DN: $dn" + echo "Validity (days): $validity" + echo "Subject Alt Name: $san" + echo + + select cmd in "$MENU_SET_CN" "$MENU_SET_DNS" "$MENU_SET_VALIDITY" "$MENU_DOIT" \ + "$MENU_QUIT"; do + + if [ -z "$cmd" ]; then + cmd="$REPLY" + fi + + case "$cmd" in + "$MENU_SET_CN") + echo + echo "${MSG}The \"Common Name\" of your server can be its FQDN or anything else." + echo "${MSG}Examples: \"My Web Server\", \"myserver.$OWNER_DOMAIN\" or \"*.$OWNER_DOMAIN\"". + echo + read -p "${MSG}The \"Common Name\" of your server: " cn + dn="/C=FR/O=$OWNER_NAME/OU=$OWNER_DOMAIN/CN=$cn" + ;; + "$MENU_SET_VALIDITY") + read -p "${MSG}Validity (days): " validity + ;; + "$MENU_SET_DNS") + echo + echo "${MSG}The Subject Alt Name can contain DNS, IP, email or URI addresses." + echo "${MSG}Prefix each SAN by its type and separate each SAN by a \",\"." + echo "${MSG}Example: \"DNS:myserver.$OWNER_DOMAIN,IP:192.168.16.4\"" + echo + read -p "${MSG}Subject Alt Name (leave empty to disable the SAN): " san + ;; + "$MENU_QUIT") + return + ;; + "$MENU_DOIT") + issue_cert + return $? + ;; + *) + echo "${MSG}Error: $cmd is not a valid command !" + continue + ;; + esac + + break + done + done +} + +function issue_cert () { + clear + echo "+-----------------------------------------------------------------------+" + echo "| Issue a Certificate (2/2) |" + echo "+-----------------------------------------------------------------------+" + echo + + trap "rm -f \"\$OPENSSL_CONF\" \"$tmp_path/lastcert.pem\"" RETURN + + local san_ext + if [ -n "$san" ]; then + san_ext="subjectAltName = $san" + fi + cat < "$OPENSSL_CONF" +$OPENSSL_CA_SECTION + +default_days = $validity +prompt = no +distinguished_name = certificate_dn +x509_extensions = certificate_extensions +policy = policy_any + +[ policy_any ] +countryName = supplied +organizationName = supplied +organizationalUnitName = supplied +commonName = supplied + +[ certificate_extensions ] +basicConstraints = critical, CA:false +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer +keyUsage = critical,digitalSignature,keyEncipherment +$san_ext +crlDistributionPoints = URI:$CDP_URL +extendedKeyUsage = serverAuth +EOF + + local csr_path="" + read -e -p "${MSG}CSR Path: " csr_path + while [ ! -r "$csr_path" ]; do + echo "${MSG}\"$csr_path\" does not exist !" + read -e -p "${MSG}CSR Path: " csr_path + done + + rm -f "$tmp_path/lastcert.pem" + + # Generate the certificate + my_openssl ca -subj "$dn" -utf8 -in "$csr_path" -notext -out "$tmp_path/lastcert.pem" + local ret="$?" + + pause + + if [ "$ret" -eq 0 ]; then + export_file "$tmp_path/lastcert.pem" + ret="$?" + fi + + return "$ret" +} + +function issue_crl () { + trap "rm -f \"\$OPENSSL_CONF\"" RETURN + cat < "$OPENSSL_CONF" +$OPENSSL_CA_SECTION +EOF + my_openssl ca -gencrl -utf8 -notext -out "$pki_path/crl.pem" \ + && my_openssl crl -inform PEM -in "$pki_path/crl.pem" -outform DER -out "$CRL_PATH" +} + +function show_cert () { + clear + + my_openssl x509 -noout -text -in "$1" | less +} + +function show_crl () { + clear + + my_openssl crl -noout -text -in "$1" | less +} + +function show_cadb () { + clear + + sed -r 's/^V\s+[0-9]+Z\s+([0-9a-zA-Z]+)\s+unknown\s+(.*)$/\1) \2/; t; d' "$pki_path/index.txt" |less +} + +function init_conf () { + # Default values + HASH_ALGO="sha1" + + # Initialization Wizard + clear + echo "+-----------------------------------------------------------------------+" + echo "| Initialization Wizard |" + echo "+-----------------------------------------------------------------------+" + echo + echo "What is your name (ex: Nicolas MASSE) ?" + while [ -z "$OWNER_NAME" ]; do read -p "${MSG}Your name is: " OWNER_NAME; done + echo + echo "Which domain name do you initialize (ex: itix.fr) ?" + while [ -z "$OWNER_DOMAIN" ]; do + read -p "${MSG}Your DNS domain name is: " OWNER_DOMAIN + done + echo + echo "Where do you want to publish the CRL (ex: http://pki.$OWNER_DOMAIN/ca.crl) ?" + while [ -z "$CDP_URL" ]; do + read -p "${MSG}The CRL will be published at: " CDP_URL + done + while [ -z "$CRL_PATH" ]; do + read -e -p "${MSG}Local CRL path (ex: /var/www/ca.crl): " CRL_PATH + done + echo + while [ -z "$CRL_VALIDITY" ]; do + read -p "${MSG}CRL validity period, in days (\"30\" is a good value): " CRL_VALIDITY + done + echo + echo "${MSG}Protect the CA key with a password (yes/no) ?" + local cmd="" + select cmd in "Yes" "No"; do + + if [ -z "$cmd" ]; then + cmd="$REPLY" + fi + + case "$cmd" in + [yY]*) + CA_PROTECT="y" + break; + ;; + [nN]*) + CA_PROTECT="" + break; + ;; + *) + echo "Unknown response '$cmd' !" + continue + ;; + esac + done + + echo + echo "Thanks !" + + # Save the conf + save_conf + local ret="$?" + + pause + return "$ret" +} + +function save_conf () { + cat < "$conf_path" +OWNER_NAME="$OWNER_NAME" +OWNER_DOMAIN="$OWNER_DOMAIN" +HASH_ALGO="$HASH_ALGO" +CDP_URL="$CDP_URL" +CRL_PATH="$CRL_PATH" +CRL_VALIDITY="$CRL_VALIDITY" +CA_PROTECT="$CA_PROTECT" +EOC +} + +# Safe mask +umask 077 + +# The CA directory +pki_path="$1" +if [ ! -e "$pki_path" ]; then + echo "${MSG}$pki_path does not exist. Do you want to create it (yes/no) ?" + cmd="" + select cmd in "Yes" "No"; do + + if [ -z "$cmd" ]; then + cmd="$REPLY" + fi + + case "$cmd" in + [yY]*) + # Nothing to do + break; + ;; + [nN]*) + exit 0 + ;; + *) + echo "Unknown response '$cmd' !" + continue + ;; + esac + done + mkdir "$pki_path" || exit $? +fi + +tmp_path="$pki_path/tmp" +if [ ! -e "$tmp_path" ]; then + mkdir "$tmp_path" || exit $? +fi + +certs_path="$pki_path/certs" +if [ ! -e "$certs_path" ]; then + mkdir "$certs_path" || exit $? +fi + +private_path="$pki_path/private" +if [ ! -e "$private_path" ]; then + mkdir "$private_path" || exit $? +fi + +conf_path="$pki_path/nanoca.conf" +if [ -e "$conf_path" ]; then + . "$conf_path" +else + init_conf || exit $? +fi + + +# OpenSSL Conf. +export OPENSSL_CONF="$tmp_path/openssl.cnf" +OPENSSL_CA_SECTION=" +[ ca ] +default_ca = CA_default + +[ CA_default ] +default_md = $HASH_ALGO +dir = $pki_path +database = $pki_path/index.txt +new_certs_dir = $pki_path/certs +certificate = $pki_path/cacert.pem +serial = $pki_path/serial +crlnumber = $pki_path/crlserial +private_key = $private_path/cakey.pem +RANDFILE = $private_path/.rand +email_in_dn = no +copy_extensions = none +unique_subject = no +name_opt = ca_default +cert_opt = ca_default +default_crl_days = $CRL_VALIDITY +" + +# Automatic processing +if [ -n "$2" ]; then + case "$2" in + crl) + issue_crl + ;; + *) + echo "Unknown action \"$2\" !" + echo + echo "Valid actions are :" + echo " - crl: Issues a CRL" + echo + false + ;; + esac + exit $? +fi + +# Main loop +while main_menu; do :; done +