#!/usr/local/bin/bash
#
# CLCA - CLI CA command utility
#
# Copyright (c) 2004-2013 Martin Bartosch, Cynops GmbH
#
# 2004-04-26 Martin Bartosch; Cynops GmbH <m.bartosch@cynops.de>
# 2004-2022 Martin Bartosch; White Rabbit Security GmbH
#
# This software is distributed under the GNU General Public License - see the
# accompanying LICENSE file for more details.
#

# Installation directory - change this to reflect your setup
CA_HOME="$PWD"
CFG=$CA_HOME/etc/clca.cfg

# common commands - change settings to suit your system
TOOLS=$(cat <<EOF
MV=/bin/mv
CP=/bin/cp
LN=/bin/ln
RM=/bin/rm
DD=/bin/dd
MKDIR=/bin/mkdir
CHMOD=/bin/chmod
MKTEMP=/bin/mktemp:/usr/bin/mktemp
CUT=/usr/bin/cut
CAT=/bin/cat
SED=/usr/bin/sed:/bin/sed
DATE=/bin/date
GREP=/usr/bin/grep:/bin/grep
EGREP=/usr/bin/egrep:/bin/egrep
HEAD=/usr/bin/head
STAT=/usr/bin/stat
WC=/usr/bin/wc
TOUCH=/usr/bin/touch
PERL=/usr/local/bin/perl
BASENAME=/usr/bin/basename
TAR=/bin/tar:/usr/bin/tar
EOF
)


######################################################################
# nothing to change below this line

trap traphandler 2 3

######################################################################
# hooks (may be overridden in clca.cfg)

hook_init() {
   :
}

hook_exit() {
   :
}

# default passphrase prompt
# return 0 means that a passphrase has been echoed to stdout
# return 1 means that no passphrase is to be used
get_passphrase() {
    if [ "$ENGINE" = "openssl" ] ; then
        local TMP
        echo -n "Enter passphrase: " >&2
        read -s TMP
        echo $TMP
    fi
}

# make sure get_passphrase is only called once
# this function exports PASSPHRASE as the unencrypted passphrase
# NOTE: callers should unset the PASSPHRASE variable after use
get_passphrase_cached() {
    local CLEARTEXT_PASSPHRASE

    if [ -z "${ENCRYPTED_PASSPHRASE}" ] ; then

	while : ; do
	    CLEARTEXT_PASSPHRASE="$(get_passphrase)"
	    RC=$?
	    if [ $RC -ge 10 ] ; then
		# return code 10 or higher: terminate silently with return code (RC - 10)
		abort $(( $RC - 10 ))
	    fi
	    if [ $RC != 0 ] ; then
		echo "ERROR: could not get passphrase"
		abort 1
	    fi
	    if [ -z "CLEARTEXT_PASSPHRASE" ] ; then
		echo "FATAL: Null passphrase"
		abort 1
	    fi

	    if verify_passphrase "$CLEARTEXT_PASSPHRASE" ; then
		break
	    fi
	    echo
	    echo "Passphrase does not unlock private key, please repeat"
	    echo
	    sleep 1
	done

	export CLCA_SESSION_KEY
	ENCRYPTED_PASSPHRASE=`echo "$CLEARTEXT_PASSPHRASE" | $OPENSSL enc -aes256 -a -e -pbkdf2 -pass env:CLCA_SESSION_KEY`
	if [ "$?" != 0 ] ; then
	    echo "ERROR: could not encrypt passphrase"
	    abort 1
	fi
	export -n CLCA_SESSION_KEY

	PASSPHRASE="$CLEARTEXT_PASSPHRASE"
	return 0
    else
	# passphrase has been set, decrypt and return
	export CLCA_SESSION_KEY
	PASSPHRASE=`echo "$ENCRYPTED_PASSPHRASE" | $OPENSSL enc -aes256 -a -d -pbkdf2 -pass env:CLCA_SESSION_KEY`
	if [ "$?" != 0 ] ; then
	    echo "ERROR: could not decrypt passphrase"
	    abort 1
	fi
	export -n CLCA_SESSION_KEY
	return 0
    fi
}

# try to unlock the private key with the pass phrase
verify_passphrase() {
    if [ "$ENGINE" = "openssl" ] ; then
	if [ ! -e "$CAPRIVDIR/$ROOTKEYNAME" ] ; then
	    echo "WARNING: CA key $CAPRIVDIR/$ROOTKEYNAME does not exist. Continuing."
	    return 0
	fi
	# try to login to software private key
	export TMPPASS="$1"
	$OPENSSL pkey -in $CAPRIVDIR/$ROOTKEYNAME -passin env:TMPPASS >/dev/null 2>&1
	if [ "$?" = "0" ] ; then
	    unset TMPPASS
	    return 0
	fi
	unset TMPPASS
	return 1
    fi

    # assume login is successful with non-openssl keystores (HSMs)
    return 0
}

if [ -r $CFG ] ; then
    . $CFG
else
    echo "Configuration file $CFG not found"
    exit 1
fi


if [ ! -r $CNF ] ; then
    echo "OpenSSL config file $CNF not found"
    exit 1
fi


for i in $TOOLS ; do
    cmd=${i%=*}
    arg=${i#*=}
    path=""
    for j in ${arg//:/ } ; do
	if [ -x $j ] ; then
	    path="$j"
	    break
	fi
    done
    if [ -z "$path" ] ; then
	echo "Could not find executable path for '$cmd' in paths '$arg'."
	echo "Fix the line containing"
	echo "$i in script $0 (or config file $CFG)"
	exit 1
    fi
    eval "$cmd=\"$path\""
    CMDLIST="$CMDLIST $cmd"
done

for i in $OPENSSL ; do
    if [ ! -x $i ] ; then
        echo "Could not find executable $i"
        echo "Fix configuration file $CFG"
        exit 1
    fi
done

if [ "$ENGINE" = "openssl" -a "$HSM_PRELOAD" != "" ] ; then
    echo "NOTE: HSM_PRELOAD should not be necessary for engine 'openssl'"
fi



######################################################################

VERSION="1.21"

showhelp()
{
    case $1 in
	commands)
	    $CAT <<EOF
CLI CA version $VERSION

Usage: clca <command> [arguments]

Available commands:
  help <command>        - Show help for specified command

  list			- List certificates
  issuecrl		- Issue CRL
  certify		- Sign certificate request
  genkey		- Generate (end entity) key
  revoke		- Revoke certificate
  initialize		- Initialize CA
  login	                - Login to CA key and enter subshell
  backup		- Create backup of CA state
  check			- Display config file check sums
EOF

	    # list all custom functions
	    for F in `declare -F -p | cut -d " " -f 3 | grep custom_ | cut -d_ -f2-`; do
		DESC=`custom_$F --shorthelp | head -1` || '<no description available>'
		printf "  %-21s - %s\n" $F "$DESC"
	    done
	    ;;
	list)
	    $CAT <<EOF
Usage: clca list <filter>

Reads the certificate database and prints out a summary of all
certificates matching the specified filter. <filter> may be
either 'valid' or 'revoked'.
If <filter> is not specified all certificates are displayed.

EOF
	    ;;
	issue_crl|issuecrl)
	    $CAT <<EOF
