Scripts/manageCA

De TartareFR
Aller à la navigation Aller à la recherche

Version potentiellement plus à jour sur https://github.com/didier13150/manageCA

#!/bin/bash
################################################################################
# Author: Didier Fabert
# Rev 0.4
################################################################################
COUNTRYNAME="FR"
STATE="Languedoc-Roussillon"
CITY="Beaucaire"
COMPANY="Home"
OCSP_URL="http://didier.domicile.org/"

PKI_PATH="/etc/pki"
NAME=""
CFG_FILE="/etc/manageCA.conf"

function printUsage() {
	echo "$(basename $0)"
}

function printHelp() {
	echo
	echo "Options:"
	echo -e "\t-c <NAME>     Config File [${CFG_FILE}]"
	echo -e "\t-p <PATH>     Path for PKI [/etc/pki]"
	echo -e "\t-n <NAME>     CA Name [None]"
}

function printMenu() {
	clear
	echo "====================================================================="
	echo "             ${COMPANY} Certificate Management System"
	echo "====================================================================="
	echo
	echo "   1) Create a Client/Server/OCSP certificate"
	echo "   2) Create a Client Certificate for Web (PKCS#12)"
	echo "   3) Renew a Certificate"
	echo "   4) Revoke a Certificate"
	echo "   5) List Certificates"
	echo
	echo "   i) Initialize Root Certificate Authority (CA)"
	echo "   d) Delete CA"
	echo "   o) Show/Modify/Save CA Options"
	echo "   q) Quit"
	echo
	echo "   Options available before init"
	echo "   p) Change PKI default path [${PKI_PATH}]"
	echo "   n) Change CA name [${NAME}]"
	echo
}

function printSubMenu {
        clear
        echo "-----------------------------------------------------------------"
        echo ${1}
        echo "-----------------------------------------------------------------"
        echo
}

function manageOptions() {
	local buffer
	while true;
	do
		printSubMenu "CA Options"
		echo "   1) Country Name [${COUNTRYNAME}]"
		echo "   2) State Name [${STATE}]"
		echo "   3) City Name [${CITY}]"
		echo "   4) Company Name [${COMPANY}]"
		echo "   5) OCSP URL [${OCSP_URL}]"
		echo
		echo "   s) Save Options"
		echo "   p) Previous menu"
		echo
		read -p " ==> Make your choice [none]: " -n 1 CHOICE
		echo
		echo
		case ${CHOICE} in
			1)
				read -p " ==> New Country Name [${COUNTRYNAME}]: " buffer
				[ ! -z ${buffer} ] && COUNTRYNAME=${buffer}
				;;
			2)
				read -p " ==> New State Name [${STATE}]: " buffer
				[ ! -z ${buffer} ] && STATE=${buffer}
				;;
			3)
				read -p " ==> New City Name [${CITY}]: " buffer
				[ ! -z ${buffer} ] && CITY=${buffer}
				;;
			4)
				read -p " ==> New Company Name [${COMPANY}]: " buffer
				[ ! -z ${buffer} ] && COMPANY=${buffer}
				;;
			5)
				read -p " ==> New OCSP URL [${OCSP_URL}]: " buffer
				[ ! -z ${buffer} ] && OCSP_URL=${buffer}
				;;
			s)
				saveCfg
				;;
			p)
				return
				;;
		esac
	done
}

function saveCfg() {
	local buffer=$1
	if [ -z ${buffer} ]
	then
		echo
		read -p " ==> File to save [${CFG_FILE}]: " buffer
		[ ! -z ${buffer} ] && CFG_FILE=${buffer}
	fi
	touch ${CFG_FILE}
	
	if [ -w ${CFG_FILE} ]
	then
		echo "## Configuration file for manageCA.sh script" > ${CFG_FILE}
		echo >> ${CFG_FILE}
		echo "# Country Code for certificate" >> ${CFG_FILE}
		echo "COUNTRYNAME=\"${COUNTRYNAME}\"" >> ${CFG_FILE}
		echo >> ${CFG_FILE}
		echo "# State Name for certificate" >> ${CFG_FILE}
		echo "STATE=\"${STATE}\"" >> ${CFG_FILE}
		echo >> ${CFG_FILE}
		echo "# City Name for certificate" >> ${CFG_FILE}
		echo "CITY=\"${CITY}\"" >> ${CFG_FILE}
		echo >> ${CFG_FILE}
		echo "# Company Name for certificate" >> ${CFG_FILE}
		echo "COMPANY=\"${COMPANY}\"" >> ${CFG_FILE}
		echo >> ${CFG_FILE}
		echo "# OCSP URL for certificate" >> ${CFG_FILE}
		echo "OCSP_URL=\"${OCSP_URL}\"" >> ${CFG_FILE}
		echo >> ${CFG_FILE}
		echo "# PKI Default Path" >> ${CFG_FILE}
		echo "PKI_PATH=\"${PKI_PATH}\"" >> ${CFG_FILE}
		echo >> ${CFG_FILE}
	else
		echo
		echo "Error: ${CFG_FILE} is not writable for you"
		read -p "Press [enter] to continue" DUMMY
	fi
}

