#!/bin/sh

. cert-sh-functions
. shell-config

CA_VERBOSE="${CA_VERBOSE:-}"
CA_SKO_ROOT="${CA_SKO_ROOT:-/var/lib/alterator-ca}"
CA_SKO_CADIR="${CA_SKO_CADIR:-$CA_SKO_ROOT/CA}"
CA_SKO_CONFIG="${CA_SKO_CONFIG:-/usr/share/alterator-ca/CA.cnf}"
CA_SKO_DNPARAM="${CA_SKO_DNPARAM:-/etc/alterator-ca/dnparam.txt}"

#CA infrastructure

ca_check_CAdir()
{
	[ -d "$CA_SKO_CADIR" -a -d "$CA_SKO_CADIR/private" ]
}

ca_make_CAdir()
{
	ca_check_CAdir && return

	mkdir -m700 -p "$CA_SKO_CADIR"
	mkdir -m700 -p "$CA_SKO_CADIR/certs"
	mkdir -m700 -p "$CA_SKO_CADIR/private"
	mkdir -m700 -p "$CA_SKO_CADIR/newcerts"

	touch "$CA_SKO_CADIR/index.txt"
	chmod 600 "$CA_SKO_CADIR/index.txt"

	echo 01 > "$CA_SKO_CADIR/serial"
	chmod 600 "$CA_SKO_CADIR/serial"
}

ca_get_subject()
{
	local C O

	C="$(shell_config_get "$CA_SKO_DNPARAM" "C")"
	C="${C:-RU}"
	O="$(shell_config_get "$CA_SKO_DNPARAM" "O")"
	O="${O:-Snake Oil, Ltd.}"
	printf "/C=%s/O=%s/OU=%s Certification Authority/CN=%s Root Certification Authority" "$C" "$O" "$O" "$O"
}

#CA private key

ca_check_CAkey()
{
	[ -f "$CA_SKO_CADIR/private/cakey.pem" ]
}

ca_make_CAkey()
{
	ca_check_CAkey && return

	"$OPENSSL" genrsa -out "$CA_SKO_CADIR/private/cakey.pem" 1024 >/dev/null 2>&1 ||
		ssl_fatal "Unable to create CA private key"
}

#CA itself

ca_check_CA()
{
	[ -f "$CA_SKO_CADIR/cacert.pem" ] || return 1

	subject="$(ca_get_cert_attr "$CA_SKO_CADIR/cacert.pem" subject)"
	ca_subject="$(ca_get_subject)"

	[ "$subject" = "$ca_subject" ]
}

ca_make_CA()
{
	ca_check_CA && return

	subject="$(ca_get_subject)"

	"$OPENSSL" req -batch -new -key "$CA_SKO_CADIR/private/cakey.pem" -out "$CA_SKO_CADIR/cacert.csr" -subj "$subject" >/dev/null 2>&1 ||
		ssl_fatal "Unable to create sign request"
	"$OPENSSL" ca -batch -config "$CA_SKO_CONFIG" -selfsign -days 3650 -extensions v3_ca -extfile /etc/openssl/openssl.cnf -keyfile "$CA_SKO_CADIR/private/cakey.pem" -in "$CA_SKO_CADIR/cacert.csr" -out "$CA_SKO_CADIR/cacert.pem" -subj "$subject" >/dev/null 2>&1 ||
		ssl_fatal "Unable to create CA certificate"

	printf '%s\n' "$subject" >> "$CA_SKO_ROOT/ca_history.txt"
	# validating newly-created certificate may yeld "not yet valid"
	sleep 1
}

ca_export_CAcert()
{
	[ -n "$1" ] ||
		ssl_fatal 'Insufficient arguments'

	cat "$CA_SKO_CADIR/cacert.pem" > "$SSL_CERTDIR/$1.pem"
	c_rehash "$SSL_CERTDIR" >/dev/null 2>&1
}

ca_update_db()
{
	"$OPENSSL" ca -batch -config "$CA_SKO_CONFIG" -updatedb 2>/dev/null ||:
}

#CA sign

ca_check_req()
{
	[ -n "$1" -a -n "$2" ] ||
		ssl_fatal 'Insufficient arguments'
	[ -f "$1" ] ||
		ssl_fatal 'Missing sign request'
	[ ! -f "$2" -o "$1" -nt "$2" ] ||
		ssl_fatal "Certificate is newer than sign request"
}

ca_sign_req2()
{
	[ -n "$1" -a -n "$2" ] ||
		ssl_fatal 'Insufficient arguments'
	
	"$OPENSSL" ca -batch -config "$CA_SKO_CONFIG" -out "$2" -in "$1" -notext >/dev/null 2>&1 ||
		ssl_fatal "Unable to sign certificate"
}

ca_sign_req()
{
	[ -n "$1" ] ||
		ssl_fatal 'Insufficient arguments.'

	ssl_check_req "$1" ||
		ssl_fatal 'Missing sign request'

	ca_sign_req2 "$SSL_CSRDIR/$1.csr" "$SSL_CERTDIR/$1.cert"

	ln -sf "$1.cert" "$SSL_CERTDIR/$1.pem"
	c_rehash "$SSL_CERTDIR" > /dev/null

	ssl_make_pem "$1"
}

