#!/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