function addUser() {
	local user
	local email
	local usage="client"
	local buffer
	local userdata
	printSubMenu "Create a client certificate"
	read -p " ==> User name [NONE]: " user
	if [[ "${user}" == "" ]]
	then
		return
	fi
	echo
	read -p " ==> User email [NONE]: " email
	if [[ "${email}" == "" ]]
	then
		return
	fi
	echo
	
	read -p " ==> Select Usage Key (server, client or ocsp) [client]: " buffer
	[ -z ${buffer} ] || usage=${buffer}
	echo
	
	if [[ "${usage}" == "ocsp" ]]
	then
			extension="-extensions OCSP"
	else
		read -p "Add OCSP Extension to Certificate ? [Y/n]: " buffer
		[ -z ${buffer} ] && buffer="y"
		if [[ "${buffer}" == "y" ]]
		then
			if [[ "${usage}" == "server" ]]
			then
				extension="-extensions OCSP_SERVER"
			else
				extension="-extensions OCSP_CLIENT"
			fi
		else
			extension=""
		fi
	fi
	
	openssl genrsa -out ${PKI_PATH}/${NAME}/certs/${user}.key 2048 \
		1>/dev/null 2>&1
	if [[ "${usage}" == "server" ]]
	then
		userdata="organizationalUnitName_default  = User\n"
	else
		userdata="organizationalUnitName_default  = Admin\n"
	fi
	userdata="${userdata}commonName_default              = ${user}\n"
	userdata="${userdata}emailAddress_default            = ${email}"
	cat ${PKI_PATH}/${NAME}/ssl.cnf | tr -d '#' | \
		sed -e "s/@USERDATA@/${userdata}/" \
		> ${PKI_PATH}/${NAME}/ssl2.cnf
	openssl req -config ${PKI_PATH}/${NAME}/ssl2.cnf -new -nodes -batch \
		-out ${PKI_PATH}/${NAME}/certs/${user}.csr \
		-key ${PKI_PATH}/${NAME}/certs/${user}.key
	openssl ca -config ${PKI_PATH}/${NAME}/ssl2.cnf \
		-cert ${PKI_PATH}/${NAME}/${NAME}_ca.crt ${extension} \
		-out ${PKI_PATH}/${NAME}/certs/${user}.crt \
		-outdir ${PKI_PATH}/${NAME}/certs \
		-infiles ${PKI_PATH}/${NAME}/certs/${user}.csr
	cat ${PKI_PATH}/${NAME}/certs/${user}.crt \
		${PKI_PATH}/${NAME}/certs/${user}.key \
			> ${PKI_PATH}/${NAME}/pem/${user}.pem
	mv ${PKI_PATH}/${NAME}/ssl2.cnf ${PKI_PATH}/${NAME}/confs/${user}-ssl.cnf
	read -p "Press [enter] to continue" DUMMY
}

function webUser() {
	local user
	printSubMenu "Create a Client Certificate for Web"
	printUserList
	read -p " ==> User name [NONE]: " user
	if [[ "${user}" == "" ]]
	then
		return
	fi
	echo
	if [ -f ${PKI_PATH}/${NAME}/certs/${user}.crt ]
	then
		openssl pkcs12 -export -inkey ${PKI_PATH}/${NAME}/certs/${user}.key \
			-in ${PKI_PATH}/${NAME}/certs/${user}.crt \
			-CAfile ${PKI_PATH}/${NAME}/${NAME}_ca.crt \
			-out ${PKI_PATH}/${NAME}/certs/${user}_browser_cert.p12
	fi
	echo
	[ -f ${PKI_PATH}/${NAME}/certs/${user}_browser_cert.p12 ] \
		&& echo "Web certificate: ${PKI_PATH}/${NAME}/certs/${user}_browser_cert.p12" \
		|| echo "Error encoured"
	echo
	read -p "Press [enter] to continue" DUMMY
}

