"""
Secure Sockets Layer / Transport Layer Security.
"""
import logging

from packetracer import packetracer, triggerlist
from packetracer.structcbs import unpack_H, unpack_I

#
# Note from April 2011: cde...@gmail.com added code that parses SSL3/TLS messages
# more in depth.
#
# Jul 2012: afleenor@google.com modified and extended SSL support further.
#

logger = logging.getLogger("packetracer")

# SSLv3/TLS versions
SSL3_V = 0x0300
TLS1_V = 0x0301
TLS11_V = 0x0302
TLS12_V = 0x0303

ssl3_versions_str = {
    SSL3_V: "SSL3",
    TLS1_V: "TLS 1.0",
    TLS11_V: "TLS 1.1",
    TLS12_V: "TLS 1.2"
}

# Alert levels
SSL3_AD_WARNING = 1
SSL3_AD_FATAL = 2
alert_level_str = {
    SSL3_AD_WARNING: "SSL3_AD_WARNING",
    SSL3_AD_FATAL: "SSL3_AD_FATAL"
}

# SSL3 alert descriptions
SSL3_AD_CLOSE_NOTIFY = 0
SSL3_AD_UNEXPECTED_MESSAGE = 10  # fatal
SSL3_AD_BAD_RECORD_MAC = 20  # fatal
SSL3_AD_DECOMPRESSION_FAILURE = 30  # fatal
SSL3_AD_HANDSHAKE_FAILURE = 40  # fatal
SSL3_AD_NO_CERTIFICATE = 41
SSL3_AD_BAD_CERTIFICATE = 42
SSL3_AD_UNSUPPORTED_CERTIFICATE = 43
SSL3_AD_CERTIFICATE_REVOKED = 44
SSL3_AD_CERTIFICATE_EXPIRED = 45
SSL3_AD_CERTIFICATE_UNKNOWN = 46
SSL3_AD_ILLEGAL_PARAMETER = 47  # fatal

# TLS1 alert descriptions
TLS1_AD_DECRYPTION_FAILED = 21
TLS1_AD_RECORD_OVERFLOW = 22
TLS1_AD_UNKNOWN_CA = 48  # fatal
TLS1_AD_ACCESS_DENIED = 49  # fatal
TLS1_AD_DECODE_ERROR = 50  # fatal
TLS1_AD_DECRYPT_ERROR = 51
TLS1_AD_EXPORT_RESTRICTION = 60  # fatal
TLS1_AD_PROTOCOL_VERSION = 70  # fatal
TLS1_AD_INSUFFICIENT_SECURITY = 71  # fatal
TLS1_AD_INTERNAL_ERROR = 80  # fatal
TLS1_AD_USER_CANCELLED = 90
TLS1_AD_NO_RENEGOTIATION = 100
# /* codes 110-114 are from RFC3546 */
TLS1_AD_UNSUPPORTED_EXTENSION = 110
TLS1_AD_CERTIFICATE_UNOBTAINABLE = 111
TLS1_AD_UNRECOGNIZED_NAME = 112
TLS1_AD_BAD_CERTIFICATE_STATUS_RESPONSE = 113
TLS1_AD_BAD_CERTIFICATE_HASH_VALUE = 114
TLS1_AD_UNKNOWN_PSK_IDENTITY = 115  # fatal

