#!/bin/bash
#
# Monitor the IDP certificate for an OCA instance, including optionally the expiry of future certificates which can be chained with the current one.
#
# Usage: $0 <oca-instance> <property>
#
# <instance> := <oca-instance-name>
# <property> := ^(next-)?((start|end)date|serial|subject|issuer|fingerprint|(issuer|subject)_hash|modulus|ocsp(id|_uri)|certificate)$
#

function usage  {
	cat <<-EOD >&2

		OCA SAML IDP Certificate montoring

		    $0 <oca-instance-name> <certificate-property>

		    Where <certificate-property> can be one of:

		      * startdate          - The certificate's notBefore date as unix epoch seconds
		      * enddate            - The certificate's notAfter date as unix epoch seconds
		      * serial             - The certificate's serial number
		      * subject            - The certificate's subject in the form /A=B/D=C/ (eg /C=AU/ST=...)
		      * fingerprint        - The certificate's fingerprint
		      * issuer             - The certificate's issuer in the form /A=B/D=C/ (eg /C=AU/ST=...)
		      * keylen             - The certificate's key length (in bits)
		      * sigalgo            - The certificate's signature algorithm (eg md5WithRSAEncryption, sha1With.., sha256With... etc)
		      * subject_hash       - The certificate's openssl subject hash value
		      * issuer_hash        - The certificate's openssl issuer hash value
		      * modulus            - The certificate's public key modulus of the certificate
		      * ocspid             - The certificate's OCSP responder ID(s)
		      * ocsp_uri           - The certificate's OCSP responder URI(s)
		      * certificate        - The actual PEM encoded X.509 certificate
		      * time-to-expiry     - The number of seconds remaining until the enddate (0 for expired OR not yet valid)

		      * next-<property>    - The <property> of the next (future) IDP certificate

		      * idp-valid-until    - The enddate of either the next (future) certificate if available else the enddate of the current certificate (unix epoch)

	EOD
	exit 1
}

[ $# -ne 2 ] && usage

# Name our arguments for sanity
INSTANCE="$1"
PROPERTY="$2"

# Normalise oca_ prefix by stripping it
INSTANCE="${INSTANCE#oca_}"

if [[ ! "${INSTANCE}" =~ ^[-_A-Za-z0-9]+$ ]]; then
        echo "Illegal instance name '${INSTANCE}'" >&2
        echo -n "ZBX_NOTSUPPORTED"
        exit 2;
fi

if [[ "${PROPERTY}" =~ idp-valid-(for|until) ]]; then

    if [[ "${PROPERTY}" = 'idp-valid-until' ]]; then
        WHAT="UNIX_TIMESTAMP(IFNULL(next.NotAfter, current.NotAfter))"
    else
        WHAT="IFNULL(
            GREATEST(
                0,
                IF( -- If is current
                    (UTC_TIMESTAMP() BETWEEN current.NotBefore AND current.NotAfter),
                    UNIX_TIMESTAMP(IFNULL(next.NotAfter, current.NotAfter)),
                    0
                ) - UNIX_TIMESTAMP()),
        0)"
    fi

    mysql -NB --database "oca_${INSTANCE}" -e "
        SELECT ${WHAT}
         FROM certificates current
	      WHERE current.IsDeleted <> 1                             -- Must not be soft deleted
              LEFT JOIN certificates next ON next.id = (
                    SELECT candidate.id
                      FROM certificates candidate
                     WHERE candidate.Type = current.Type               -- Must be of the same type
                           AND candidate.ObjectId <=> current.ObjectId -- And for the same object (could be NULL)
                           AND candidate.NotBefore <= current.NotAfter -- Must be valid before or at expiry of current
                           AND candidate.NotAfter > current.NotAfter   -- Must expire after current expires
                     ORDER BY candidate.NotAfter DESC LIMIT 1          -- If there are multiple we want the one that expires last
                )
        WHERE current.Type = 'samlidp'
        ORDER BY IF( (UTC_TIMESTAMP()     BETWEEN current.NotBefore AND current.NotAfter), current.NotBefore, '0000-00-00 00:00:00') DESC, -- Oldest current items first
                 IF( (UTC_TIMESTAMP() NOT BETWEEN current.NotBefore AND current.NotAfter), current.NotAfter, '0000-00-00 00:00:00') DESC   -- If nothing is current newest (expired) item last
        LIMIT 1
    " 2>/dev/null
    exit $?