function renewUser() {
	local user
	printSubMenu "Renew a Server Certificate"
	printUserList
	read -p " ==> User name [NONE]: " user
	if [[ "${user}" == "" ]]
	then
		echo "Error: Name cannot be empty"
		read -p "Press [enter] to continue" DUMMY
		return
	fi
	revokeUser ${user}
	openssl ca -config ${PKI_PATH}/${NAME}/confs/${user}-ssl.cnf \
        -out ${PKI_PATH}/${NAME}/certs/${user}.crt \
        -outdir ${PKI_PATH}/${NAME}/certs \
        -infiles ${PKI_PATH}/${NAME}/certs/${user}.csr
	cat ${PKI_PATH}/${NAME}/certs/${user}.crt \
		${PKI_PATH}/${NAME}/certs/${user}.key \
			> ${PKI_PATH}/${NAME}/pem/${user}.pem
	echo
	read -p "Press [enter] to continue" DUMMY
}

function revokeUser() {
	local user=$1
	printSubMenu "Revoke a Client Certificate"
	printUserList
	[ -z ${user} ] && read -p " ==> User name [NONE]: " user
	if [[ "${user}" == "" ]]
	then
		return
	fi
	openssl ca -revoke ${PKI_PATH}/${NAME}/certs/${user}.crt \
		-config ${PKI_PATH}/${NAME}/ssl.cnf
	# Save old certificate
	x=1
	while [ -f "${PKI_PATH}/${NAME}/certs/${user}.revoked.$x.crt" ]
	do
		x=$(( $x + 1 ))
	done
		mv ${PKI_PATH}/${NAME}/certs/${user}.crt ${PKI_PATH}/${NAME}/certs/${user}.revoked.$x.crt
	
	x=1
	while [ -f "${PKI_PATH}/${NAME}/pem/${user}.revoked.$x.pem" ]
	do
		x=$(( $x + 1 ))
	done
	mv ${PKI_PATH}/${NAME}/pem/${user}.pem ${PKI_PATH}/${NAME}/pem/${user}.revoked.$x.pem
	# Regen CRL
	openssl ca -gencrl -config ${PKI_PATH}/${NAME}/ssl.cnf \
		-out ${PKI_PATH}/${NAME}/${NAME}_ca.crl
	echo
	echo "Don't forget to reload Apache"
	echo
	[ -z ${1} ] read -p "Press [enter] to continue" DUMMY
}

function listUser() {
	printSubMenu "List Client Certificates"
	while [ 1 ]
	do
	   read LINE || break
	   LISTNUM=`echo ${LINE} | grep -v "^R" | awk '{ print $3 }'`
	   LISTCN=`echo ${LINE} | grep -v "^R" | awk -F CN= '{ print $2 }' | cut -d '/' -f1`
	   [ -z ${LISTNUM} ] || echo " ${LISTNUM} ${LISTCN}"
	done < ${PKI_PATH}/${NAME}/index.txt
	echo
	read -p "Press [enter] to continue" DUMMY
}

function printUserList() {
	while [ 1 ]
	do
	   read LINE || break
	   LISTCN=`echo ${LINE} | grep -v "^R" | awk -F CN= '{ print $2 }' | cut -d '/' -f1`
	   [ -z ${LISTCN} ] || echo "- ${LISTCN}"
	done < ${PKI_PATH}/${NAME}/index.txt
}

function changeDefaultPath() {
	local buffer
	read -p " ==> Select New path for CA [${PKI_PATH}]: " buffer
	if [ ! -z ${buffer} ]; then PKI_PATH=${buffer} ; fi
}

function changeName() {
	read -p " ==> Select New CA name [NONE]: " NAME
}