Usage: clca issuecrl

Issues a new CRL with default validity. The resulting CRL will
be stored as $CRLDIR/YYYYMMDDHHMMSS.crl.
In addition a symlink to this file will be created, making it
possible to refer to the latest CRL as $CRLDIR/ca.crl
The CRL is written in PEM format and may be converted to DER
using the command
openssl crl -in $CRLDIR/ca.crl -out $CRLDIR/cacrl.der -outform der

The issuecrl command uses the default_ca section as specified in openssl.cnf.

EOF
	    ;;
	genkey)
	    $CAT <<EOF
Usage: clca genkey [OPTIONS]

Generates a (software) public/private key pair.

OPTIONS:
--keyfile KEYFILE
KEYFILE defaults to the CA key specfied in the clca.cfg ($CAPRIVDIR/$ROOTKEYNAME).
KEYFILE will not be overwritten if it already exists.

--protect default|passphrase|none
Defines the encryption passphrase source for the generated key. Default: 'default'
'default':    generated key is protected by the same mechanism as defined for the CA key (see below)
'passphrase': explicitly ask for a new passphrase for the key protection
'none':       do not encrypt generated key

NOTE: if the environment variable PASSPHRASE is set for '--protect default' the value
of this variable will be used to protect the generated private key. Otherwise
the default meachanism will be used to obtain the CA private key.

--encalg aes256|aes192|aes128|des3
Encrypt key using specified symmetric algorithm. Default: aes256
This setting does not have any effect if "--protect none" is specified.

--algorithm rsa|ec
Public key algorithm to use. Default: 'rsa'
'rsa': generate RSA public key pair
'ec':  generate EC public key pair

Additional options for algorithm 'rsa':
--keysize BITS
Size of the RSA key to generate. Default: 2048.

Additional options for algorithm 'ec':
--curve CURVENAME
Short name of the EC curve. Default: prime256v1
Refer to
openssl ecparam -list_curves
for a full list of the possible parameters.

EOF
		;;
	certify)
	    $CAT <<EOF
Usage: clca certify --profile <name> [--startdate DATESPEC] [--enddate DATESPEC] [--days DAYS] [--reqformat P10|SSCERT|KEY] [--subject <subject>] [--san TYPE:SAN] [--batch] <request file>

Signs a PKCS#10 certificate request (DER/PEM format is automatically
detected). Certificate extensions and validity are determined by
settings in openssl.cnf.
The certificate Subject DN from the request data will be used verbatim
in the certification process unless explicitly overridden via --subject.

When signing certificate requests it is possible to add any number of
Subject Alternative Names by specifying one or more --san options.
The argument following --san is TYPE:SAN.
TYPE must be one of the following: email, DNS, dirName, URI, IP, otherName.
SAN is the respective value. See below for examples.

The certificate profile to use for signing the certificate must be
specified via --profile <name>. The specified argument corresponds to
a section in the openssl.cnf file.
If no --profile is specified the script prints the list of certificate
profiles available for the certify command and exits.

(NOTE: In order to qualify as a valid --profile the openssl.cnf file
section must include x509_extensions and must not include crl_extensions
nor a distinguished_name.)

If --reqformat SSCERT is specified then the input file should be
a self-signed certificate that will be used instead of a PKCS#10 request
(which is the default).

If --reqformat KEY is specified the specified request file must be a PEM
formatted private key.
If the key is encrypted, the passphrase must be set as environment
variable PASSPHRASE when calling the certify command.
A temporary request file is generated from the specified key file
and a certificate is issued for the temporary request.
The request/certificate --subject option is mandatory in this mode.

After certification is performed the new certificate is copied to
the directory $CERTDIR.
The certificate database is updated and a copy of the certificate
is written to the file 'newcert.pem' in the current directory.

If --startdate or --enddate are specified, these dates are used for
the certificate's lifetime. The dates must be specified in the DATESPEC
format and are interpreted as UTC times.
The date specification can be abbreviated by omitting parts of the
date/time specification (e. g. "YYMM" only). Omitted date/time components
are initialized to the lowest possible value.
If --days is specified, the generated certificates is valid for the
specified number of days. (Note that unexpected behavior may result if
combined with --startdate.)
If neither --startdate, --enddate nor --days are specified the default
certificate validity is used.

If --batch is specified the underlying openssl commmand will be called
in batch mode (useful for calling in shell scripts).

Example:
clca certify --profile endentity
  --subject "/DC=org/DC=openxpki/O=OpenXPKI/CN=example.openxpki.org" \\
  --san DNS:foo.example.com --san DNS:bar.example.com --san IP:10.0.0.1 \\
  --san "otherName:1.3.6.1.4.1.311.20.2.3;UTF8:test@kerberos-domain.internal" foo.csr

EOF
	    showhelp datespec
	    ;;
	revoke)
	    $CAT <<EOF
Usage: clca revoke [--reason <reason>] [--compromisetime TIME] <serial number|certificate file>

Revokes the specified certificate. The certificate can be specified
implicitly (as the PEM encoded X.509 certificate file name) or
as its serial number.
NOTE that referencing the certificate by serial number is not possible
if RANDOMIZE_SERIAL is set, in this case the certificate must be
specified via its file name.

If the serial number is specified instead of a file name the script checks
if the certificate exists in the certificate database and that it is
still valid. If the corresponding certificate exists in the
certificate directory the certificate is revoked after asking
the user for approval.

If --reason is specified reason is recorded as revocation reason. Valid
reason codes are
unspecified, keyCompromise, CACompromise, affiliationChanged, superseded,
cessationOfOperation

If --compromisetime is specified, it denotes the point in zulu time the
certificate was compromised. TIME must have the format YYYYMMDDHHMMSSZ.
The Z character at the end of the time spec is mandatory.
If --compromisetime is set, the --reason is set to keyCompromise unless
--reason is CACompromise.

Example:
clca revoke --reason keyCompromise --compromisetime 20190105120800Z 04

EOF
	    ;;
	initialize)
	    $CAT <<EOF
Usage: clca initialize [--req <filename>] [--startdate DATESPEC] [--enddate DATESPEC]

Initializes the CA database and creates either a self-signed certificate
or a PKCS#10 certificate request.
NOTE: This purges all existing data about issued certificates and CRLs and
thus should be performed only once.

If --req <filename> are specified as arguments, no self-signed
certificate is created, instead a PKCS#10 certificate request is
written to the specified filename.

If --startdate and --enddate is specified, these dates denominate
the CA certificate absolute life time for a self-signed CA certificate.
The date specification can be abbreviated by omitting parts of the
date/time specification (e. g. "YYMM" only). Omitted date/time components
are initialized to the lowest possible value.

It is safe to call this function after creating the database,
as it will detect that a CA has already been created and terminate
with a notification message without modifying any data.

If any of the CA directories (ca/certs/crl) already exist, the script
will terminate with an error.

