"""
Secure Sockets Layer / Transport Layer Security.
"""
#
# 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.
#

from pypacker import pypacker, triggerlist

import logging
import struct

# avoid references for performance reasons
unpack = struct.unpack

unpack_be_h = struct.Struct(">H").unpack

logger = logging.getLogger("pypacker")

# 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(pypacker.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)
		self._fragmented = False

		# records is the only header so it's ok to avoid lazy dissecting
		while offset < dlen:
			record_len = unpack_be_h(buf[offset + 3: offset + 5])[0]

			if offset + record_len > dlen:
				# TODO: handle fragmentation
				# idea: design _dissect() in a way which allows continuation
				# at any given start-point, eg start of a record-entry
				# Later call to a "reassemble([pkt1, pkt2])" can re-use
				# _dissect to add all other records
				logger.warn("possible fragmentation found")
				self._fragmented = True
				# non-parsed data becomes body content
				dlen = offset
				break
			record = Record(buf[offset: offset + 5 + record_len])
			records.append(record)
			offset += len(record)
		# logger.debug("adding records, dlen/offset at end: %d %d" % (dlen, offset))
		self.records.extend(records)
		return dlen


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

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

	def __dissect(self, buf):
		# logger.debug("parsing TLSRecord")
		# TODO: check for different handshages
		self._init_handler(buf[0], buf[5:])
		return 5


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


#
# Record contents
#
class HandshakeHello(pypacker.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),
	)

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

		while offset < buflen:
			ext_content_len = unpack(buf[offset + 2: offset + 4])
			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(buf[offset_extlen : offset_extlen+2])
		self._init_triggerlist("extensions", buf[offset_extlen + 2:], self.__parse_extension)


class HandshakeData(pypacker.Packet):
	pass


class ChangeCipherSpec(pypacker.Packet):
	pass


class ApplicationData(pypacker.Packet):
	pass


pypacker.Packet.load_handler(Record,
	{
		RECORD_TLS_CHG_CIPHERSPEC: ChangeCipherSpec,
		RECORD_TLS_HANDSHAKE_HELLO: HandshakeHello,
		RECORD_TLS_HANDSHAKE_DATA: HandshakeData,
		RECORD_TLS_APPDATA: ApplicationData
	}
)