function initCA() {
	printSubMenu "CA Initialisation"
	if [ -f ${PKI_PATH}/${NAME}/ssl.cnf ]
	then
		read -p "!!! Already initalized !!!"
		return
	fi
	read -p " ==> Fully qualified Hostname [NONE]: " hostname
	if [[ "$hostname" == "" ]]
	then
		echo "Error: Fully qualified Hostname cannot be empty"
		read -p "Press [enter] to continue" DUMMY
		return
	fi
	echo
	read -p " ==> Admin email [NONE]: " email
	if [[ "$email" == "" ]]
	then
		echo "Error: email cannot be empty"
		read -p "Press [enter] to continue" DUMMY
		return
	fi
	
	mkdir -p ${PKI_PATH}/${NAME}/{certs,newcerts,private,confs,crl,pem}
	initConfig
	touch ${PKI_PATH}/${NAME}/index.txt
	[ -f ${PKI_PATH}/${NAME}/serial ] || echo 01 > ${PKI_PATH}/${NAME}/serial
	[ -f ${PKI_PATH}/${NAME}/crlnumber ] || echo 01 > ${PKI_PATH}/${NAME}/crlnumber
	[ -f ${PKI_PATH}/${NAME}/private/${NAME}_ca.key ] || \
		openssl genrsa -out ${PKI_PATH}/${NAME}/private/${NAME}_ca.key 2048 \
		1>/dev/null 2>&1
	local userdata="organizationalUnitName_default  = Admin\n"
	userdata="${userdata}commonName_default              = ${hostname}\n"
	userdata="${userdata}emailAddress_default            = ${email}"
	cat ${PKI_PATH}/${NAME}/ssl.cnf | tr -d '#' | \
		sed -e "s/@USERDATA@/${userdata}/" \
		> ${PKI_PATH}/${NAME}/ssl2.cnf
	openssl req -config ${PKI_PATH}/${NAME}/ssl2.cnf -new -x509 -days 3650 -batch \
		-key ${PKI_PATH}/${NAME}/private/${NAME}_ca.key \
		-out ${PKI_PATH}/${NAME}/${NAME}_ca.crt -extensions v3_ca
	mv ${PKI_PATH}/${NAME}/ssl2.cnf ${PKI_PATH}/${NAME}/confs/ca.cnf
	[ -f ${PKI_PATH}/${NAME}/${NAME}_ca.crl ] || openssl ca -gencrl \
		-config ${PKI_PATH}/${NAME}/ssl.cnf -out ${PKI_PATH}/${NAME}/${NAME}_ca.crl
	ln ${PKI_PATH}/${NAME}/${NAME}_ca.crl ${PKI_PATH}/${NAME}/crl/
	local hash=`openssl crl -hash -noout -in ${PKI_PATH}/${NAME}/crl/${NAME}_ca.crl`
	ln -s ${PKI_PATH}/${NAME}/crl/${NAME}_ca.crl ${PKI_PATH}/${NAME}/crl/$hash.r0
	echo
	echo "CA initialized"
	echo
	read -p "Press [enter] to continue" DUMMY
}

function deleteCA() {
	printSubMenu "Deleting CA"
	read -p " ==> Are you sure ? Type uppercase YES to confirm: " CONFIRM
	if [[ "${CONFIRM}" == "YES" ]]
	then
		rm -rf ${PKI_PATH}/${NAME}
		echo
		echo "CA completely deleted"
		echo
		read -p "Press [enter] to continue" DUMMY
	fi
}