The following steps must be performed to create a CA:

1. Before the database can be initialized it is necessary to create
   the certificate profile and CA configuration (config files ca.cfg
   and openssl.cnf in $CA_HOME/etc).

2. An RSA key must be generated. If a HSM is used, the key must
   be generated using the HSM utilities. Otherwise you will need
   to create a private key by issuing

   openssl genrsa -aes256 -out $CAPRIVDIR/cakey.pem 2048 $CONFIGARG
   chmod 400 $CAPRIVDIR/cakey.pem

3. Create the CA using the initialize command

EOF
	    showhelp datespec
	    ;;
	check)
	    $CAT <<EOF
Usage: clca check

Display cryptographic check sums for config and executable files.
EOF
        ;;
	login)
	    $CAT <<EOF
Usage: clca login

Ask for the passphrase of the CA key, verify if the passphrase is
OK and execute a subshell.

Within the subshell it is possible to repeatedly call clca without
having to re-enter the CA passphrase for each single CA key operation.

Type 'exit' to leave the subshell.
EOF
        ;;
	backup)
	    $CAT <<EOF
Usage: clca backup [filename]

Creates a backup of the CA database and configuration in gzip compressed
tar format. If no filename is specified, the backup will be named
YYYYMMDDHHMMSS-clca-backup.tar.gz in the current directory (caps replaced
with timestamp).
EOF
        ;;
	datespec)
	    $CAT <<EOF
DATESPEC
A DATESPEC is an absolute timestamp representing a point in time. Two different
formats are supported:

Truncated format ("traditional") format: YY[MM[DD[HH[MM]SS[Z]]]]]

IMPORTANT: The truncated format is only supported for years up to and including
the year 2049. In order to specify the year 2050 or later, the complete format
must be used.

The truncated format uses a two-digit year representation and optionally allows
any number of two-digit date/time specification components up to seconds.
It is possible to specify only portions of the date, starting from the
left-hand side and leaving out the lower tier date components, e. g. "YY" or
"YYMMDD".
The missing date elements are implicitly filled with the lowest sensible value
(01 for months and days, 00 for hours, minutes and seconds).


Complete format: YYYYMMDDHHMMSS[Z]
If the specified timestamp is exactly 14 digits long it is assumed to contain
a full date/time specification. A trailing "Z" is optional (see "Time zone").

Time zone:
A full DATESPEC always contains a time zone specification which is following the
actual timestamp value. It may be omitted when specifying the timestamp on the command
line. The default time zone is "Z" and specifies the UTC (Zulu) time zone.

EOF
    esac
}


# calls cleanup and exit with specified exit code
abort()
{
    cleanup
    exit $1
}

traphandler()
{
    reset
    abort 1
}

# clean temporary files
cleanup()
{
    if [ -n "$RM" ] ; then
	$RM -f $PURGEFILES
    fi
}

# register file to purge later
topurge()
{
    if [ -n "$1" ] ; then
	PURGEFILES="$PURGEFILES $*"
    fi
}


# modify openssl.conf and adapt paths contained in the configuration
# file to reflect the settings given in clca.cfg
# this function may be called more then once
# ARG: path to openssl.cnf file
modify_cnf()
{
    $CAT <<EOF | $PERL - $1 >$1.new
\$cahome="$CA_HOME";
\$cadb="$CADBDIR/index.txt";
\$cacert="$CACERT";
\$serial="$CADBDIR/serial";
\$crlnumber="$CADBDIR/crlnumber.txt";
\$crldir="$CRLDIR";
\$cacrl="$CRLDIR/ca.crl";
\$certdir="$CERTDIR";

foreach (qw (\$cadb \$cacert \$serial \$crldir \$cacrl \$certdir)) {
    eval "\$_ =~ s#\$cahome#\\\\\\\$dir#g";
}

while (<>) {
    s/^(dir)\s*=.*/\$1 = \$cahome/;
    s/^(certs)\s*=.*/\$1 = \$certdir/;
    s/^(crl_dir)\s*=.*/\$1 = \$crldir/;
    s/^(database)\s*=.*/\$1 = \$cadb/;
    s/^(new_certs_dir)\s*=.*/\$1 = \$certdir/;
    s/^(certificate)\s*=.*/\$1 = \$cacert/;
    s/^(serial)\s*=.*/\$1 = \$serial/;
    s/^(crlnumber)\s*=.*/\$1 = \$crlnumber/;
    s/^(crl)\s*=.*/\$1 = \$cacrl/;
    print;
}
EOF

    if [ $? = 0 ] ; then
	$MV $1.new $1
    else
	echo "ERROR: could not modify openssl.cnf"
	abort 1
    fi
}

# arg: DATESPEC, format YY[MM[DD[HH[MM]SS[Z]]]]] or YYYYMMDDHHMMSS[Z]
# It is possible to specify only portions of the date,
# starting from the left-hand side and leave out the
# lower tier date components, e. g. "YY" or "YYMMDD".
# The function will add any missing date elements and
# return a valid OpenSSL date with the full fomat YYMMDDHHMMSSZ
# (including the Z at the end of the string).
sanitize_openssl_date() {
    $PERL -e '
    my $date = shift;

    my $yy;
    my $mm;
    my $dd;
    my $hh;
    my $min;
    my $ss;
    my $z;

    # check for YYYYMMDDHHMMSS[Z] syntax
    ($yy, $mm, $dd, $hh, $min, $ss, $z) = ($date =~ m{^(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(Z)?$});

    # not a complete date spec, try the traditional (possibly truncated) one
    if (! defined $yy) {
       ($yy, $mm, $dd, $hh, $min, $ss, $z) = ($date =~ m{^(\d\d)(\d\d)?(\d\d)?(\d\d)?(\d\d)?(\d\d)?(Z)?$});
    }

    if (! defined $yy) {
        print STDERR "ERROR: specified date is not of the form YY[MM[DD[HH[MM[SS[Z]]]]]] or YYYYMMDDHHMMSS[Z]\n";
        exit 1;
    }

    if ($yy >= 50 && $yy < 100) {
        print STDERR "ERROR: Year $yy used in traditional date specification. This would be interpreted as the year 19$yy.\n";
        print STDERR "       Refusing to continue. Please specify the absolute date as YYYYMMDDHHMMSS\n";
        exit 1;
    }
    if ($yy == 20) {
        print STDERR "WARNING: Year 2020 specified in traditional date spec. Please double-check.\n";
    }


    $mm = "01" unless defined $mm;
    $dd = "01" unless defined $dd;
    $hh = "00" unless defined $hh;
    $min = "00" unless defined $min;
    $ss = "00" unless defined $ss;
    $z = "Z" unless defined $z;

    if ($mm < 1 || $mm > 12) {
        print STDERR "ERROR: Invalid month $mm specified.\n";
        exit 1;
    }
    if ($dd < 1 || $dd > 31) {
        print STDERR "ERROR: Invalid day $dd specified.\n";
        exit 1;
    }
    if ($hh > 23) {
        print STDERR "ERROR: Invalid hour $hh specified.\n";
        exit 1;
    }
    if ($min > 59) {
        print STDERR "ERROR: Invalid minute $min specified.\n";
        exit 1;
    }
    if ($ss > 59) {
        print STDERR "ERROR: Invalid second $ss specified.\n";
        exit 1;
    }

    # Determine the full year for validation purposes
    my $full_year;
    if ($yy >= 100) {
        $full_year = sprintf ("%04d", $yy);  # YYYY format
    } else {
        # better safe than sorry
        if ($yy < 50) {
            $full_year = 2000 + $yy;
        } else {
            $full_year = 1900 + $yy;
        }
    }

    # Days per month (non-leap year)
    my @days_in_month = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);

    # Leap year check
    if ($mm == 2) {
        if (($full_year % 4 == 0 && $full_year % 100 != 0) || ($full_year % 400 == 0)) {
            $days_in_month[2] = 29;
        }
    }

    if ($dd < 1 || $dd > $days_in_month[$mm]) {
        print STDERR "ERROR: Invalid day $dd for month $mm in year $full_year.\n";
        exit 1;
    }

    print $yy . $mm . $dd . $hh . $min . $ss . $z . "\n";
    ' "$1"
}