elif  [[ "${PROPERTY}" =~ ^next- ]]; then
    # Retrieve the *next* PEM (filter through openssl to ensure we don't have any inline keys!)
    if ! PEM=$(
        mysql -NB --database "oca_${INSTANCE}" --raw -e "
            SELECT (SELECT next.Certificate
                          FROM certificates next
                         WHERE current.Type = next.Type               -- Must be of the same type
                               AND current.ObjectId <=> next.ObjectId -- And for the same object (could be NULL)
                               AND current.NotAfter <= next.NotBefore -- Must be valid before or at expiry of current
                               AND current.NotAfter <= next.NotAfter  -- Must expire after current (ie extends the period)
			       AND next.IsDeleted <> 1                -- Must not be soft deleted
                         ORDER BY next.NotAfter DESC LIMIT 1          -- If there are multiple we want the one that expires last
                    )  AS NextPEM
            FROM certificates current
            WHERE Type = 'samlidp'
                  AND UTC_TIMESTAMP() BETWEEN NotBefore AND NotAfter
            ORDER BY NotBefore ASC
            LIMIT 1
        " 2>/dev/null \
          | openssl x509 2>/dev/null
    ); then
        echo -n 'ZBX_NOTSUPPORTED'
        exit 2
    fi
else
    # Retrieve the PEM (filter through openssl to ensure we don't have any inline keys!)
    if ! PEM=$(
        mysql -NB --database "oca_${INSTANCE}" --raw -e "SELECT Certificate FROM certificates WHERE Type = 'samlidp' AND UTC_TIMESTAMP() BETWEEN NotBefore AND NotAfter AND IsDeleted <> 1 ORDER BY NotBefore ASC LIMIT 1" 2>/dev/null \
          | openssl x509 2>/dev/null
    ); then
        echo -n 'ZBX_NOTSUPPORTED'
        exit 2
    fi
fi

# Strip next- we've handled this in the PEM retrieval
PROPERTY="${PROPERTY#next-}"

if [[ "${PROPERTY}" == 'certificate' ]]; then

    echo "${PEM}"

elif [[ "${PROPERTY}" == 'keylen' ]]; then

    openssl x509 -noout -text <<<"${PEM}" 2>/dev/null | sed -nre 's/^[ \t]+Public-Key: \(([0-9]+) bit\)[ \t]*$/\1/  p'

elif [[ "${PROPERTY}" == 'sigalgo' ]]; then

    openssl x509 -noout -text <<<"${PEM}" 2>/dev/null | awk '/Signature Algorithm: /{print $3; exit;}'

elif [[ "${PROPERTY}" == 'time-to-expiry' ]]; then

    openssl x509 -noout -dates <<<"${PEM}" \
      | perl -MDate::Parse -ne '
          s/^.*?= *//g; push(@ts, str2time($_));
          END {
              my $now=time();
              printf("%s\n", (($now < $ts[0] || $now > $ts[1]) ? 0 : ($ts[1] - $now)))
          }
        '

elif [[ "${PROPERTY}" =~ ^((start|end)date|serial|subject|issuer|fingerprint|(issuer|subject)_hash|modulus|ocsp(id|_uri))$ ]]; then

    RESULT="$(openssl x509 -noout "-${PROPERTY}" <<<"${PEM}" 2>/dev/null | sed -re 's/^[^=]+= *//g')"

    if [[ "${PROPERTY}" =~ ^(start|end)date$ ]]; then
        RESULT="$(date --date="$RESULT" +%s)"
    elif [[ "${PROPERTY}" == 'modulus' ]]; then
        RESULT="$(sha256sum <<<"${RESULT}" | cut -d\  -f1)"
    fi

    echo "${RESULT}"

else
    usage "Unknown/Unsupported certificate property '${PROPERTY}'"
    exit 1
fi