function initConfig() {
	cat << 'EOF' > ${PKI_PATH}/${NAME}/ssl.cnf
HOME                    = @HOME@
RANDFILE                = @HOME@/.rand

[ca] 
default_ca              = ca_default

[ca_default] 
dir                     = @HOME@
certs                   = $dir/certs
crl_dir                 = $dir/crl
database                = $dir/index.txt
new_certs_dir           = $dir/newcerts
certificate             = $dir/@NAME@_ca.crt
private_key             = $dir/private/@NAME@_ca.key
serial                  = $dir/serial
crl                     = $dir/@NAME@_ca.crl
crlnumber               = $dir/crlnumber
crl_extensions          = crl_ext
x509_extensions         = usr_cert
name_opt                = ca_default
cert_opt                = ca_default
default_days            = 365
default_crl_days        = 30
default_md              = md5
preserve                = no
policy                  = policy_match

[policy_match] 
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[req] 
default_bits            = 1024
default_keyfile         = privkey.pem
distinguished_name      = req_distinguished_name
attributes              = req_attributes
x509_extensions         = v3_ca
string_mask             = MASK:0x2002

[req_distinguished_name] 
countryName                     = Country Name (2 letter code)
countryName_default             = @COUNTRYNAME@
countryName_min                 = 2
countryName_max                 = 2
stateOrProvinceName             = State or Province Name (full name)
stateOrProvinceName_default     = @STATE@
localityName                    = Locality Name (eg, city)
localityName_default            = @CITY@
0.organizationName              = Organization Name (eg, company)
0.organizationName_default      = @ORGANISATION@
organizationalUnitName          = Organizational Unit Name (eg, section)
commonName                      = Common Name (eg, your name or your server\'s hostname)
commonName_max                  = 64
emailAddress                    = Email Address
emailAddress_max                = 64
##@USERDATA@

[req_attributes] 
challengePassword               = A challenge password
challengePassword_min           = 4
challengePassword_max           = 20
unstructuredName                = An optional company name

[usr_cert] 
basicConstraints                = CA:FALSE
nsComment                       = "OpenSSL Generated Certificate"
subjectKeyIdentifier            = hash
authorityKeyIdentifier          = keyid,issuer:always

[v3_ca] 
subjectKeyIdentifier            = hash
authorityKeyIdentifier          = keyid:always,issuer:always
basicConstraints                = CA:true

[crl_ext]
authorityKeyIdentifier=keyid:always,issuer:always

[OCSP]
basicConstraints        = CA:FALSE
keyUsage                = digitalSignature
extendedKeyUsage        = OCSPSigning
issuerAltName           = issuer:copy
subjectKeyIdentifier    = hash
authorityKeyIdentifier  = keyid:always,issuer:always
authorityInfoAccess     = OCSP;URI:@OCSPURL@
 
[OCSP_SERVER]
nsComment                       = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier            = hash
authorityKeyIdentifier          = keyid,issuer:always
issuerAltName                   = issuer:copy
basicConstraints                = critical,CA:FALSE
keyUsage                        = digitalSignature, nonRepudiation, keyEncipherment
nsCertType                      = server
extendedKeyUsage                = serverAuth
authorityInfoAccess             = OCSP;URI:@OCSPURL@
 
[OCSP_CLIENT]
nsComment                       = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier            = hash
authorityKeyIdentifier          = keyid,issuer:always
issuerAltName                   = issuer:copy
basicConstraints                = critical,CA:FALSE
keyUsage                        = digitalSignature, nonRepudiation
nsCertType                      = client
extendedKeyUsage                = clientAuth
authorityInfoAccess             = OCSP;URI:@OCSPURL@

EOF
	sed -i \
		-e "s#@HOME@#${PKI_PATH}/${NAME}#g" \
		-e "s#@NAME@#${NAME}#g" \
		-e "s#@COUNTRYNAME@#${COUNTRYNAME}#g" \
		-e "s#@STATE@#${STATE}#g" \
		-e "s#@CITY@#${CITY}#g" \
		-e "s#@ORGANISATION@#${COMPANY}#g" \
		-e "s#@OCSPURL@#${OCSP_URL}#g" \
		${PKI_PATH}/${NAME}/ssl.cnf
}

#Main program


# process command line arguments
while getopts "?hup:n:c:" opt
do
	case "${opt}" in
		u)
			printUsage
			exit 0
			;;
		h|\?)
			printUsage
			printHelp
			exit 0
			;;
		c)
			CFG_FILE=$OPTARG
			;;
		p)
			PKI_PATH=$OPTARG
			;;
		n)
			NAME=$OPTARG
			;;
	esac
done

# Load config
[ -f ${CFG_FILE} ] && source ${CFG_FILE} || saveCfg ${CFG_FILE}

clear
[ -z ${NAME} ] && changeName
while true;
do
	printMenu
#	CheckOpenSSLConfig
	read -p " ==> Make your choice [none]: " -n 1 CHOICE
	case ${CHOICE} in
		1)
			addUser
			;;
		2)
			webUser
			;;
		3)
			renewUser
			;;
		4)
			revokeUser
			;;
		5)
			listUser
			;;
		I|i)
			initCA
			;;
		Q|q)
			echo
			break
			;;
		P|p)
			echo
			changeDefaultPath
			;;
		N|n)
			echo
			changeName
			;;
		D|d)
			deleteCA
			;;
		O|o)
			manageOptions
			;;
	esac
done