######################################################################
######################################################################

genkey()
{
    ENCALG=${DEFAULT_ENC_ALGORITHM:-aes256}
    KEYFILE="$CAPRIVDIR/$ROOTKEYNAME"
    ALGORITHM=${DEFAULT_PUBKEY_ALGORITHM:-rsa}
    KEYSIZE=${DEFAULT_RSA_KEYSIZE:-2048}
    CURVE=${DEFAULT_EC_CURVE:-prime256v1}
    PROTECT=default

    while [ -n "$1" ] ; do
	case "$1" in
	    --keyfile)
		KEYFILE=$2
		shift
		shift
		;;
	    --encalg)
		if ! echo $2 | $EGREP '^(aes256|aes192|aes128|des3)$' >/dev/null ; then
		    echo "ERROR: invalid encryption algorithm $2"
		    abort 1
		fi
		ENCALG="$2"
		shift
		shift
		;;
	    --algorithm)
		ALGORITHM=$2
		shift
		shift
		;;
	    --keysize)
		KEYSIZE="$2"
		shift
		shift
		;;
	    --curve)
		CURVE="$2"
		shift
		shift
		;;
	    --protect)
		PROTECT="$2"
		shift
		shift
		;;
	    *)
		showhelp genkey
		abort 1
		;;
	esac
    done

    if [ -e "$KEYFILE" ] ; then
	echo "ERROR: key file $KEYFILE already exists. Refusing to overwrite."
	abort 1
    fi


    case "$PROTECT" in
	default)
	    if [ -z "$PASSPHRASE" ] ; then
		get_passphrase_cached
		export PASSPHRASE
	    else
		echo "****************************************************************"
		echo "INFO: using externally supplied PASSPHRASE instead of CA default"
		echo "****************************************************************"
		echo
	    fi
	    if [ -n "${PASSPHRASE}" ] ; then
		OPENSSLOPT="$OPENSSLOPT -passout env:PASSPHRASE -$ENCALG"
	    fi
	    ;;
	passphrase)
	    OPENSSLOPT="$OPENSSLOPT -$ENCALG"
	    ;;
	none)
	    ;;
	*)
	    echo "ERROR: invalid --protect mode"
	    abort1
	    ;;
    esac

    mkdir -p $CAPRIVDIR

    case "$ALGORITHM" in
	rsa)
	    $OPENSSL genrsa $OPENSSLOPT -out "$KEYFILE" $KEYSIZE
	    ;;
	ec)
	    $OPENSSL ecparam -genkey -name $CURVE | $OPENSSL ec $OPENSSLOPT -out "$KEYFILE"
	    ;;
	*)
	    echo "ERROR: invalid encryption algorithm $2"
	    abort 1
	    ;;
    esac

    if [ $? != 0 ] ; then
	echo "ERROR: key generation failed"
	unset PASSPHRASE
	abort 1
    fi
    unset PASSPHRASE
    $CHMOD 400 $KEYFILE
    echo "Key written to $KEYFILE"
}


