#!/usr/bin/env python3


from OpenSSL import SSL, crypto
import socket
from datetime import datetime
import json
import argparse
import subprocess
import re


def get_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("hostname", help="Hostname to connect to")
    parser.add_argument(
        "-p", "--port", type=int, default=443, help="TCP port. Default 443"
    )
    parser.add_argument("--sni", help="Server Name Indication")
    parser.add_argument(
        "-s",
        "--starttls",
        help='If using STARTTLS to start using TLS after connect. \
        For exmaple "--starttls ftp"',
    )
    args = parser.parse_args()
    return args


def get_expiry_days(cert, date_now):
    # Get the Expiry date and time
    not_after = cert.get_notAfter().decode("utf8")[:-1]

    # Convert to datetime object
    not_after = datetime.strptime(not_after, "%Y%m%d%H%M%S")

    # Get days until expiry
    date_diff = (not_after - date_now).days

    return date_diff


def get_common_name(cert):
    # Get the subject into a tuple
    cert_subject = cert.get_subject().get_components()

    # Convert tuple to dict and get the common name
    cert_subject = dict(cert_subject)
    common_name = cert_subject.get(b"CN", cert_subject.get(b"OU"))
    if common_name:
        common_name = common_name.decode("utf8")

    return common_name


def get_subject_alternative_name(cert):
    san = ""
    ext_count = cert.get_extension_count()

    for i in range(0, ext_count):
        ext = cert.get_extension(i)
        if "subjectAltName" in str(ext.get_short_name()):
            san = ext.__str__()

    return san


def get_issuer(cert):
    issuer_obj = cert.get_issuer()
    match_cn = re.search("/CN=(.*)", str(issuer_obj))

    if match_cn and match_cn.group(1):
        issuer = match_cn.group(1)
    else:
        issuer = "n/a"

    return issuer


def NOOSCALL_connect_and_get_cert_chain(hostname=None, port=None):
    # Set context and connect
    ctx = SSL.Context(SSL.TLSv1_2_METHOD)
    s = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
    s.connect((hostname, port))
    s.send(b"GET /")

    # Get the certificate chain
    cert_chain = s.get_peer_cert_chain()

    # Sort so the servers certificate is the first one
    cert_chain.reverse()

    return cert_chain


def connect_and_get_cert_chain(hostname=None, port=None, sni=None, starttls=None):
    """
    Workaround for having issues with connect to with
    the OpenSSL module. So runnig an OS call instead.
    """

    openssl_cmd = [
        "openssl",
        "s_client",
        "-connect {0}:{1}".format(hostname, port),
        "-showcerts",
    ]

    if starttls and starttls != "false":
        openssl_cmd.append("-starttls {0}".format(starttls))

    if sni:
        openssl_cmd.append("-servername {0}".format(sni))
    else:
        openssl_cmd.append("-servername {0}".format(hostname))
    cmd = 'echo -n | {0} 2>/dev/null | \
           sed --quiet "/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p" | \
           sed "s/-----END CERTIFICATE-----/-----END CERTIFICATE-----\\n/"'.format(
        " ".join(openssl_cmd)
    )

    output = subprocess.check_output(cmd, shell=True)
    output = output.decode("utf8")
    output = re.split("\n\n", output)

    cert_chain = [crypto.load_certificate(crypto.FILETYPE_PEM, x) for x in output if x]

    # Sort so the server certificate is the first one
    cert_chain.reverse()

    return cert_chain


def print_certificates_info(hostname=None, port=443, sni=None, starttls=None):
    date_now = datetime.now()

    cert_chain = connect_and_get_cert_chain(
        hostname=hostname, port=port, sni=sni, starttls=starttls
    )

    certificates_info = {}
    server_cert = cert_chain.pop()
    server_cert_common_name = get_common_name(server_cert)
    if not server_cert_common_name:
        server_cert_common_name = hostname

    certificates_info["server_certificate"] = {
        "common_name": server_cert_common_name,
        "expiry_days": get_expiry_days(server_cert, date_now),
    }

    server_cert_subject_alternative_name = get_subject_alternative_name(server_cert)
    if server_cert_subject_alternative_name:
        certificates_info["server_certificate"][
            "subject_alternative_name"
        ] = server_cert_subject_alternative_name

    issuer = get_issuer(server_cert)
    if issuer:
        certificates_info["server_certificate"]["issuer"] = issuer

    certificates_info["intermediate_certificates_discovery"] = []
    certificates_info["intermediate_certificates"] = {}

    for cert in cert_chain:
        common_name = get_common_name(cert)
        common_name_var = common_name.lower().replace(" ", "_")

        certificates_info["intermediate_certificates_discovery"].append(
            {"common_name": common_name, "common_name_var": common_name_var}
        )
        certificates_info["intermediate_certificates"][common_name_var] = {
            "common_name": get_common_name(cert),
            "expiry_days": get_expiry_days(cert, date_now),
        }

        issuer = get_issuer(cert)
        if issuer:
            certificates_info["intermediate_certificates"][common_name_var][
                "issuer"
            ] = issuer

    print(json.dumps({"tls_certificates_info": certificates_info}))


if __name__ == "__main__":
    args = get_args()
    print_certificates_info(
        hostname=args.hostname, port=args.port, sni=args.sni, starttls=args.starttls
    )
