diff options
Diffstat (limited to 'libmproxy/contrib/tls')
-rw-r--r-- | libmproxy/contrib/tls/__init__.py | 5 | ||||
-rw-r--r-- | libmproxy/contrib/tls/_constructs.py | 213 | ||||
-rw-r--r-- | libmproxy/contrib/tls/alert_message.py | 64 | ||||
-rw-r--r-- | libmproxy/contrib/tls/ciphersuites.py | 343 | ||||
-rw-r--r-- | libmproxy/contrib/tls/exceptions.py | 2 | ||||
-rw-r--r-- | libmproxy/contrib/tls/hello_message.py | 178 | ||||
-rw-r--r-- | libmproxy/contrib/tls/message.py | 313 | ||||
-rw-r--r-- | libmproxy/contrib/tls/record.py | 110 | ||||
-rw-r--r-- | libmproxy/contrib/tls/utils.py | 52 |
9 files changed, 1280 insertions, 0 deletions
diff --git a/libmproxy/contrib/tls/__init__.py b/libmproxy/contrib/tls/__init__.py new file mode 100644 index 00000000..4b540884 --- /dev/null +++ b/libmproxy/contrib/tls/__init__.py @@ -0,0 +1,5 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function diff --git a/libmproxy/contrib/tls/_constructs.py b/libmproxy/contrib/tls/_constructs.py new file mode 100644 index 00000000..49661efb --- /dev/null +++ b/libmproxy/contrib/tls/_constructs.py @@ -0,0 +1,213 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from construct import Array, Bytes, Struct, UBInt16, UBInt32, UBInt8, PascalString, Embed, \ + TunnelAdapter, GreedyRange, Switch + +from .utils import UBInt24 + +ProtocolVersion = Struct( + "version", + UBInt8("major"), + UBInt8("minor"), +) + +TLSPlaintext = Struct( + "TLSPlaintext", + UBInt8("type"), + ProtocolVersion, + UBInt16("length"), # TODO: Reject packets with length > 2 ** 14 + Bytes("fragment", lambda ctx: ctx.length), +) + +TLSCompressed = Struct( + "TLSCompressed", + UBInt8("type"), + ProtocolVersion, + UBInt16("length"), # TODO: Reject packets with length > 2 ** 14 + 1024 + Bytes("fragment", lambda ctx: ctx.length), +) + +TLSCiphertext = Struct( + "TLSCiphertext", + UBInt8("type"), + ProtocolVersion, + UBInt16("length"), # TODO: Reject packets with length > 2 ** 14 + 2048 + Bytes("fragment", lambda ctx: ctx.length), +) + +Random = Struct( + "random", + UBInt32("gmt_unix_time"), + Bytes("random_bytes", 28), +) + +SessionID = Struct( + "session_id", + UBInt8("length"), + Bytes("session_id", lambda ctx: ctx.length), +) + +CipherSuites = Struct( + "cipher_suites", + UBInt16("length"), # TODO: Reject packets of length 0 + Array(lambda ctx: ctx.length // 2, UBInt16("cipher_suites")), +) + +CompressionMethods = Struct( + "compression_methods", + UBInt8("length"), # TODO: Reject packets of length 0 + Array(lambda ctx: ctx.length, UBInt8("compression_methods")), +) + +ServerName = Struct( + "", + UBInt8("type"), + PascalString("name", length_field=UBInt16("length")), +) + +SNIExtension = Struct( + "", + TunnelAdapter( + PascalString("server_names", length_field=UBInt16("length")), + TunnelAdapter( + PascalString("", length_field=UBInt16("length")), + GreedyRange(ServerName) + ), + ), +) + +ALPNExtension = Struct( + "", + TunnelAdapter( + PascalString("alpn_protocols", length_field=UBInt16("length")), + TunnelAdapter( + PascalString("", length_field=UBInt16("length")), + GreedyRange(PascalString("name")) + ), + ), +) + +UnknownExtension = Struct( + "", + PascalString("bytes", length_field=UBInt16("extensions_length")) +) + +Extension = Struct( + "Extension", + UBInt16("type"), + Embed( + Switch( + "data", lambda ctx: ctx.type, + { + 0x00: SNIExtension, + 0x10: ALPNExtension + }, + default=UnknownExtension + ) + ) +) + +extensions = TunnelAdapter( + PascalString("extensions", length_field=UBInt16("extensions_length")), + GreedyRange(Extension) +) + +ClientHello = Struct( + "ClientHello", + ProtocolVersion, + Random, + SessionID, + CipherSuites, + CompressionMethods, + extensions, +) + +ServerHello = Struct( + "ServerHello", + ProtocolVersion, + Random, + SessionID, + Bytes("cipher_suite", 2), + UBInt8("compression_method"), + extensions, +) + +ClientCertificateType = Struct( + "certificate_types", + UBInt8("length"), # TODO: Reject packets of length 0 + Array(lambda ctx: ctx.length, UBInt8("certificate_types")), +) + +SignatureAndHashAlgorithm = Struct( + "algorithms", + UBInt8("hash"), + UBInt8("signature"), +) + +SupportedSignatureAlgorithms = Struct( + "supported_signature_algorithms", + UBInt16("supported_signature_algorithms_length"), + # TODO: Reject packets of length 0 + Array( + lambda ctx: ctx.supported_signature_algorithms_length / 2, + SignatureAndHashAlgorithm, + ), +) + +DistinguishedName = Struct( + "certificate_authorities", + UBInt16("length"), + Bytes("certificate_authorities", lambda ctx: ctx.length), +) + +CertificateRequest = Struct( + "CertificateRequest", + ClientCertificateType, + SupportedSignatureAlgorithms, + DistinguishedName, +) + +ServerDHParams = Struct( + "ServerDHParams", + UBInt16("dh_p_length"), + Bytes("dh_p", lambda ctx: ctx.dh_p_length), + UBInt16("dh_g_length"), + Bytes("dh_g", lambda ctx: ctx.dh_g_length), + UBInt16("dh_Ys_length"), + Bytes("dh_Ys", lambda ctx: ctx.dh_Ys_length), +) + +PreMasterSecret = Struct( + "pre_master_secret", + ProtocolVersion, + Bytes("random_bytes", 46), +) + +ASN1Cert = Struct( + "ASN1Cert", + UBInt32("length"), # TODO: Reject packets with length not in 1..2^24-1 + Bytes("asn1_cert", lambda ctx: ctx.length), +) + +Certificate = Struct( + "Certificate", # TODO: Reject packets with length > 2 ** 24 - 1 + UBInt32("certificates_length"), + Bytes("certificates_bytes", lambda ctx: ctx.certificates_length), +) + +Handshake = Struct( + "Handshake", + UBInt8("msg_type"), + UBInt24("length"), # TODO: Reject packets with length > 2 ** 24 + Bytes("body", lambda ctx: ctx.length), +) + +Alert = Struct( + "Alert", + UBInt8("level"), + UBInt8("description"), +) diff --git a/libmproxy/contrib/tls/alert_message.py b/libmproxy/contrib/tls/alert_message.py new file mode 100644 index 00000000..ef02f56d --- /dev/null +++ b/libmproxy/contrib/tls/alert_message.py @@ -0,0 +1,64 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from enum import Enum + +from characteristic import attributes + +from . import _constructs + + +class AlertLevel(Enum): + WARNING = 1 + FATAL = 2 + + +class AlertDescription(Enum): + CLOSE_NOTIFY = 0 + UNEXPECTED_MESSAGE = 10 + BAD_RECORD_MAC = 20 + DECRYPTION_FAILED_RESERVED = 21 + RECORD_OVERFLOW = 22 + DECOMPRESSION_FAILURE = 30 + HANDSHAKE_FAILURE = 40 + NO_CERTIFICATE_RESERVED = 41 + BAD_CERTIFICATE = 42 + UNSUPPORTED_CERTIFICATE = 43 + CERTIFICATE_REVOKED = 44 + CERTIFICATE_EXPIRED = 45 + CERTIFICATE_UNKNOWN = 46 + ILLEGAL_PARAMETER = 47 + UNKNOWN_CA = 48 + ACCESS_DENIED = 49 + DECODE_ERROR = 50 + DECRYPT_ERROR = 51 + EXPORT_RESTRICTION_RESERVED = 60 + PROTOCOL_VERSION = 70 + INSUFFICIENT_SECURITY = 71 + INTERNAL_ERROR = 80 + USER_CANCELED = 90 + NO_RENEGOTIATION = 100 + UNSUPPORTED_EXTENSION = 110 + + +@attributes(['level', 'description']) +class Alert(object): + """ + An object representing an Alert message. + """ + @classmethod + def from_bytes(cls, bytes): + """ + Parse an ``Alert`` struct. + + :param bytes: the bytes representing the input. + :return: Alert object. + """ + construct = _constructs.Alert.parse(bytes) + return cls( + level=AlertLevel(construct.level), + description=AlertDescription(construct.description) + ) diff --git a/libmproxy/contrib/tls/ciphersuites.py b/libmproxy/contrib/tls/ciphersuites.py new file mode 100644 index 00000000..86298f80 --- /dev/null +++ b/libmproxy/contrib/tls/ciphersuites.py @@ -0,0 +1,343 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from enum import Enum + +from .exceptions import UnsupportedCipherException + + +class CipherSuites(Enum): + TLS_NULL_WITH_NULL_NULL = 0x0000 + TLS_RSA_WITH_NULL_MD5 = 0x0001 + TLS_RSA_WITH_NULL_SHA = 0x0002 + TLS_RSA_EXPORT_WITH_RC4_40_MD5 = 0x0003 + TLS_RSA_WITH_RC4_128_MD5 = 0x0004 + TLS_RSA_WITH_RC4_128_SHA = 0x0005 + TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 = 0x0006 + TLS_RSA_WITH_IDEA_CBC_SHA = 0x0007 + TLS_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x0008 + TLS_RSA_WITH_DES_CBC_SHA = 0x0009 + TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000A + TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x000B + TLS_DH_DSS_WITH_DES_CBC_SHA = 0x000C + TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA = 0x000D + TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x000E + TLS_DH_RSA_WITH_DES_CBC_SHA = 0x000F + TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = 0x0010 + TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x0011 + TLS_DHE_DSS_WITH_DES_CBC_SHA = 0x0012 + TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = 0x0013 + TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x0014 + TLS_DHE_RSA_WITH_DES_CBC_SHA = 0x0015 + TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x0016 + TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 = 0x0017 + TLS_DH_anon_WITH_RC4_128_MD5 = 0x0018 + TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA = 0x0019 + TLS_DH_anon_WITH_DES_CBC_SHA = 0x001A + TLS_DH_anon_WITH_3DES_EDE_CBC_SHA = 0x001B + TLS_KRB5_WITH_DES_CBC_SHA = 0x001E + TLS_KRB5_WITH_3DES_EDE_CBC_SHA = 0x001F + TLS_KRB5_WITH_RC4_128_SHA = 0x0020 + TLS_KRB5_WITH_IDEA_CBC_SHA = 0x0021 + TLS_KRB5_WITH_DES_CBC_MD5 = 0x0022 + TLS_KRB5_WITH_3DES_EDE_CBC_MD5 = 0x0023 + TLS_KRB5_WITH_RC4_128_MD5 = 0x0024 + TLS_KRB5_WITH_IDEA_CBC_MD5 = 0x0025 + TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA = 0x0026 + TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA = 0x0027 + TLS_KRB5_EXPORT_WITH_RC4_40_SHA = 0x0028 + TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 = 0x0029 + TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5 = 0x002A + TLS_KRB5_EXPORT_WITH_RC4_40_MD5 = 0x002B + TLS_PSK_WITH_NULL_SHA = 0x002C + TLS_DHE_PSK_WITH_NULL_SHA = 0x002D + TLS_RSA_PSK_WITH_NULL_SHA = 0x002E + TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F + TLS_DH_DSS_WITH_AES_128_CBC_SHA = 0x0030 + TLS_DH_RSA_WITH_AES_128_CBC_SHA = 0x0031 + TLS_DHE_DSS_WITH_AES_128_CBC_SHA = 0x0032 + TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033 + TLS_DH_anon_WITH_AES_128_CBC_SHA = 0x0034 + TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035 + TLS_DH_DSS_WITH_AES_256_CBC_SHA = 0x0036 + TLS_DH_RSA_WITH_AES_256_CBC_SHA = 0x0037 + TLS_DHE_DSS_WITH_AES_256_CBC_SHA = 0x0038 + TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039 + TLS_DH_anon_WITH_AES_256_CBC_SHA = 0x003A + TLS_RSA_WITH_NULL_SHA256 = 0x003B + TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C + TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D + TLS_DH_DSS_WITH_AES_128_CBC_SHA256 = 0x003E + TLS_DH_RSA_WITH_AES_128_CBC_SHA256 = 0x003F + TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 = 0x0040 + TLS_RSA_WITH_CAMELLIA_128_CBC_SHA = 0x0041 + TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA = 0x0042 + TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA = 0x0043 + TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA = 0x0044 + TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA = 0x0045 + TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA = 0x0046 + TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067 + TLS_DH_DSS_WITH_AES_256_CBC_SHA256 = 0x0068 + TLS_DH_RSA_WITH_AES_256_CBC_SHA256 = 0x0069 + TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 = 0x006A + TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B + TLS_DH_anon_WITH_AES_128_CBC_SHA256 = 0x006C + TLS_DH_anon_WITH_AES_256_CBC_SHA256 = 0x006D + TLS_RSA_WITH_CAMELLIA_256_CBC_SHA = 0x0084 + TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA = 0x0085 + TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA = 0x0086 + TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA = 0x0087 + TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA = 0x0088 + TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA = 0x0089 + TLS_PSK_WITH_RC4_128_SHA = 0x008A + TLS_PSK_WITH_3DES_EDE_CBC_SHA = 0x008B + TLS_PSK_WITH_AES_128_CBC_SHA = 0x008C + TLS_PSK_WITH_AES_256_CBC_SHA = 0x008D + TLS_DHE_PSK_WITH_RC4_128_SHA = 0x008E + TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA = 0x008F + TLS_DHE_PSK_WITH_AES_128_CBC_SHA = 0x0090 + TLS_DHE_PSK_WITH_AES_256_CBC_SHA = 0x0091 + TLS_RSA_PSK_WITH_RC4_128_SHA = 0x0092 + TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA = 0x0093 + TLS_RSA_PSK_WITH_AES_128_CBC_SHA = 0x0094 + TLS_RSA_PSK_WITH_AES_256_CBC_SHA = 0x0095 + TLS_RSA_WITH_SEED_CBC_SHA = 0x0096 + TLS_DH_DSS_WITH_SEED_CBC_SHA = 0x0097 + TLS_DH_RSA_WITH_SEED_CBC_SHA = 0x0098 + TLS_DHE_DSS_WITH_SEED_CBC_SHA = 0x0099 + TLS_DHE_RSA_WITH_SEED_CBC_SHA = 0x009A + TLS_DH_anon_WITH_SEED_CBC_SHA = 0x009B + TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009C + TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009D + TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E + TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F + TLS_DH_RSA_WITH_AES_128_GCM_SHA256 = 0x00A0 + TLS_DH_RSA_WITH_AES_256_GCM_SHA384 = 0x00A1 + TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 = 0x00A2 + TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 = 0x00A3 + TLS_DH_DSS_WITH_AES_128_GCM_SHA256 = 0x00A4 + TLS_DH_DSS_WITH_AES_256_GCM_SHA384 = 0x00A5 + TLS_DH_anon_WITH_AES_128_GCM_SHA256 = 0x00A6 + TLS_DH_anon_WITH_AES_256_GCM_SHA384 = 0x00A7 + TLS_PSK_WITH_AES_128_GCM_SHA256 = 0x00A8 + TLS_PSK_WITH_AES_256_GCM_SHA384 = 0x00A9 + TLS_DHE_PSK_WITH_AES_128_GCM_SHA256 = 0x00AA + TLS_DHE_PSK_WITH_AES_256_GCM_SHA384 = 0x00AB + TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 = 0x00AC + TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 = 0x00AD + TLS_PSK_WITH_AES_128_CBC_SHA256 = 0x00AE + TLS_PSK_WITH_AES_256_CBC_SHA384 = 0x00AF + TLS_PSK_WITH_NULL_SHA256 = 0x00B0 + TLS_PSK_WITH_NULL_SHA384 = 0x00B1 + TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 = 0x00B2 + TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 = 0x00B3 + TLS_DHE_PSK_WITH_NULL_SHA256 = 0x00B4 + TLS_DHE_PSK_WITH_NULL_SHA384 = 0x00B5 + TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 = 0x00B6 + TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 = 0x00B7 + TLS_RSA_PSK_WITH_NULL_SHA256 = 0x00B8 + TLS_RSA_PSK_WITH_NULL_SHA384 = 0x00B9 + TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BA + TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BB + TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BC + TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BD + TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BE + TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BF + TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C0 + TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C1 + TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C2 + TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C3 + TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C4 + TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C5 + TLS_EMPTY_RENEGOTIATION_INFO_SCSV = 0x00FF + TLS_ECDH_ECDSA_WITH_NULL_SHA = 0xC001 + TLS_ECDH_ECDSA_WITH_RC4_128_SHA = 0xC002 + TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xC003 + TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA = 0xC004 + TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA = 0xC005 + TLS_ECDHE_ECDSA_WITH_NULL_SHA = 0xC006 + TLS_ECDHE_ECDSA_WITH_RC4_128_SHA = 0xC007 + TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xC008 + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009 + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A + TLS_ECDH_RSA_WITH_NULL_SHA = 0xC00B + TLS_ECDH_RSA_WITH_RC4_128_SHA = 0xC00C + TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA = 0xC00D + TLS_ECDH_RSA_WITH_AES_128_CBC_SHA = 0xC00E + TLS_ECDH_RSA_WITH_AES_256_CBC_SHA = 0xC00F + TLS_ECDHE_RSA_WITH_NULL_SHA = 0xC010 + TLS_ECDHE_RSA_WITH_RC4_128_SHA = 0xC011 + TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA = 0xC012 + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013 + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014 + TLS_ECDH_anon_WITH_NULL_SHA = 0xC015 + TLS_ECDH_anon_WITH_RC4_128_SHA = 0xC016 + TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA = 0xC017 + TLS_ECDH_anon_WITH_AES_128_CBC_SHA = 0xC018 + TLS_ECDH_anon_WITH_AES_256_CBC_SHA = 0xC019 + TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA = 0xC01A + TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA = 0xC01B + TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA = 0xC01C + TLS_SRP_SHA_WITH_AES_128_CBC_SHA = 0xC01D + TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA = 0xC01E + TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA = 0xC01F + TLS_SRP_SHA_WITH_AES_256_CBC_SHA = 0xC020 + TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA = 0xC021 + TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA = 0xC022 + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023 + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024 + TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC025 + TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC026 + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027 + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028 + TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 = 0xC029 + TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 = 0xC02A + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C + TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02D + TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02E + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030 + TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 = 0xC031 + TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 = 0xC032 + TLS_ECDHE_PSK_WITH_RC4_128_SHA = 0xC033 + TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA = 0xC034 + TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA = 0xC035 + TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA = 0xC036 + TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 = 0xC037 + TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 = 0xC038 + TLS_ECDHE_PSK_WITH_NULL_SHA = 0xC039 + TLS_ECDHE_PSK_WITH_NULL_SHA256 = 0xC03A + TLS_ECDHE_PSK_WITH_NULL_SHA384 = 0xC03B + TLS_RSA_WITH_ARIA_128_CBC_SHA256 = 0xC03C + TLS_RSA_WITH_ARIA_256_CBC_SHA384 = 0xC03D + TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256 = 0xC03E + TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384 = 0xC03F + TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256 = 0xC040 + TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384 = 0xC041 + TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256 = 0xC042 + TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384 = 0xC043 + TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256 = 0xC044 + TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384 = 0xC045 + TLS_DH_anon_WITH_ARIA_128_CBC_SHA256 = 0xC046 + TLS_DH_anon_WITH_ARIA_256_CBC_SHA384 = 0xC047 + TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256 = 0xC048 + TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384 = 0xC049 + TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256 = 0xC04A + TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384 = 0xC04B + TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256 = 0xC04C + TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384 = 0xC04D + TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256 = 0xC04E + TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384 = 0xC04F + TLS_RSA_WITH_ARIA_128_GCM_SHA256 = 0xC050 + TLS_RSA_WITH_ARIA_256_GCM_SHA384 = 0xC051 + TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256 = 0xC052 + TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384 = 0xC053 + TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256 = 0xC054 + TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384 = 0xC055 + TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256 = 0xC056 + TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384 = 0xC057 + TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256 = 0xC058 + TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384 = 0xC059 + TLS_DH_anon_WITH_ARIA_128_GCM_SHA256 = 0xC05A + TLS_DH_anon_WITH_ARIA_256_GCM_SHA384 = 0xC05B + TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256 = 0xC05C + TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384 = 0xC05D + TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256 = 0xC05E + TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384 = 0xC05F + TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256 = 0xC060 + TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384 = 0xC061 + TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256 = 0xC062 + TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384 = 0xC063 + TLS_PSK_WITH_ARIA_128_CBC_SHA256 = 0xC064 + TLS_PSK_WITH_ARIA_256_CBC_SHA384 = 0xC065 + TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256 = 0xC066 + TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384 = 0xC067 + TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256 = 0xC068 + TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384 = 0xC069 + TLS_PSK_WITH_ARIA_128_GCM_SHA256 = 0xC06A + TLS_PSK_WITH_ARIA_256_GCM_SHA384 = 0xC06B + TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256 = 0xC06C + TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384 = 0xC06D + TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256 = 0xC06E + TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384 = 0xC06F + TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256 = 0xC070 + TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384 = 0xC071 + TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 = 0xC072 + TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 = 0xC073 + TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 = 0xC074 + TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 = 0xC075 + TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0xC076 + TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 = 0xC077 + TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0xC078 + TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 = 0xC079 + TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC07A + TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC07B + TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC07C + TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC07D + TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC07E + TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC07F + TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256 = 0xC080 + TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384 = 0xC081 + TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256 = 0xC082 + TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384 = 0xC083 + TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256 = 0xC084 + TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384 = 0xC085 + TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC086 + TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC087 + TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC088 + TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC089 + TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC08A + TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC08B + TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC08C + TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC08D + TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 = 0xC08E + TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 = 0xC08F + TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256 = 0xC090 + TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384 = 0xC091 + TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 = 0xC092 + TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 = 0xC093 + TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256 = 0xC094 + TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384 = 0xC095 + TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 = 0xC096 + TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 = 0xC097 + TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 = 0xC098 + TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 = 0xC099 + TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 = 0xC09A + TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 = 0xC09B + TLS_RSA_WITH_AES_128_CCM = 0xC09C + TLS_RSA_WITH_AES_256_CCM = 0xC09D + TLS_DHE_RSA_WITH_AES_128_CCM = 0xC09E + TLS_DHE_RSA_WITH_AES_256_CCM = 0xC09F + TLS_RSA_WITH_AES_128_CCM_8 = 0xC0A0 + TLS_RSA_WITH_AES_256_CCM_8 = 0xC0A1 + TLS_DHE_RSA_WITH_AES_128_CCM_8 = 0xC0A2 + TLS_DHE_RSA_WITH_AES_256_CCM_8 = 0xC0A3 + TLS_PSK_WITH_AES_128_CCM = 0xC0A4 + TLS_PSK_WITH_AES_256_CCM = 0xC0A5 + TLS_DHE_PSK_WITH_AES_128_CCM = 0xC0A6 + TLS_DHE_PSK_WITH_AES_256_CCM = 0xC0A7 + TLS_PSK_WITH_AES_128_CCM_8 = 0xC0A8 + TLS_PSK_WITH_AES_256_CCM_8 = 0xC0A9 + TLS_PSK_DHE_WITH_AES_128_CCM_8 = 0xC0AA + TLS_PSK_DHE_WITH_AES_256_CCM_8 = 0xC0AB + TLS_ECDHE_ECDSA_WITH_AES_128_CCM = 0xC0AC + TLS_ECDHE_ECDSA_WITH_AES_256_CCM = 0xC0AD + TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 = 0xC0AE + TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 = 0xC0AF + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCC14 + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCC13 + + +def select_preferred_ciphersuite(client_supported, server_supported): + for i in server_supported: + assert isinstance(i, CipherSuites) + if i in client_supported: + return i + + raise UnsupportedCipherException( + "Client supported ciphersuites are not supported on the server." + ) diff --git a/libmproxy/contrib/tls/exceptions.py b/libmproxy/contrib/tls/exceptions.py new file mode 100644 index 00000000..75b34d11 --- /dev/null +++ b/libmproxy/contrib/tls/exceptions.py @@ -0,0 +1,2 @@ +class UnsupportedCipherException(Exception): + pass diff --git a/libmproxy/contrib/tls/hello_message.py b/libmproxy/contrib/tls/hello_message.py new file mode 100644 index 00000000..23cd872b --- /dev/null +++ b/libmproxy/contrib/tls/hello_message.py @@ -0,0 +1,178 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from enum import Enum + +from characteristic import attributes + +from construct import Container + +from six import BytesIO + +from . import _constructs + + +@attributes(['major', 'minor']) +class ProtocolVersion(object): + """ + An object representing a ProtocolVersion struct. + """ + + +@attributes(['gmt_unix_time', 'random_bytes']) +class Random(object): + """ + An object representing a Random struct. + """ + + +@attributes(['type', 'data']) +class Extension(object): + """ + An object representing an Extension struct. + """ + def as_bytes(self): + return _constructs.Extension.build(Container( + type=self.type.value, length=len(self.data), data=self.data)) + + +@attributes(['client_version', 'random', 'session_id', 'cipher_suites', + 'compression_methods', 'extensions']) +class ClientHello(object): + """ + An object representing a ClientHello message. + """ + def as_bytes(self): + return _constructs.ClientHello.build( + Container( + version=Container(major=self.client_version.major, + minor=self.client_version.minor), + random=Container( + gmt_unix_time=self.random.gmt_unix_time, + random_bytes=self.random.random_bytes + ), + session_id=Container(length=len(self.session_id), + session_id=self.session_id), + cipher_suites=Container(length=len(self.cipher_suites) * 2, + cipher_suites=self.cipher_suites), + compression_methods=Container( + length=len(self.compression_methods), + compression_methods=self.compression_methods + ), + extensions_length=sum([2 + 2 + len(ext.data) + for ext in self.extensions]), + extensions_bytes=b''.join( + [ext.as_bytes() for ext in self.extensions] + ) + ) + ) + + @classmethod + def from_bytes(cls, bytes): + """ + Parse a ``ClientHello`` struct. + + :param bytes: the bytes representing the input. + :return: ClientHello object. + """ + construct = _constructs.ClientHello.parse(bytes) + # XXX Is there a better way in Construct to parse an array of + # variable-length structs? + extensions = [] + extensions_io = BytesIO(construct.extensions_bytes) + while extensions_io.tell() < construct.extensions_length: + extension_construct = _constructs.Extension.parse_stream( + extensions_io) + extensions.append( + Extension(type=ExtensionType(extension_construct.type), + data=extension_construct.data)) + return ClientHello( + client_version=ProtocolVersion( + major=construct.version.major, + minor=construct.version.minor, + ), + random=Random( + gmt_unix_time=construct.random.gmt_unix_time, + random_bytes=construct.random.random_bytes, + ), + session_id=construct.session_id.session_id, + # TODO: cipher suites should be enums + cipher_suites=construct.cipher_suites.cipher_suites, + compression_methods=( + construct.compression_methods.compression_methods + ), + extensions=extensions, + ) + + +class ExtensionType(Enum): + SIGNATURE_ALGORITHMS = 13 + # XXX: See http://tools.ietf.org/html/rfc5246#ref-TLSEXT + + +@attributes(['server_version', 'random', 'session_id', 'cipher_suite', + 'compression_method', 'extensions']) +class ServerHello(object): + """ + An object representing a ServerHello message. + """ + def as_bytes(self): + return _constructs.ServerHello.build( + Container( + version=Container(major=self.server_version.major, + minor=self.server_version.minor), + random=Container( + gmt_unix_time=self.random.gmt_unix_time, + random_bytes=self.random.random_bytes + ), + session_id=Container(length=len(self.session_id), + session_id=self.session_id), + cipher_suite=self.cipher_suite, + compression_method=self.compression_method.value, + extensions_length=sum([2 + 2 + len(ext.data) + for ext in self.extensions]), + extensions_bytes=b''.join( + [ext.as_bytes() for ext in self.extensions] + ) + ) + ) + + @classmethod + def from_bytes(cls, bytes): + """ + Parse a ``ServerHello`` struct. + + :param bytes: the bytes representing the input. + :return: ServerHello object. + """ + construct = _constructs.ServerHello.parse(bytes) + # XXX: Find a better way to parse extensions + extensions = [] + extensions_io = BytesIO(construct.extensions_bytes) + while extensions_io.tell() < construct.extensions_length: + extension_construct = _constructs.Extension.parse_stream( + extensions_io) + extensions.append( + Extension(type=ExtensionType(extension_construct.type), + data=extension_construct.data)) + return ServerHello( + server_version=ProtocolVersion( + major=construct.version.major, + minor=construct.version.minor, + ), + random=Random( + gmt_unix_time=construct.random.gmt_unix_time, + random_bytes=construct.random.random_bytes, + ), + session_id=construct.session_id.session_id, + cipher_suite=construct.cipher_suite, + compression_method=CompressionMethod(construct.compression_method), + extensions=extensions, + ) + + +class CompressionMethod(Enum): + NULL = 0 diff --git a/libmproxy/contrib/tls/message.py b/libmproxy/contrib/tls/message.py new file mode 100644 index 00000000..b372859f --- /dev/null +++ b/libmproxy/contrib/tls/message.py @@ -0,0 +1,313 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from enum import Enum + +from characteristic import attributes + +from construct import Container + +from six import BytesIO + +from . import _constructs + +from .hello_message import ( + ClientHello, ProtocolVersion, ServerHello +) + + +class ClientCertificateType(Enum): + RSA_SIGN = 1 + DSS_SIGN = 2 + RSA_FIXED_DH = 3 + DSS_FIXED_DH = 4 + RSA_EPHEMERAL_DH_RESERVED = 5 + DSS_EPHEMERAL_DH_RESERVED = 6 + FORTEZZA_DMS_RESERVED = 20 + + +class HashAlgorithm(Enum): + NONE = 0 + MD5 = 1 + SHA1 = 2 + SHA224 = 3 + SHA256 = 4 + SHA384 = 5 + SHA512 = 6 + + +class SignatureAlgorithm(Enum): + ANONYMOUS = 0 + RSA = 1 + DSA = 2 + ECDSA = 3 + + +class HandshakeType(Enum): + HELLO_REQUEST = 0 + CLIENT_HELLO = 1 + SERVER_HELLO = 2 + CERTIFICATE = 11 + SERVER_KEY_EXCHANGE = 12 + CERTIFICATE_REQUEST = 13 + SERVER_HELLO_DONE = 14 + CERTIFICATE_VERIFY = 15 + CLIENT_KEY_EXCHANGE = 16 + FINISHED = 20 + + +class HelloRequest(object): + """ + An object representing a HelloRequest struct. + """ + def as_bytes(self): + return b'' + + +class ServerHelloDone(object): + """ + An object representing a ServerHelloDone struct. + """ + def as_bytes(self): + return b'' + + +@attributes(['certificate_types', 'supported_signature_algorithms', + 'certificate_authorities']) +class CertificateRequest(object): + """ + An object representing a CertificateRequest struct. + """ + def as_bytes(self): + return _constructs.CertificateRequest.build(Container( + certificate_types=Container( + length=len(self.certificate_types), + certificate_types=[cert_type.value + for cert_type in self.certificate_types] + ), + supported_signature_algorithms=Container( + supported_signature_algorithms_length=2 * len( + self.supported_signature_algorithms + ), + algorithms=[Container( + hash=algorithm.hash.value, + signature=algorithm.signature.value, + ) + for algorithm in self.supported_signature_algorithms + ] + ), + certificate_authorities=Container( + length=len(self.certificate_authorities), + certificate_authorities=self.certificate_authorities + ) + )) + + @classmethod + def from_bytes(cls, bytes): + """ + Parse a ``CertificateRequest`` struct. + + :param bytes: the bytes representing the input. + :return: CertificateRequest object. + """ + construct = _constructs.CertificateRequest.parse(bytes) + return cls( + certificate_types=[ + ClientCertificateType(cert_type) + for cert_type in construct.certificate_types.certificate_types + ], + supported_signature_algorithms=[ + SignatureAndHashAlgorithm( + hash=HashAlgorithm(algorithm.hash), + signature=SignatureAlgorithm(algorithm.signature), + ) + for algorithm in ( + construct.supported_signature_algorithms.algorithms + ) + ], + certificate_authorities=( + construct.certificate_authorities.certificate_authorities + ) + ) + + +@attributes(['hash', 'signature']) +class SignatureAndHashAlgorithm(object): + """ + An object representing a SignatureAndHashAlgorithm struct. + """ + + +@attributes(['dh_p', 'dh_g', 'dh_Ys']) +class ServerDHParams(object): + """ + An object representing a ServerDHParams struct. + """ + @classmethod + def from_bytes(cls, bytes): + """ + Parse a ``ServerDHParams`` struct. + + :param bytes: the bytes representing the input. + :return: ServerDHParams object. + """ + construct = _constructs.ServerDHParams.parse(bytes) + return cls( + dh_p=construct.dh_p, + dh_g=construct.dh_g, + dh_Ys=construct.dh_Ys + ) + + +@attributes(['client_version', 'random']) +class PreMasterSecret(object): + """ + An object representing a PreMasterSecret struct. + """ + @classmethod + def from_bytes(cls, bytes): + """ + Parse a ``PreMasterSecret`` struct. + + :param bytes: the bytes representing the input. + :return: CertificateRequest object. + """ + construct = _constructs.PreMasterSecret.parse(bytes) + return cls( + client_version=ProtocolVersion( + major=construct.version.major, + minor=construct.version.minor, + ), + random=construct.random_bytes, + ) + + +@attributes(['asn1_cert']) +class ASN1Cert(object): + """ + An object representing ASN.1 Certificate + """ + def as_bytes(self): + return _constructs.ASN1Cert.build(Container( + length=len(self.asn1_cert), + asn1_cert=self.asn1_cert + )) + + +@attributes(['certificate_list']) +class Certificate(object): + """ + An object representing a Certificate struct. + """ + def as_bytes(self): + return _constructs.Certificate.build(Container( + certificates_length=sum([4 + len(asn1cert.asn1_cert) + for asn1cert in self.certificate_list]), + certificates_bytes=b''.join( + [asn1cert.as_bytes() for asn1cert in self.certificate_list] + ) + + )) + + @classmethod + def from_bytes(cls, bytes): + """ + Parse a ``Certificate`` struct. + + :param bytes: the bytes representing the input. + :return: Certificate object. + """ + construct = _constructs.Certificate.parse(bytes) + # XXX: Find a better way to parse an array of variable-length objects + certificates = [] + certificates_io = BytesIO(construct.certificates_bytes) + + while certificates_io.tell() < construct.certificates_length: + certificate_construct = _constructs.ASN1Cert.parse_stream( + certificates_io + ) + certificates.append( + ASN1Cert(asn1_cert=certificate_construct.asn1_cert) + ) + return cls( + certificate_list=certificates + ) + + +@attributes(['verify_data']) +class Finished(object): + def as_bytes(self): + return self.verify_data + + +@attributes(['msg_type', 'length', 'body']) +class Handshake(object): + """ + An object representing a Handshake struct. + """ + def as_bytes(self): + if self.msg_type in [ + HandshakeType.SERVER_HELLO, HandshakeType.CLIENT_HELLO, + HandshakeType.CERTIFICATE, HandshakeType.CERTIFICATE_REQUEST, + HandshakeType.HELLO_REQUEST, HandshakeType.SERVER_HELLO_DONE, + HandshakeType.FINISHED + ]: + _body_as_bytes = self.body.as_bytes() + else: + _body_as_bytes = b'' + return _constructs.Handshake.build( + Container( + msg_type=self.msg_type.value, + length=self.length, + body=_body_as_bytes + ) + ) + + @classmethod + def from_bytes(cls, bytes): + """ + Parse a ``Handshake`` struct. + + :param bytes: the bytes representing the input. + :return: Handshake object. + """ + construct = _constructs.Handshake.parse(bytes) + return cls( + msg_type=HandshakeType(construct.msg_type), + length=construct.length, + body=cls._get_handshake_message( + HandshakeType(construct.msg_type), construct.body + ), + ) + + @staticmethod + def _get_handshake_message(msg_type, body): + _handshake_message_parser = { + HandshakeType.CLIENT_HELLO: ClientHello.from_bytes, + HandshakeType.SERVER_HELLO: ServerHello.from_bytes, + HandshakeType.CERTIFICATE: Certificate.from_bytes, + # 12: parse_server_key_exchange, + HandshakeType.CERTIFICATE_REQUEST: CertificateRequest.from_bytes, + # 15: parse_certificate_verify, + # 16: parse_client_key_exchange, + } + + try: + if msg_type == HandshakeType.HELLO_REQUEST: + return HelloRequest() + elif msg_type == HandshakeType.SERVER_HELLO_DONE: + return ServerHelloDone() + elif msg_type == HandshakeType.FINISHED: + return Finished(verify_data=body) + elif msg_type in [HandshakeType.SERVER_KEY_EXCHANGE, + HandshakeType.CERTIFICATE_VERIFY, + HandshakeType.CLIENT_KEY_EXCHANGE, + ]: + raise NotImplementedError + else: + return _handshake_message_parser[msg_type](body) + except NotImplementedError: + return None # TODO diff --git a/libmproxy/contrib/tls/record.py b/libmproxy/contrib/tls/record.py new file mode 100644 index 00000000..481c93bc --- /dev/null +++ b/libmproxy/contrib/tls/record.py @@ -0,0 +1,110 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from enum import Enum + +from characteristic import attributes + +from construct import Container + +from . import _constructs + + +@attributes(['major', 'minor']) +class ProtocolVersion(object): + """ + An object representing a ProtocolVersion struct. + """ + + +@attributes(['type', 'version', 'fragment']) +class TLSPlaintext(object): + """ + An object representing a TLSPlaintext struct. + """ + def as_bytes(self): + return _constructs.TLSPlaintext.build( + Container( + type=self.type.value, + version=Container(major=self.version.major, + minor=self.version.minor), + length=len(self.fragment), + fragment=self.fragment + ) + ) + + @classmethod + def from_bytes(cls, bytes): + """ + Parse a ``TLSPlaintext`` struct. + + :param bytes: the bytes representing the input. + :return: TLSPlaintext object. + """ + construct = _constructs.TLSPlaintext.parse(bytes) + return cls( + type=ContentType(construct.type), + version=ProtocolVersion( + major=construct.version.major, + minor=construct.version.minor + ), + fragment=construct.fragment + ) + + +@attributes(['type', 'version', 'fragment']) +class TLSCompressed(object): + """ + An object representing a TLSCompressed struct. + """ + @classmethod + def from_bytes(cls, bytes): + """ + Parse a ``TLSCompressed`` struct. + + :param bytes: the bytes representing the input. + :return: TLSCompressed object. + """ + construct = _constructs.TLSCompressed.parse(bytes) + return cls( + type=ContentType(construct.type), + version=ProtocolVersion( + major=construct.version.major, + minor=construct.version.minor + ), + fragment=construct.fragment + ) + + +@attributes(['type', 'version', 'fragment']) +class TLSCiphertext(object): + """ + An object representing a TLSCiphertext struct. + """ + @classmethod + def from_bytes(cls, bytes): + """ + Parse a ``TLSCiphertext`` struct. + + :param bytes: the bytes representing the input. + :return: TLSCiphertext object. + """ + construct = _constructs.TLSCiphertext.parse(bytes) + return cls( + type=ContentType(construct.type), + version=ProtocolVersion( + major=construct.version.major, + minor=construct.version.minor + ), + fragment=construct.fragment + ) + + +class ContentType(Enum): + CHANGE_CIPHER_SPEC = 20 + ALERT = 21 + HANDSHAKE = 22 + APPLICATION_DATA = 23 diff --git a/libmproxy/contrib/tls/utils.py b/libmproxy/contrib/tls/utils.py new file mode 100644 index 00000000..a971af49 --- /dev/null +++ b/libmproxy/contrib/tls/utils.py @@ -0,0 +1,52 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import construct + +import six + + +class _UBInt24(construct.Adapter): + def _encode(self, obj, context): + return ( + six.int2byte((obj & 0xFF0000) >> 16) + + six.int2byte((obj & 0x00FF00) >> 8) + + six.int2byte(obj & 0x0000FF) + ) + + def _decode(self, obj, context): + obj = bytearray(obj) + return (obj[0] << 16 | obj[1] << 8 | obj[2]) + + +def UBInt24(name): # noqa + return _UBInt24(construct.Bytes(name, 3)) + + +def LengthPrefixedArray(subcon, length_field=construct.UBInt8("length")): + """ + An array prefixed by a byte length field. + + In contrast to construct.macros.PrefixedArray, + the length field signifies the number of bytes, not the number of elements. + """ + subcon_with_pos = construct.Struct( + subcon.name, + construct.Embed(subcon), + construct.Anchor("__current_pos") + ) + + return construct.Embed( + construct.Struct( + "", + length_field, + construct.Anchor("__start_pos"), + construct.RepeatUntil( + lambda obj, ctx: obj.__current_pos == ctx.__start_pos + getattr(ctx, length_field.name), + subcon_with_pos + ), + ) + )
\ No newline at end of file |