certify()
{
    if [ "$1" = "" ] ; then
	showhelp certify
	abort 1
    fi

    SAN=()

    PROFILE_ARG_SPECIFIED=0

    REQFORMAT="P10"
    while [ -n "$1" ] ; do
	case "$1" in
	    --profile)
		PROFILE="$2"
		# PROFILE may be passed in internally, only perform sanity checks if specified on command line
		PROFILE_ARG_SPECIFIED=1
		shift
		shift
		;;
	    --startdate|--notbefore)
		STARTDATE=$(sanitize_openssl_date $2)
		if [ -z "$STARTDATE" ] ; then
		    abort 1
		fi
		shift
		shift
		;;
	    --enddate|--notafter)
		ENDDATE=$(sanitize_openssl_date $2)
		if [ -z "$ENDDATE" ] ; then
		    abort 1
		fi
		shift
		shift
		;;
	    --days)
		DAYS=$2
		if [ -z "$DAYS" ] ; then
		    abort 1
		fi
		shift
		shift
		;;
	    --reqformat)
		# optional request format
		# possible values: "SSCERT"
		REQFORMAT="$2"
		shift
		shift
		;;
	    --subject)
		SUBJECT="$2"
		shift
		shift
		;;
	    --san)
		# push SAN to SAN list for later processing
		SAN+=("$2")
		shift
		shift
		;;
            --batch)
                BATCH=1
                shift
                ;;
            --help)
		showhelp certify
		abort 1
		;;
	    *)
		REQ="$1"
		shift
		;;
	esac
    done

    if [ -z "$PROFILE" ] ; then
	echo "ERROR: --profile missing"
	echo
	echo "Available profiles:"
	get_certificate_profiles
	echo
	abort 1
    fi

    if [ "$PROFILE_ARG_SPECIFIED" = "1" -a "`get_certificate_profiles $PROFILE`" != "1" ] ; then
	echo "ERROR: invalid --profile specified."
	echo
	echo "Available profiles:"
	get_certificate_profiles
	echo
	abort 1
    fi

    if [ ${#SAN[@]} != 0 ] ; then
	# SANs specified, need to create temporary config with SANs embedded

	TMPCNF=`$MKTEMP opensslcnf.XXXXXX`
	topurge $TMPCNF

	$PERL -n -e '
        BEGIN {
            $profile = shift;
            while (my $line = shift) {
                push @RAWSANS, $line;
            }
        }
        chomp;
        next if (/^\s*#/);
        $lastsection = $section;
        if (/\[\s*(.*?)\s*\]/) {
            $section = $1;
        }
        if (defined $x509_ext_section && ($x509_ext_section eq $section)) {
            # remove original SAN section vom profile extension section
            next if (/^\s*subjectAltName/);
        }
        if ($lastsection ne $section) {
            # begin of new section found
            if (defined $x509_ext_section && ($x509_ext_section eq $section)) {
                # we are in the extensions section of the requested profile, add SAN section
                push @OUT, $_;
                push @OUT, "subjectAltName = \@command_line_sans";
                $ok = 1;
                next;
            }
        }
        if ($section eq $profile) {
            if (/^\s*x509_extensions\s*=\s*(.*?)\s*$/) {
                # remember extension section name for this profile
                $x509_ext_section = $1;
            }
        }
        push @OUT, $_;
        END {
            foreach my $line (@OUT) {
                print $line . "\n";
            }
            if ($ok) {
                print "[ command_line_sans ]\n";
                my %sancount;
                foreach my $entry (@RAWSANS) {
                    my ($type, $data) = ($entry =~ /^(.*?):(.*)/);
                    if (! defined $type || ($type !~ m{^(?:DNS|IP|email|otherName|URI/)$})) {
                        print STDERR "ERROR: invalid SAN type, use one of the following: DNS|IP|email|otherName|URI\n";
                        exit 1;
                    }
                    my $count = $sancount{$type}++ || 0;
                    print "$type.$count = $data\n";
                }
            }
        }' $PROFILE ${SAN[@]} <$CNF >>$TMPCNF
	if [ $? != 0 ] ; then
	    echo "ERROR: could not process SANs"
	    abort 1
	fi

	# pass temp config to further processing
	CONFIGARG="-config $TMPCNF"
    fi

    if [ ! -r "$REQ" ] ; then
	echo "ERROR: Could not read certificate request $REQ"
	abort 1
    fi

    case "$REQFORMAT" in
	P10|PKCS10)
	    echo "PKCS 10 request"
	    INFORM=der
	    if $GREP "BEGIN.*CERTIFICATE REQUEST" $REQ >/dev/null 2>&1 ; then
		INFORM=pem
		INFILE="$REQ"
	    else
		echo "Converting DER encoded certificate request to PEM"
		INFILE=`$MKTEMP /tmp/temp.XXXXXX`
		topurge $INFILE

		$OPENSSL req -in $REQ -inform $INFORM -out $INFILE -outform pem
		if [ "$?" != 0 ] ; then
		    echo "Conversion failed."
		    abort 1
		fi
	    fi
	    REQUESTINPUT="-in $INFILE"
	    # parse request before signing
	    echo "Request to certify:"
	    $OPENSSL req -text -noout -in $INFILE
	    ;;
	SSCERT|CERT)
	    echo "self signed cert request"
	    INFORM=der
	    if $GREP "BEGIN.*CERTIFICATE" $REQ >/dev/null 2>&1 ; then
		INFORM=pem
		INFILE="$REQ"
	    else
		echo "Converting DER encoded certificate to PEM"
		INFILE=`$MKTEMP /tmp/temp.XXXXXX`
		topurge $INFILE

		$OPENSSL x509 -in $REQ -inform $INFORM -out $INFILE -outform pem
		if [ "$?" != 0 ] ; then
		    echo "ERROR: Conversion failed."
		    abort 1
		fi
	    fi
	    REQUESTINPUT="-ss_cert $INFILE"
	    echo "Request to certify:"
	    $OPENSSL x509 -text -noout -in $INFILE
	    ;;
	KEY)
	    if [ -z "$SUBJECT" ] ; then
		echo "ERROR: --subject is required for KEY request format"
		abort 1
	    fi

	    if [ ! -e $REQ ]; then
		echo "ERROR: pass pem encoded key as last argument."
		abort 1
	    fi

	    INFILE=`$MKTEMP /tmp/temp.XXXXXX`
	    topurge $INFILE

	    if [ -z "$PASSPHRASE" ] ; then
		get_passphrase_cached
		export PASSPHRASE
	    fi
	    if [ -n "${PASSPHRASE}" ] ; then
		OPENSSLOPT="-passin env:PASSPHRASE"
	    fi

	    $OPENSSL req -new $OPENSSLOPT -out $INFILE -outform pem -key "$REQ" $CONFIGARG
	    if [ "$?" != 0 ] ; then
		echo "ERROR: CSR creation failed."
		abort 1
	    fi
	    unset PASSPHRASE

	    REQUESTINPUT="-in $INFILE"
	    echo "Request to certify:"
	    $OPENSSL req -text -noout -in $INFILE
	    ;;
	*)
	    echo "ERROR: Unsupported request format '$REQFORMAT' specified"
	    abort 1
	    ;;
    esac




    if [ "$ENGINE" = "openssl" ] ; then
	KEYARG="-keyfile $CAPRIVDIR/$ROOTKEYNAME"
    else
	# the openssl ca command uses -keyfile instead of -key for the key attribute.
	# and -key means something different here. seriously.
	KEYARG="-engine $ENGINE -keyform engine -keyfile \"$ROOTKEYNAME\""
    fi

    if [ -n "$STARTDATE" ] ; then
	VALIDITY="-startdate $STARTDATE "
    fi
    if [ -n "$ENDDATE" ] ; then
	if [ -n "$DAYS" ] ; then
	    echo "ERROR: --notafter/--enddate and --days are mutually exclusive"
	    abort 1
	fi
	VALIDITY="$VALIDITY -enddate $ENDDATE "
    fi
    if [ -n "$DAYS" ] ; then
	if [ -n "$STARTDATE" ] ; then
	    echo "WARNING: using --startdate/--notbefore and --days does not work as expected, --days is interpreted as relative to NOW, not to --notbefore"
	fi
	VALIDITY="$VALIDITY -days $DAYS "
    fi


    SERIAL=`$CAT $CADBDIR/serial`
    if [ -n "$SUBJECT" ] ; then SUBJECT="-subj '$SUBJECT'" ; fi

    if [ "$RANDOMIZE_SERIAL" == "1" ] ; then
	OPENSSLOPT="-rand_serial"
    fi

    get_passphrase_cached
    export PASSPHRASE
    if [ -n "${PASSPHRASE}" ] ; then
	OPENSSLOPT="$OPENSSLOPT -passin env:PASSPHRASE"
    fi


    # Note: set BATCH to a nonempty value to activate openssl batch mode
    if [ -n "$BATCH" ] ; then
	OPENSSLOPT="$OPENSSLOPT -batch"
    fi

    eval $HSM_PRELOAD $OPENSSL ca $KEYARG $OPENSSLOPT -name $PROFILE $VALIDITY $SUBJECT -cert $CACERT -out newcert.pem -outdir $CERTDIR $CONFIGARG $REQUESTINPUT

    if [ "$?" != 0 ] ; then
	echo "ERROR: Certification failed."
	abort 1
    fi
    unset PASSPHRASE

    if [ -r $CERTDIR/$SERIAL.pem ] ; then
	echo "Wrote certificate to $CERTDIR/$SERIAL.pem"
    fi
}