# Mapping alert types to strings
alert_description_str = {
    SSL3_AD_CLOSE_NOTIFY: "SSL3_AD_CLOSE_NOTIFY",
    SSL3_AD_UNEXPECTED_MESSAGE: "SSL3_AD_UNEXPECTED_MESSAGE",
    SSL3_AD_BAD_RECORD_MAC: "SSL3_AD_BAD_RECORD_MAC",
    SSL3_AD_DECOMPRESSION_FAILURE: "SSL3_AD_DECOMPRESSION_FAILURE",
    SSL3_AD_HANDSHAKE_FAILURE: "SSL3_AD_HANDSHAKE_FAILURE",
    SSL3_AD_NO_CERTIFICATE: "SSL3_AD_NO_CERTIFICATE",
    SSL3_AD_BAD_CERTIFICATE: "SSL3_AD_BAD_CERTIFICATE",
    SSL3_AD_UNSUPPORTED_CERTIFICATE: "SSL3_AD_UNSUPPORTED_CERTIFICATE",
    SSL3_AD_CERTIFICATE_REVOKED: "SSL3_AD_CERTIFICATE_REVOKED",
    SSL3_AD_CERTIFICATE_EXPIRED: "SSL3_AD_CERTIFICATE_EXPIRED",
    SSL3_AD_CERTIFICATE_UNKNOWN: "SSL3_AD_CERTIFICATE_UNKNOWN",
    SSL3_AD_ILLEGAL_PARAMETER: "SSL3_AD_ILLEGAL_PARAMETER",
    TLS1_AD_DECRYPTION_FAILED: "TLS1_AD_DECRYPTION_FAILED",
    TLS1_AD_RECORD_OVERFLOW: "TLS1_AD_RECORD_OVERFLOW",
    TLS1_AD_UNKNOWN_CA: "TLS1_AD_UNKNOWN_CA",
    TLS1_AD_ACCESS_DENIED: "TLS1_AD_ACCESS_DENIED",
    TLS1_AD_DECODE_ERROR: "TLS1_AD_DECODE_ERROR",
    TLS1_AD_DECRYPT_ERROR: "TLS1_AD_DECRYPT_ERROR",
    TLS1_AD_EXPORT_RESTRICTION: "TLS1_AD_EXPORT_RESTRICTION",
    TLS1_AD_PROTOCOL_VERSION: "TLS1_AD_PROTOCOL_VERSION",
    TLS1_AD_INSUFFICIENT_SECURITY: "TLS1_AD_INSUFFICIENT_SECURITY",
    TLS1_AD_INTERNAL_ERROR: "TLS1_AD_INTERNAL_ERROR",
    TLS1_AD_USER_CANCELLED: "TLS1_AD_USER_CANCELLED",
    TLS1_AD_NO_RENEGOTIATION: "TLS1_AD_NO_RENEGOTIATION",
    TLS1_AD_UNSUPPORTED_EXTENSION: "TLS1_AD_UNSUPPORTED_EXTENSION",
    TLS1_AD_CERTIFICATE_UNOBTAINABLE: "TLS1_AD_CERTIFICATE_UNOBTAINABLE",
    TLS1_AD_UNRECOGNIZED_NAME: "TLS1_AD_UNRECOGNIZED_NAME",
    TLS1_AD_BAD_CERTIFICATE_STATUS_RESPONSE: "TLS1_AD_BAD_CERTIFICATE_STATUS_RESPONSE",
    TLS1_AD_BAD_CERTIFICATE_HASH_VALUE: "TLS1_AD_BAD_CERTIFICATE_HASH_VALUE",
    TLS1_AD_UNKNOWN_PSK_IDENTITY: "TLS1_AD_UNKNOWN_PSK_IDENTITY"
}

# Record types
RECORD_TLS_CHG_CIPHERSPEC = 20
RECORD_TLS_ALERT = 21
RECORD_TLS_HANDSHAKE = 22
RECORD_TLS_HANDSHAKE_HELLO = 22 * 10
RECORD_TLS_HANDSHAKE_DATA = 22 * 100
RECORD_TLS_APPDATA = 23

# Handshake types
HNDS_HELLO_REQ = 0
HNDS_HELLO_CLIENT = 1
HNDS_HELLO_SERVER = 2
HNDS_CERTIFICATE = 11
HNDS_SERVER_KEY_EXCHANGE = 12
HNDS_CERTIFICATE_REQ = 13
HNDS_SERVER_HELLO_DONE = 14
HNDS_CERT_VERIFIY = 15
HNDS_CLIENT_KEY_EXCHANGE = 16
HNDS_FINISHED = 20


class SSL(packetracer.Packet):
    __hdr__ = (
        ("records", None, triggerlist.TriggerList),
    )

    def _dissect(self, buf):
        # logger.debug("parsing SSL")
        # parse all records out of message
        # possible types are Client/Sevrer Hello, Change Cipher Spec etc.
        records = []
        offset = 0
        dlen = len(buf)

        # records is the only header so it's ok to avoid lazy dissecting
        while offset < dlen:
            record_len = unpack_H(buf[offset + 3: offset + 5])[0]
            record = Record(buf[offset: offset + 5 + record_len])
            records.append(record)
            offset += 5 + record_len
        # logger.debug("adding %d records", len(records))

        self.records.extend(records)
        return dlen

    def get_cert_length(self):
        """
        return -- length of certificate content if this is a certificate handshake or 0 if not
        """
        for record in self.records:
            try:
                # logger.debug("%r", record)
                if record.type == RECORD_TLS_HANDSHAKE and record.higher_layer.type == HNDS_CERTIFICATE:
                    # logger.debug("type: %X, sub: %X, len: %r",
                    #	record.type, record.handshake.type, record.handshake.len_i)
                    # Handshake Proto -> Cert -> Length
                    return record.higher_layer.len_i - 3
            except Exception as ex:
                logger.warning(ex)
                pass
        return 0


