ca-scripts/lib/ca-functions

291 lines
9.3 KiB
Bash
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# Common functions for ca-scripts.
PROGNAME=$( basename $0 )
CONFFILE="/etc/ca-scripts.conf"
SHAREDIR=$(dirname $(dirname $0))/../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 "$HOME/.ca-scripts.conf" ]; then
# Does a system-wide config exist? If so load it first.
if [ -r "$CONFFILE" ]; then
source "$CONFFILE"
fi
# Override system configuration with local values.
# If manually set on command-line, don't load.
if [ -n "$CONFFILECLI" ]; then
source "$HOME/.ca-scripts.conf"
fi
elif [ -r "$CONFFILE" ]; then
source "$CONFFILE"
else
if [ -n "$CONFFILECLI" ]; then
error "Unable to load $HOME/.ca-scripts.conf or $CONFFILE"
else
error "Unable to load $CONFFILE"
fi
fi
# TODO: Refactored config loader with error fallback, allowing local overrides.
#if [ ! -r "$CONFFILE" ]; then
# error "Unable to find $CONFFILE."
#fi
## XXX: seems like . <file> 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__
}
# TODO: Remove this, as it's no longer used.
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 sedstr
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"
while read ca_var; do
sedstr="${sedstr}s"$'\1'"%$ca_var%"$'\1'"${!ca_var}"$'\1'"g; "
#sedstr="${sedstr}s%$ca_var%${!ca_var}g; "
done < <(set | awk -F\= '/^CA_[A-Z5_]*=/{print $1}')
#echo "DEBUG: setstr=$sedstr"
sed -e "$sedstr" <"$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
}
cert_info() {
local certFile="$1"
local certCN certIssuer certValid certExpire certCA certFilename
if [ -r "$certFile" ]; then
certFilename=$(basename "$certFile")
certCN="$(openssl x509 -in "$certFile" -noout -subject | sed -r 's|.*CN=(.*)|\1|; s|/[^/]*=.*$||')"
certIssuer="$(openssl x509 -in "$certFile" -noout -issuer | sed -r 's|.*CN=(.*)|\1|; s|/[^/]*=.*$||')"
certValid="$(openssl x509 -in "$certFile" -noout -startdate | sed -r 's|.*notBefore=(.*)|\1|;')"
certExpire="$(openssl x509 -in "$certFile" -noout -enddate | sed -r 's|.*notAfter=(.*)$|\1|;')"
if [ "$certCN" = "$certIssuer" ]; then
echo "$certFilename: $certCN expires on $certExpire"
else
echo "$certFilename: $certCN issued by $certIssuer expires on $certExpire"
fi
else
echo "ERROR: $certFile does not exist or cannot be read"
fi
}