######################################################################
revoke()
{
    ID=""
    REASON="unspecified"
    while [ -n "$1" ] ; do
	case "$1" in
	    --help)
		showhelp revoke
		abort 1
		;;
	    --reason)
		REASON=$2
		shift
		shift
		;;
	    --compromisetime)
		COMPROMISETIME=$2
		shift
		shift
		;;
	    --*)
		echo "ERROR: invalid option $1"
		abort 1
		;;
	    *)
		ID=$1
		shift
		;;
	esac
    done

    if [ "$ID" = "" ] ; then
	showhelp revoke
	abort 1
    fi

    case $REASON in
	unspecified|keyCompromise|CACompromise|affiliationChanged|superseded|cessationOfOperation)
	    :
	    ;;
	*)
	    echo "ERROR: invalid reason code $REASON"
	    abort 1
	    ;;
    esac

    if [ -n "$COMPROMISETIME" ] ; then
	if ! echo $COMPROMISETIME | $EGREP '^[[:digit:]]{14}Z$' >/dev/null 2>&1 ; then
	    echo "ERROR: --compromisetime argument must match YYYYMMDDHHMMSSZ"
	    echo "       (including trailing Z)"
	    abort 1
	fi
	if [ "$REASON" != "CACompromise" ] ; then
	    REASON="keyCompromise"
	fi
    fi

    CERT_TO_REVOKE=""

    if [ -e "$ID" ] ; then
	# certificate file specified
	CERT_TO_REVOKE="$ID"

    else
	# try to identify certificate by serial number

	FOUND=$((`$CUT -f1,4 $CADBDIR/index.txt | $EGREP -i "^V.*\<$ID\>" | wc -l`))

	case $FOUND in
	    0)
		echo "No matching valid certificate found for serial number '$ID'"
		abort 0
		;;
	    1)
		SERIAL=`$CUT -f1,4 $CADBDIR/index.txt | $EGREP -i "^V.*\<$ID\>" | $CUT -f2`
		if [ -r $CERTDIR/$SERIAL.pem ] ; then
		    echo "Revoking certificate with serial number '$SERIAL':"

		    $OPENSSL x509 -in $CERTDIR/$SERIAL.pem -subject -dates -serial -fingerprint -noout
		    if [ "$?" != 0 ] ; then
			echo "ERROR: Could not decode X.509 certificate $CERTDIR/$SERIAL.pem. Should not happen."
			abort 1
		    fi

		    CERT_TO_REVOKE=$CERTDIR/$SERIAL.pem

		else
		    echo "ERROR: Certificate file $CERTDIR/$SERIAL.pem not found"
		    abort 1
		fi
		;;
	    *)
		echo "ERROR: More than one certificate found."
		abort 1
		;;
	esac
    fi

    if [ ! -r "$CERT_TO_REVOKE" ] ; then
	echo "ERROR: could not read certificate file $CERT_TO_REVOKE"
	abort 1
    fi


    if [ "$ENGINE" = "openssl" ] ; then
	KEYARG="-keyfile $CAPRIVDIR/$ROOTKEYNAME"
    else
	KEYARG="-engine $ENGINE -keyform engine -keyfile \"$ROOTKEYNAME\""
    fi

    get_passphrase_cached
    export PASSPHRASE
    if [ -n "${PASSPHRASE}" ] ; then
	OPENSSLOPT="-passin env:PASSPHRASE"
    fi

    if [ -n "$COMPROMISETIME" ] ; then
	if [ "$REASON" = "CACompromise" ] ; then
	    OPENSSLOPT="$OPENSSLOPT -crl_CA_compromise $COMPROMISETIME"
	else
	    OPENSSLOPT="$OPENSSLOPT -crl_compromise $COMPROMISETIME"
	fi
    else
	OPENSSLOPT="$OPENSSLOPT -crl_reason $REASON"
    fi

    eval $HSM_PRELOAD $OPENSSL ca $KEYARG $OPENSSLOPT -revoke $CERT_TO_REVOKE $CONFIGARG

    if [ $? != 0 ] ; then
	echo "ERROR: Could not revoke certificate."
	abort 1
    fi
    unset PASSPHRASE

    echo "INFO: It is recommended to issue a CRL after revoking a certificate."
}

######################################################################
list()
{
    FILTER=""
    while [ -n "$1" ] ; do
	case "$1" in
	    --help)
		showhelp list
		abort 1
		;;
	    valid|--valid)
		FILTER="V"
		shift
		;;
	    revoked|--revoked)
		FILTER="R"
		shift
		;;
	    *)
		showhelp list
		abort 1
		;;
	esac
    done

    case "$FILTER" in
	V)
	    echo "Filter: Only valid certificates"
	    ;;
	R)
	    echo "Filter: Only revoked certificates"
	    ;;
	*)
	    echo "Filter: All certificates"
	    ;;
    esac


    $CUT -f1,4 <$CADBDIR/index.txt | ( while read status serial ; do
	    if [ "$FILTER" = "" -o "$FILTER" = "$status" ] ; then
		CERTFILE="$CERTDIR/$serial.pem"

		case $status in
		    V)
			PSTATUS="VALID"
			;;
		    R)
			PSTATUS="REVOKED"
			;;
		    E)
			PSTATUS="EXPIRED"
			;;
		esac

		echo "$PSTATUS Certificate $CERTFILE"
		if [ -r "$CERTFILE" ] ; then
		    $OPENSSL x509 -in $CERTFILE -subject -dates -serial -fingerprint -noout
		    echo
		else
		    echo "WARNING: Certificate $CERTFILE does not exist"
		fi
	    fi
	done )
}


######################################################################
# issue CRL
issue_crl()
{
    while [ -n "$1" ] ; do
	case "$1" in
	    --help)
		showhelp issuecrl
		abort 1
		;;
	    *)
		showhelp issuecrl
		abort 1
		;;
	esac
    done

    echo "Issuing new CRL..."

    # determine filename
    TIMESTAMP=`$DATE +%Y%m%d%H%M%S`
    CRLFILE=$CRLDIR/$TIMESTAMP.crl
    LATEST=$CRLDIR/ca.crl

    if [ "$ENGINE" = "openssl" ] ; then
	KEYARG="-keyfile $CA_HOME/private/$ROOTKEYNAME"
    else
	# the openssl ca command uses -keyfile instead of -key for the key attribute.
	# and -key means something different here. seriously.
	KEYARG="-engine $ENGINE -keyform engine -keyfile \"$ROOTKEYNAME\""
    fi

    get_passphrase_cached
    export PASSPHRASE
    if [ -n "${PASSPHRASE}" ] ; then
	OPENSSLOPT="-passin env:PASSPHRASE"
    fi

    eval $HSM_PRELOAD $OPENSSL ca -gencrl $KEYARG $OPENSSLOPT -out $CRLFILE $CONFIGARG
    if [ $? != 0 ] ; then
	echo "ERROR: Could not issue CRL."
	$RM -f $CRLFILE
	abort 1
    fi
    unset PASSPHRASE

    ( cd $CRLDIR && $LN -sf `$BASENAME $CRLFILE` `$BASENAME $LATEST` )

    $OPENSSL crl -in $CRLFILE -text -noout

    echo
    echo "* CRL $CRLFILE created."
    echo "NOTE: Latest CRL is also available as $LATEST."
}