class Extension(packetracer.Packet):
    """
    Handshake protocol extension
    """
    __hdr__ = (
        ("type", "H", 0),
        ("len", "H", 0)
    )


#
# Record contents
#
class Handshake(packetracer.Packet):
    __hdr__ = (
        ("type", "B", 0),
        ("len", "3s", b"\x00" * 3)
    )

    len_i = packetracer.get_property_bytes_num("len", ">I")

    def extract_certificates(self):
        """
        Extracts certificates from a Handshake packet
        Workflow:
            find 1# cert segment(SSL.get_cert_length()) -> collect/assemble until
            cert length collected -> create SSL(tcp_bytes) -> ssl.handshake.extract_certs()
        return -- [cert1, cert2, ...]
        """
        ret = []

        if self.type != HNDS_CERTIFICATE:
            logger.warning("not a certificate handshake: %r", self)
            return ret

        bts_body = self.body_bytes
        certs_len = self.len_i - 3
        # logger.debug("total cert length: %d", certs_len)
        # skip total cert length
        off = 3

        while off < certs_len:
            cert_len = unpack_I(b"\x00" + bts_body[off: off + 3])[0]
            # logger.debug("cert length: %d", cert_len)
            cert_bytes = bts_body[off + 3: off + 3 + cert_len]
            off += 3 + cert_len
            ret.append(cert_bytes)

        return ret


class HandshakeHello(packetracer.Packet):
    __hdr__ = (
        ("type", "B", 0),
        # can't use struct here but:
        # int.from_bytes(len, "big")
        ("len", "3s", b"\x00" * 3),
        ("tlsversion", "H", 0x0301),
        ("random", "32s", b"\x00" * 32),
        ("sid_len", "B", 32),
        # variable length
        ("sid", None, b"A" * 32),
        ("ciphersuite", "H", 0x0035),
        ("compression", "B", 0),
        ("ext_len", "H", 0x0000),
        ("extensions", None, triggerlist.TriggerList),
    )

    len_i = packetracer.get_property_bytes_num("len", ">I")

    @staticmethod
    def __parse_extension(buf):
        extensions = []
        offset = 0
        buflen = len(buf)

        while offset < buflen:
            ext_content_len = unpack_H(buf[offset + 2: offset + 4])[0]
            ext_len = 4 + ext_content_len
            extensions.append(Extension(buf[offset: offset + ext_len]))
            offset += ext_len

        return extensions

    def _dissect(self, buf):
        sid_len = buf[38]
        offset_extlen = 38 + sid_len + 3
        # ext_len = unpack_H(buf[offset_extlen : offset_extlen+2])
        self._init_triggerlist("extensions", buf[offset_extlen + 2:], self.__parse_extension)


class HandshakeData(packetracer.Packet):
    pass


class ChangeCipherSpec(packetracer.Packet):
    pass


class ApplicationData(packetracer.Packet):
    pass


class Record(packetracer.Packet):
    """
    SSLv3 or TLSv1+ Record layer.
    """

    __hdr__ = (
        ("type", "B", 0),
        ("version", "H", 0),
        ("len", "H", 0),
    )

    __handler__ = {
        RECORD_TLS_CHG_CIPHERSPEC: ChangeCipherSpec,
        RECORD_TLS_HANDSHAKE: Handshake,
        RECORD_TLS_HANDSHAKE_HELLO: HandshakeHello,
        RECORD_TLS_HANDSHAKE_DATA: HandshakeData,
        RECORD_TLS_APPDATA: ApplicationData
    }

    def _dissect(self, buf):
        # TODO: check for other handshakes
        # if buf[0] == RECORD_TLS_HANDSHAKE:
        #	logger.debug("got a handshake")
        self._init_handler(buf[0], buf[5:])
        return 5
