#! /usr/bin/env python3

import datetime
from OpenSSL.crypto import load_crl
from OpenSSL.crypto import FILETYPE_PEM
from docopt import docopt

help = """Easy-rsa Doctor
This tool is intended to diagnose an index.txt file generated by easy-rsa (2 and 3).
This file keeps all the information on certificates up to date.
Without arguments, it displays all valid certificates.
You can use this tool to display expired certificates, revoked certificates, or certificates to expire in the next n days.
You can also analyse this file against a PEM formated CRL to find which certificates it revokes, and find missing revoked certificates in the CRL.

Usage:
  easy_rsa_helper.py -i <index> [-w <days>]
  easy_rsa_helper.py -i <index> [-r]
  easy_rsa_helper.py -i <index> [-e]
  easy_rsa_helper.py -i <index> --crl <crl> [-m]

Options:
  -h --help                             Help
  -i <index>, --index <index>           index.txt file
  -w <days>, --watch <days>             Watch certificates to renew in the next n days
  -e, --expired                         Print only expired certificates
  -r, --revoked                         Print only revoked certificates
  --crl <crl>                           Print revoked certificates in a PEM file
  -m, --missing                         Find missing revoked certificates in PEM file

"""


def decode_date(date):
        try:
                date = datetime.datetime.strptime(date, "%y%m%d%H%M%SZ")
                return date
        except:  # Happens when certificate revoked in CRL and not in index.txt
                return u"No valid date found"


def print_cn(cert):
        # Two formats exist for DN : cn_only and org
        # Let's try both
        try:  # org format
                return cert[5].split("=")[5][:-13].strip()
        except IndexError:  # maybe cn_only format ?
            try:
                return cert[5].split("=")[1].strip()
            except:
                print(u"Format error.")
                exit(1)


def parse_crl():
        rev_list = []
        with open(arguments["--crl"]) as f:
                pem = f.read()
                crl = load_crl(FILETYPE_PEM, pem)
                revoked = crl.get_revoked()
                for r in revoked:
                        rev_list.append(r.get_serial().decode("utf-8"))
        with open(arguments["--index"], "r") as index:
                certifs = index.readlines()
                for cert in certifs:
                        cert = cert.split("\t")
                        if cert[3] in rev_list and not arguments["--missing"]:
                                print(u"Revoked certificate found !")
                                if cert[0] != "R":
                                        print(u"Not revoked in index.txt file")
                                print(u"CN : {0}".format(print_cn(cert)))
                                print(u"Revoked {0}\n".format(decode_date(cert[2])))
                        elif cert[0] == "R" and arguments["--missing"]:
                                if cert[3] not in rev_list:
                                        print(u"Missing revoked certificate found !")
                                        print(u"CN : {0}".format(print_cn(cert)))
                                        print(u"Revoked {0}\n".format(decode_date(cert[2])))


def parse_index():
        with open(arguments["--index"], "r") as index:
                certifs = index.readlines()
                for cert in certifs:
                        cert = cert.split("\t")
                        if cert[0] == "V" and not arguments["--revoked"]:  # If certificate not revoked
                                if decode_date(cert[1]) > datetime.datetime.now() and not arguments["--expired"] and not arguments["--watch"]:  # If not expired
                                        delta = decode_date(cert[1]) - datetime.datetime.now()
                                        print(u"CN : {0}".format(print_cn(cert)))
                                        print(u"Expires after {0}".format(decode_date(cert[1])))
                                        print(u"Expires in {0} days\n".format(delta.days))
                                elif arguments["--watch"]:
                                    try:
                                        days = int(arguments["--watch"])
                                    except:
                                        print(u"You did not entered a correct number of days. Exiting...")
                                        exit(1)
                                    if decode_date(cert[1]) > datetime.datetime.now() and decode_date(cert[1]) < datetime.datetime.now() + datetime.timedelta(days):
                                        delta = decode_date(cert[1]) - datetime.datetime.now()
                                        print(u"CN : {0}".format(print_cn(cert)))
                                        print(u"Expires after {0}".format(decode_date(cert[1])))
                                        print(u"Expires in {0} days\n".format(delta.days))
                                elif decode_date(cert[1]) < datetime.datetime.now() and arguments["--expired"]:  # If expired
                                        print(u"Certificat expired :")
                                        print(u"CN : {0}".format(print_cn(cert)))
                                        print(u"Expired since {0}\n".format(decode_date(cert[1])))
                        elif cert[0] == "R" and arguments["--revoked"]:
                                print(u"Certificat revoked :")
                                print(u"CN : {0}".format(print_cn(cert)))
                                print(u"Revoked {0}\n".format(decode_date(cert[2])))

if __name__ == "__main__":
        arguments = docopt(help)
        if arguments["--crl"]:
                parse_crl()
        else:
                parse_index()