######################################################################
# set up CA structure
initialize()
{
    while [ -n "$1" ] ; do
	case "$1" in
	    --startdate|--notbefore)
		STARTDATE=$(sanitize_openssl_date $2)
		if [ -z "$STARTDATE" ] ; then
		    abort 1
		fi
		shift
		shift
		;;
	    --enddate|--notafter)
		ENDDATE=$(sanitize_openssl_date $2)
		if [ -z "$ENDDATE" ] ; then
		    abort 1
		fi
		shift
		shift
		;;
	    --req)
		CAREQ="$2"
		shift
		shift
		;;
	    --serial)
		SERIAL="$2"
		shift
		shift
		;;
	    --help)
		showhelp initialize
		abort 1
		;;
	    *)
		showhelp initialize
		abort 1
		;;
	esac
    done

    if [ -n "$CAREQ" ] ; then
	if [ -n "$STARTDATE" -o -n "$ENDDATE" ] ; then
	    echo "--req and --startdate/--enddate are mutually exclusive"
	    abort 1
	fi
    fi

    # verify if CA key already exists
    case "$ENGINE" in
	chil)
	    FOUND=`$NFAST_HOME/bin/nfkminfo -k | ( while read dummy1 app dummy2 key ; do
		if [ "$dummy1" = "AppName" -a "$app" = "hwcrhk" -a "$key" = "$ROOTKEYNAME" ] ; then
		    echo 1
		fi
	    done ) `
	    ;;
        gem)
            FOUND=1
            ;;
	openssl)
	    if [ -r $CAPRIVDIR/$ROOTKEYNAME ] ; then
		FOUND=1
	    fi
	    ;;
	pkcs11)
	    FOUND=1
	    ;;
	*)
	    echo "ERROR: Engine '$ENGINE' not supported yet"
	    abort 1
	    ;;
    esac

    if [ "$FOUND" != "1" ] ; then
	echo "Root CA key '$ROOTKEYNAME' was not found, please create root key or"
	echo "fix configuration"
	case "$ENGINE" in
	    openssl)
		echo "Hint:"
		echo "mkdir $CAPRIVDIR"
		echo "openssl genrsa -aes256 -out $CAPRIVDIR/$ROOTKEYNAME 2048 $CONFIGARG"
		echo "chmod 400 $CAPRIVDIR/$ROOTKEYNAME"
		;;
	    chil)
		echo "Hint:"
		echo "$NFAST_HOME/bin/generatekey2 hwcrhk NAME=$ROOTKEYNAME"
		;;
	esac

	abort 1;
    fi

    for DIR in $CADBDIR $CERTDIR $CRLDIR ; do
	if [ -e $DIR ] ; then
	    echo "* ERROR: $DIR already exists, bailing out."
	    echo "Hint: If you wish to clear this CA and start with a new one remove the following files and directories:"
	    if [ "$ENGINE" = "openssl" ] ; then

		echo "# rm -rf $CAPRIVDIR # only if you wish to erase the private key"
	    fi
	    echo "rm -rf $CADBDIR"
	    echo "rm -rf $CERTDIR"
	    echo "rm -rf $CRLDIR"
	    abort 1
	fi
    done

    echo "Creating a new CA..."

    echo -e "* Adjusting $CNF file"
    $MKDIR -p $CADBDIR
    $CP $CNF $CNF.bak

    # adapt openssl.cnf file
    modify_cnf $CNF

    for i in $CERTDIR $CRLDIR ; do
	echo "* Creating directory $i"
	$MKDIR -p $i || abort 1
    done

    if [ "$ENGINE" = "openssl" ] ; then
	echo "* Creating directory $CAPRIVDIR"
	$MKDIR -m 700 -p $CAPRIVDIR || abort 1
    fi

    echo "* Initializing CA"

    if [ ! -s $CAPRIVDIR/.rand ] ; then
	echo "Initialize random seed"
	$MKDIR -p $CAPRIVDIR
	$DD if=/dev/urandom of=$CA_HOME/private/.rand bs=1 count=1024
	$CHMOD 600 $CAPRIVDIR/.rand
	if [ "`$WC -c $CAPRIVDIR/.rand | awk '{print $1}'`" != "1024" ] ; then
	    echo "ERROR: Could not read enough random data from /dev/random, rerun this script"
	    abort 1
	fi
    fi

    echo "* Initializing certificate index file"
    $TOUCH $CADBDIR/index.txt
    echo "* Initializing serial numbers"

    $OPENSSL rand -hex 20 > $CADBDIR/serial

    echo "* Initializing CRL index"
    $CAT > $CADBDIR/crlnumber.txt <<EOF
01
EOF



    if [ "$ENGINE" = "openssl" ] ; then
	KEYARG="-key $CAPRIVDIR/$ROOTKEYNAME"
    else
	KEYARG="-engine $ENGINE -keyform engine -key \"$ROOTKEYNAME\" "
    fi

    if [ -z "$CAREQ" ]; then
	# create self-signed certificate
	X509ARG=-x509
	if [ -n "$SERIAL" ] ; then
	    X509ARG="$X509ARG -set_serial $SERIAL"
	fi

	OUT="$CACERT"

	# determine if absolute certificate validity is requested
	if [ -n "$STARTDATE" -o -n "$ENDDATE" ] ; then
	    HAVE_VALIDITY=1
	    # work around OpenSSL bug: self-signed certificates cannot
	    # be created with absolute validity, so create a temporary
	    # self-signed cert with validity 1 days, a certificate request
	    # and certify the request using the temporary certificate
	    OUT=`$MKTEMP tempcacert.XXXXXX`
	    topurge $OUT
	    # this temporary certificate should not exist at all, so limit
	    # its validity to one day.
	    CA_VALIDITY=1

	    echo "* Absolute validity requested, creating dummy certificate to work around an"
	    echo "  OpenSSL limitation. (You may have to enter the private key PIN twice due"
	    echo "  to this problem.)"
	fi
    else
	# create certificate request only
	echo "* Generating self-signed CA root certificate with a validity of $CA_VALIDITY days"
	OUT="$CAREQ"
    fi

    # oliwel - overload environment to get a clean request
    export CN="Root CA"

    get_passphrase_cached
    export PASSPHRASE
    if [ -n "${PASSPHRASE}" ] ; then
	OPENSSLOPT="-passin env:PASSPHRASE"
    fi

    if [ -n "${CA_VALIDITY}" ] ; then
	OPENSSLOPT="$OPENSSLOPT -days $CA_VALIDITY"
    fi

    eval $HSM_PRELOAD $OPENSSL req $OPENSSLOPT -new $X509ARG $KEYARG -out $OUT $CONFIGARG

    if [ $? != 0 ] ; then
	echo "ERROR: Something went wrong, could not create $OUT."
	abort 1
    fi
    unset PASSPHRASE

    # self-signed-certificate-with-absolute-validity workaround continued
    if [ -n "$HAVE_VALIDITY" ] ; then
	#
	if [ -n "$STARTDATE" ] ; then
	    VALIDITY="-startdate $STARTDATE "
	fi
	if [ -n "$ENDDATE" ] ; then
	    VALIDITY="$VALIDITY -enddate $ENDDATE "
	fi

 	# now we have got:
 	# a dummy ca certificate, validity 1 day, subject as entered
 	SAVED_CACERT="$CACERT"
 	CACERT="$OUT"

	# force serial number of prototype certificate
 	SAVED_SERIAL=`$CAT $CADBDIR/serial`
	SERIAL=`$OPENSSL x509 -noout -serial -in $OUT`
	SERIAL="${SERIAL#*=}"
 	echo "$SERIAL" >$CADBDIR/serial

	# force Root extensions
	PROFILE="CA_default"
 	certify $VALIDITY --reqformat SSCERT $OUT
	unset PROFILE

 	CACERT="$SAVED_CACERT"

	# install ca certificate
 	$MV newcert.pem $CACERT

	# restore previous serial number
 	echo "$SAVED_SERIAL" >$CADBDIR/serial

	# clear cert db
	$MV $CADBDIR/index.txt.old $CADBDIR/index.txt
    fi


    # final step for self-signed certificates
    if [ -z "$CAREQ" ]; then
	# convert cacert to der
	    $OPENSSL x509 -in $CACERT -inform pem -out $CADBDIR/`$BASENAME $CACERT .pem`.der -outform der
	    if [ $? != 0 ] ; then
		echo "Could not convert certificate to DER (should not happen)."
	    fi
	    echo "* CA root certificate contents:"
	    $OPENSSL x509 -in $CACERT -noout -text
    else
	echo "* Certify PKCS#10 $CAREQ and place resulting CA certificate in $CACERT"
    fi

}


