#! /bin/bash # common functions for ca-scripts PROGNAME=$( basename $0 ) CONFFILE="/etc/ca-scripts.conf" SHAREDIR="/home/alex/code/ca-scripts/tpl" CRYPTKEY="-nodes" INDEXTPL="index-html" INDEXOUT="" # ideally, run these scripts as an unprivileged "ssl" user/group # and place users that need access to ssl certs into that group # no world-readable stuff here umask 027 error() { usage >&2 echo -e "ERROR: $1\n" >&2 exit 1 } ca_check_var() { local varname vartest varname="$1" vartest="$2" eval "if [ ! $vartest \"\$$varname\" ]; then echo '$varname value \"\$$varname\" failed \"$vartest\" test' fi" } ca_override_conf() { local varname varname="${1#USER_}" eval "$varname=\"\$USER_$varname\"" } ca_set_default() { local varname vardef varname="$1" vardef="$2" eval "if [ -z \"\$$varname\" ]; then $varname=\"$vardef\" fi" } ca_load_conf() { local varname vartest varerr vardef error ca_name if [ ! -r "$CONFFILE" ]; then error "Unable to find $CONFFILE." fi # XXX: seems like . doesn't work if it's not relative to a directory # look this up on the internet sometime to work out why... if [ "$CONFFILE" = "$( basename $CONFFILE )" ]; then CONFFILE="./$CONFFILE" fi . "$CONFFILE" error="" while read vartest varname; do varerr=$( ca_check_var "$varname" "$vartest" ) if [ -n "$varerr" ]; then error="$error\n $varerr" fi done <<__TESTS__ -d CA_HOME -n CA_DOMAIN -n CA_DN_C -n CA_DN_ST -n CA_DN_L -n CA_DN_O -n CA_DN_OU -n CA_DN_CN __TESTS__ if [ -n "$error" ]; then error "Parsing config file $CONFFILE failed:\n$error" fi # check user-provided variables and copy them to CA_ namespace to override # any defaults that have potentially been set in configuration file # XXX: this is getting really dirty now, perhaps find an alternative? set | awk -F\= '/^USER_CA_[A-Z_]*=/{print $1}' | while read user_var; do ca_override_conf "$user_var" done # XXX: and this alternative should probably have better validation ;-) case "$CA_CRT_TYPE" in server|client|user) :;; *) error "Unrecognised certificate type '$CA_CRT_TYPE'!";; esac # we need to do these first to use them in other default defs # NOTE: bash's here-string syntax appends \n which tr turns to _ :( # CA_NAME is NOT configurable, due to the breakage this could cause. CA_NAME="$( echo -n "$CA_DOMAIN" | tr -c '[:alnum:]@-' _ )" ca_set_default CA_EMAIL "ca@$CA_DOMAIN" while read varname vardef; do ca_set_default "$varname" "$vardef" done <<__DEFAULTS__ CA_DESC $CA_DN_CN CA_DAYS 3652 CA_PATHLEN 0 CA_CRT_URI http://$CA_DOMAIN/ca/$CA_NAME.ca.crt CA_CRL_URI http://$CA_DOMAIN/ca/$CA_NAME.ca.crl CA_CRT_DAYS 365 CA_CRL_DAYS 365 CA_CRT_BITS 2048 CA_CRT_TYPE server CA_CRT_C $CA_DN_C CA_CRT_ST $CA_DN_ST CA_CRT_L $CA_DN_L CA_CRT_O $CA_DN_O CA_CRT_OU $CA_DN_OU CA_CRT_E $CA_EMAIL __DEFAULTS__ } ca_sed_cmd() { # MD5 in CA_CR[TL]_MD5_FP has a non alphabetic character :( # XXX: pretty sure this is a dirty and wrong way of templating vars # but we can hope that there's no ascii 001 in the values... set | awk -F\= '/^CA_[A-Z5_]*=/{print $1}' | while read ca_var; do echo -e "s\001%$ca_var%\001${!ca_var}\001g;" done } ca_template() { local template dest if [ -r "$1" ]; then template="$1" elif [ -r "$SHAREDIR/$1.tpl" ]; then template="$SHAREDIR/$1.tpl" else error "Could not read from template $1" fi dest="$2" sed -e "$(ca_sed_cmd)" <"$template" >"$dest" } ca_gen_crl() { openssl ca -config "$CA_HOME/cnf/$CA_NAME.ca.cnf" -gencrl -md sha1 \ -crldays "$CA_CRL_DAYS" -out "$CA_HOME/crl/$CA_NAME.ca.crl" openssl crl -in "$CA_HOME/crl/$CA_NAME.ca.crl" \ -out "$CA_HOME/crl/$CA_NAME.ca.crl.der" -outform DER } ca_gen_p12() { local cnf_name cnf_name="$1" openssl pkcs12 -export -descert -out $CA_HOME/p12/$cnf_name.p12 \ -in $CA_HOME/crt/$cnf_name.crt \ -inkey $CA_HOME/key/$cnf_name.key \ -certfile $CA_HOME/crt/$CA_NAME.ca.crt } ca_checksum() { CA_CRT_MD5_FP="$( openssl x509 -in $CA_HOME/crt/$CA_NAME.ca.crt \ -noout -md5 -fingerprint | cut -d= -f2 )" CA_CRT_SHA_FP="$( openssl x509 -in $CA_HOME/crt/$CA_NAME.ca.crt \ -noout -sha1 -fingerprint | cut -d= -f2 )" CA_CRL_MD5_FP="$( openssl crl -in $CA_HOME/crl/$CA_NAME.ca.crl \ -noout -md5 -fingerprint | cut -d= -f2 )" CA_CRL_SHA_FP="$( openssl crl -in $CA_HOME/crl/$CA_NAME.ca.crl \ -noout -sha1 -fingerprint | cut -d= -f2 )" } ca_cnf_name() { local crt crt="$1" # work out what configuration files we should be using from the cert's CN echo $( openssl x509 -in "$crt" -noout -nameopt sep_multiline,use_quote \ -subject | awk -F= '/CN=/{ gsub("[^A-Za-z0-9@-]", "_", $2); print $2}' ) # grep "CN=" | cut -d= -f2 | tr -c '[:alnum:]@-' _ ) # originally did this like the above but tried awk instead. awk(1) seems # to lie about it's egrep(1) support though as no matter what I tried the # tr(1) regex didn't work in the gsub() call above. } ca_cnf_type() { local crt # XXX: dirty hack -- derive type from filename being *.TYPE.crt crt="${1%.crt}" crt="${crt##*.}" case "$crt" in server|client|user) echo "$crt";; *) echo $CA_CRT_TYPE;; esac } ca_find_cnf() { local name _name _type name="$1" if [ -f "$name" ]; then _name="$(ca_cnf_name $name)" _type="$(ca_cnf_type $name)" if [ $(basename "$name" .crt) = "${_name}.${_type}" ]; then echo "${_name}.${_type}" return else error "Unable to derive config details from certificate '$name'." fi fi # XXX: this stil doesn't handle default types. FIXME when it's not 1am. _name=$( echo -n "$name" | tr -c '[:alnum:]@-' _ ) if [ "$CA_CRT_TYPE" = "user" ]; then # user names may have dots etc. in, so use munged version in match # check if name is "user@domain", append $CA_DOMAIN if not if [ "${_name%%@*}" = "$_name" \ -a -f "$CA_HOME/crt/${_name}@$CA_NAME.$CA_CRT_TYPE.crt" ]; then # name is not fully-qualified, but a cert exists for it echo "${_name}@$CA_NAME.$CA_CRT_TYPE" elif [ -f "$CA_HOME/crt/$_name.$CA_CRT_TYPE.crt" ]; then # name was fully-qualified and a cert exists echo "$_name.$CA_CRT_TYPE" else error "Could not find $CA_CRT_TYPE certificate configuration matching '$name'" fi else # check if name is fully-qualified -- contains more than 1 dot # NOTE: we have to do this test with the unmunged name ... if [ "${name%%.*}" = "$name" \ -a -f "$CA_HOME/crt/${_name}_$CA_NAME.$CA_CRT_TYPE.crt" ]; then # name is not fully-qualified, but a cert exists for it echo "${_name}_$CA_NAME.$CA_CRT_TYPE" elif [ -f "$CA_HOME/crt/$_name.$CA_CRT_TYPE.crt" ]; then # name was fully-qualified and a cert exists echo "$_name.$CA_CRT_TYPE" else error "Could not find $CA_CRT_TYPE certificate configuration matching '$name'" fi fi }