aboutsummaryrefslogtreecommitdiffstats
path: root/libmproxy
diff options
context:
space:
mode:
authorMaximilian Hils <git@maximilianhils.com>2015-08-26 05:39:00 +0200
committerMaximilian Hils <git@maximilianhils.com>2015-08-26 05:39:00 +0200
commit3fa65c48dd2880a806985e273b3fa280103e2a7b (patch)
tree7ca2dabb2789462424ff0d0c28e3399d57540345 /libmproxy
parent8ce0de8bed5fbd2c42e7b43ee553e065e1c08a4c (diff)
downloadmitmproxy-3fa65c48dd2880a806985e273b3fa280103e2a7b.tar.gz
mitmproxy-3fa65c48dd2880a806985e273b3fa280103e2a7b.tar.bz2
mitmproxy-3fa65c48dd2880a806985e273b3fa280103e2a7b.zip
manually read tls clienthello [wip]
Diffstat (limited to 'libmproxy')
-rw-r--r--libmproxy/contrib/tls/__init__.py5
-rw-r--r--libmproxy/contrib/tls/_constructs.py213
-rw-r--r--libmproxy/contrib/tls/alert_message.py64
-rw-r--r--libmproxy/contrib/tls/ciphersuites.py343
-rw-r--r--libmproxy/contrib/tls/exceptions.py2
-rw-r--r--libmproxy/contrib/tls/hello_message.py178
-rw-r--r--libmproxy/contrib/tls/message.py313
-rw-r--r--libmproxy/contrib/tls/record.py110
-rw-r--r--libmproxy/contrib/tls/utils.py52
-rw-r--r--libmproxy/protocol2/tls.py44
10 files changed, 1313 insertions, 11 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
diff --git a/libmproxy/protocol2/tls.py b/libmproxy/protocol2/tls.py
index 98c5d603..78372284 100644
--- a/libmproxy/protocol2/tls.py
+++ b/libmproxy/protocol2/tls.py
@@ -1,6 +1,6 @@
from __future__ import (absolute_import, print_function, division)
-import traceback
+from ..contrib.tls._constructs import ClientHello
from netlib import tcp
import netlib.http.http2
@@ -11,16 +11,16 @@ from .layer import Layer
class TlsLayer(Layer):
def __init__(self, ctx, client_tls, server_tls):
+ self.client_sni = None
+ self.client_alpn_protos = None
+
super(TlsLayer, self).__init__(ctx)
self._client_tls = client_tls
self._server_tls = server_tls
- self.client_sni = None
+
self._sni_from_server_change = None
- self.client_alpn_protos = None
self.__server_tls_exception = None
- # foo alpn protos = [netlib.http.http1.HTTP1Protocol.ALPN_PROTO_HTTP1, netlib.http.http2.HTTP2Protocol.ALPN_PROTO_H2], # TODO: read this from client_conn first
-
def __call__(self):
"""
The strategy for establishing SSL is as follows:
@@ -45,6 +45,28 @@ class TlsLayer(Layer):
https://www.openssl.org/docs/ssl/SSL_CTX_set_cert_cb.html
- The original mitmproxy issue is https://github.com/mitmproxy/mitmproxy/issues/427
"""
+ import struct
+
+ # Read all records that contain the initial Client Hello message.
+ client_hello = ""
+ client_hello_size = 1
+ offset = 0
+ while len(client_hello) < client_hello_size:
+ record_header = self.client_conn.rfile.peek(offset+5)[offset:]
+ record_size = struct.unpack("!H", record_header[3:])[0] + 5
+ record_body = self.client_conn.rfile.peek(offset+record_size)[offset+5:]
+ client_hello += record_body
+ offset += record_size
+ client_hello_size = struct.unpack("!I", '\x00' + client_hello[1:4])[0] + 4
+
+ client_hello = ClientHello.parse(client_hello[4:])
+
+ for extension in client_hello.extensions:
+ if extension.type == 0x00:
+ host = extension.server_names[0].name
+ if extension.type == 0x10:
+ alpn = extension.alpn_protocols
+
client_tls_requires_server_cert = (
self._client_tls and self._server_tls and not self.config.no_upstream_cert
)
@@ -60,12 +82,12 @@ class TlsLayer(Layer):
def connect(self):
if not self.server_conn:
self.ctx.connect()
- if self._server_tls and not self._server_tls_established:
+ if self._server_tls and not self.server_conn.tls_established:
self._establish_tls_with_server()
def reconnect(self):
self.ctx.reconnect()
- if self._server_tls and not self._server_tls_established:
+ if self._server_tls and not self.server_conn.tls_established:
self._establish_tls_with_server()
def set_server(self, address, server_tls, sni, depth=1):
@@ -75,10 +97,6 @@ class TlsLayer(Layer):
self._server_tls = server_tls
@property
- def _server_tls_established(self):
- return self.server_conn and self.server_conn.tls_established
-
- @property
def sni_for_upstream_connection(self):
if self._sni_from_server_change is False:
return None
@@ -138,6 +156,10 @@ class TlsLayer(Layer):
connection.set_context(new_context)
def __handle_alpn_select(self, conn_, options):
+ """
+ Once the client signals the alternate protocols it supports,
+ we reconnect upstream with the same list and pass the server's choice down to the client.
+ """
# TODO: change to something meaningful?
# alpn_preference = netlib.http.http1.HTTP1Protocol.ALPN_PROTO_HTTP1
alpn_preference = netlib.http.http2.HTTP2Protocol.ALPN_PROTO_H2