###########################################################################
check()
{
    while [ -n "$1" ] ; do
	case "$1" in
	    --help)
		showhelp check
		abort 1
		;;
	    --testdate)
		sanitize_openssl_date $2
		abort 0
		;;
	    *)
		showhelp check
		abort 1
		;;
	esac
    done

    echo "Check sums for config files:"
    for i in $0 $CFG $CNF ; do
	$OPENSSL dgst -sha256 $i
    done

    echo
    echo "Cumulative checksum over all system tools and executables:"
    # compute cumulative checksum on system tools
    CHECKLIST="$CMDLIST OPENSSL"
    if [ -n "$HSM_PRELOAD" ] ; then
	CHECKLIST="$CHECKLIST HSM_PRELOAD"
    fi
    ( $OPENSSL dgst -sha256 $0 ;
      for i in $CHECKLIST ; do
          eval $OPENSSL dgst -sha256 \$$i
      done ) | $CUT -d' ' -f2  | $OPENSSL dgst -sha256
}


backup()
{
    FILENAME=""
    while [ -n "$1" ] ; do
	case "$1" in
	    --help)
		showhelp backup
		abort 1
		;;
	    *)
		FILENAME="$1"
		shift
		;;
	esac
    done

    echo "Backup CA configuration and database"

    if [ "$FILENAME" = "" ] ; then
	TIMESTAMP=`$DATE +%Y%m%d%H%M%S`
	FILENAME="$TIMESTAMP-clca-backup.tar.gz"
    fi
    if [ -e "$FILENAME" ] ; then
	echo "ERROR: File $FILENAME already exists. Aborted."
	abort 1
    fi
    echo "Creating backup archive file $FILENAME"

    ( cd $CA_HOME && $TAR -c -z --exclude $FILENAME -f - . ) >$FILENAME
}


# undocumented feature - modify openssl.cnf to reflect current
# path settings
modifycnf()
{
    echo "Modifying $CNF file"
    modify_cnf $CNF
}

get_certificate_profiles() {
    # this inline script iterates through the OpenSSL configuration and
    # extracts all sections which satisfy the following conditions:
    # - contained in a regular OpenSSL section: [ Foo ]
    # - section includes x509_extensions
    # - section does not include crl_extensions (only useful for issuing CAs on the CA level)
    # - section does not include a DN (only useful request generation)
    #
    # Arguments:
    # $1 - if empty: print list of profiles
    #      if specified: if profile exists, print profile, otherwise no output

    $PERL -n -e '
        BEGIN {
            $wanted_profile = shift;
        }
        chomp;
        next if (/^\s*#/);
        if (/\[\s*(.*?)\s*\]/) {
            $section = $1
        }
        if (/x509_extensions/) {
            $has_x509_extensions = 1
        }
        if (/crl_extensions/) {
            $has_crl_extensions = 1
        }
        if (/distinguished_name/) {
            $has_dn = 1
        }
        if ($lastsection ne $section) {
            if ($has_x509_extensions && ! $has_crl_extensions && ! $has_dn) {
                push @sections, $lastsection;
            }
            $has_x509_extensions = 0;
            $has_crl_extensions = 0;
            $has_dn = 0;
        }
        $lastsection = $section;
        END {
            if ($wanted_profile) {
                print grep(/^$wanted_profile$/, @sections) . "\n";
            } else {
                print join("\n", @sections) . "\n";
            }
        }' $1 <$CNF
}

######################################################################
# main

# get a random password for this session
if [ -z "$CLCA_SESSION_KEY" ] ; then
    # only set it if not passed in from the environment
    # if we are called from a "login" sub shell we get the
    # session key of the parent for decryption of the
    # encrypted passphrase
    CLCA_SESSION_KEY=`$OPENSSL rand -base64 40`
fi


if [ "$CNF" != "" ] ; then CONFIGARG="-config $CNF" ; fi

CMD=`$BASENAME $0 | sed -e 's/^clca//'`
INVOCATION="$0"
if [ "$CMD" = "" ] ; then
    if [ "$1" != "" ] ; then
	CMD="$1"
	INVOCATION="$0 $CMD"
	shift
    else
	CMD="help"
    fi
fi
CMD=`echo $CMD | $SED -e 's/^_//'`

case "$CMD" in
    issuecrl)
        # call legacy function name
        CMD=issue_crl
        ;;
    issue_crl|certify|genkey|revoke|list|initialize|check|backup|modifycnf)
        :
	;;
    login)
	shift
	get_passphrase_cached
	export ENCRYPTED_PASSPHRASE
	export CLCA_SESSION_KEY
	echo "Entering subshell, you can now run clca commands without having to enter the passphrase."
	echo "Type 'exit' to return."
	( export PS1="\h:\w \u [clca: logged in] \$ " ; $SHELL -i )
	cleanup
	exit 0
	;;
    help)
	if [ "$1" = "" ] ; then
	    showhelp commands
	else
	    showhelp $1
	fi
	abort 1
	;;
    *)
	CUSTOM_FUNCTION="custom_$CMD"
	TYPE=`type -t $CUSTOM_FUNCTION`
	if [ "$TYPE" != "function" ] ; then
	    echo "Unknown invocation mode '$CMD'"
	    showhelp commands
	    abort 1
	else
	    # delegate command execution to custom function
	    CMD=$CUSTOM_FUNCTION
	fi
	;;
esac

hook_init "$@"
$CMD "$@"
hook_exit "$@"

cleanup