#CA attributes

ca_get_cert_attr()
{
	local file param

	[ -n "$1" -a -n "$2" ] ||
		ssl_fatal 'Insufficient arguments'

	file="$1" && shift
	param="$1" && shift
	"$OPENSSL" x509 -in "$file" -noout -nameopt compat "-$param" "$@" 2>/dev/null | cut -d= -f 2- | sed 's,\(^[[:blank:]]\+\|[[:blank:]]\+$\),,g'
}

#CA check

ca_needs_resign()
{
	local code="$1"

	[ "$code" = "1" -o "$code" = "3" -o "$code" = "4" -o "$code" = "6" ] &&
		return 0
	return 1
}

# 0 - OK
# 1 - invalid
# 2 - not yet valid
# 3 - expired
# 4 - will expire in 10 days
# 5 - issuer does not match CA
# 6 - self-signed certificate
ca_check_cert2()
{
	local startdate enddate curdate subject issuer ca_subject ca_issuer

	[ -n "$1" ] ||
		ssl_fatal 'Insufficient arguments.'

	[ -s "$1" ] ||
		return 1

	startdate="$(ca_get_cert_attr "$1" startdate)" ||
		return 1
	enddate="$(ca_get_cert_attr "$1" enddate)" ||
		return 1
	subject="$(ca_get_cert_attr "$1" subject)" ||
		return 1
	issuer="$(ca_get_cert_attr "$1" issuer)" ||
		return 1
	ca_subject="$(ca_get_cert_attr "$CA_SKO_CADIR/cacert.pem" subject)" ||
		return 1
	ca_issuer="$(ca_get_subject)"

	if [ "$issuer" = "$ca_subject" ]; then
		# local CA itself or signed by local CA
		if [ "$subject" = "$issuer" -a "$issuer" != "$ca_issuer" ]; then
			# outdated local CA
			return 1
		fi
	else
		if [ "$subject" = "$issuer" ]; then
			# self-signed
			return 6
		else
			if grep -qsFx -e "$ca_subject" "$CA_SKO_ROOT/ca_history.txt" 2>/dev/null; then
				# old local
				[ "$issuer" = "$ca_issuer" ] ||
					return 1
			else
				# foreign trusted
				return 5
			fi
		fi
	fi

	startdate="$(date -d "$startdate" +%s)"
	enddate="$(date -d "$enddate" +%s)"
	curdate="$(date +%s)"
	[ -n "$startdate" -a -n "$enddate" -a -n "$curdate" ] ||
		return 1

	[ "$curdate" -gt "$startdate" ] ||
		return 2
	[ "$curdate" -lt "$enddate" ] ||
		return 3
	"$OPENSSL" x509 -in "$1" -checkend 864000 > /dev/null||
		return 4

	"$OPENSSL" verify -CAfile "$CA_SKO_CADIR/cacert.pem" "$1" >/dev/null 2>&1 ||
		return 1
}

ca_status_message()
{
	[ -n "$1" ] ||
		ssl_fatal 'Insufficient arguments.'

	case "$1" in
		0) printf 'OK\n';;
		1) printf 'invalid\n';;
		2) printf 'not yet valid\n';;
		3) printf 'expired\n';;
		4) printf 'will expire in 10 or less days\n';;
		5) printf 'issuer does not match CA\n';;
		6) printf 'self-signed certificate\n';;
		*) printf 'unknown error';;
	esac
}

ca_check_cert()
{
	local ret

	[ -n "$1" ] ||
		ssl_fatal 'Insufficient arguments.'

	ret=0
	ca_check_cert2 "$@" || ret=$?

	printf '%s: %s\n' "$1" "$(ca_status_message "$ret")"

	return $ret
}

#CA info

ca_show_cert()
{
	local startdate enddate sha1fp md5fp subj issuer retcode status

	[ -n "$1" ] ||
		ssl_fatal 'Insufficient arguments'
	[ -f "$1" ] ||
		ssl_fatal 'Missing certificate'

	startdate="$(ca_get_cert_attr "$1" startdate)"
	enddate="$(ca_get_cert_attr "$1" enddate)"
	sha1fp="$(ca_get_cert_attr "$1" fingerprint)"
	md5fp="$(ca_get_cert_attr "$1" fingerprint -md5)"
	subj="$(ca_get_cert_attr "$1" subject)"
	issuer="$(ca_get_cert_attr "$1" issuer)"

	retcode=0
	ca_check_cert2 "$@" || retcode=$?
	status="$(ca_status_message "$retcode")"

	if [ -z "$CA_VERBOSE" ]; then
		printf '%s\t%s\t%s\t%s\n' "${1##*/}" "$enddate" "$subj" "$status"
	else
		printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%d:%s\n' "$1" "$startdate" "$enddate" "$subj" "$issuer" "$sha1fp" "$md5fp" "$retcode" "$status"
	fi
}


# vim: set ts=4:
