From 48ec7fbe108fd41e406458f802100420907dc702 Mon Sep 17 00:00:00 2001 From: Ashwini Oruganti Date: Thu, 17 Nov 2016 10:46:01 +0530 Subject: [PATCH 01/13] Define ServerNameList extension constructs --- tls/_constructs.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/tls/_constructs.py b/tls/_constructs.py index 1ce3676..01f86d3 100644 --- a/tls/_constructs.py +++ b/tls/_constructs.py @@ -6,7 +6,8 @@ from functools import partial -from construct import Array, Bytes, Pass, Struct, UBInt16, UBInt32, UBInt8 +from construct import (Array, Bytes, PascalString, Pass, Struct, Switch, + UBInt16, UBInt32, UBInt8) from tls._common import enums @@ -67,6 +68,30 @@ Array(lambda ctx: ctx.length, UBInt8("compression_methods")) ) +ServerName = Struct( + "server_name_list", + # TODO: Use an EnumSwitch here. + EnumClass(UBInt8("name_type"), enums.NameType), + Switch( + "name", + lambda ctx: ctx.name_type, + { + enums.NameType.HOST_NAME: PascalString( + "HostName", + length_field=UBInt16("length") + ) + } + ) +) + +HostName = Struct( + "name", + UBInt16("length"), + Bytes("name", lambda ctx: ctx.length), +) + +ServerNameList = TLSPrefixedArray("server_name_list", ServerName) + SignatureAndHashAlgorithm = Struct( "algorithms", EnumClass(UBInt8("hash"), enums.HashAlgorithm), @@ -86,6 +111,7 @@ type_enum=enums.ExtensionType, value_field="data", value_choices={ + enums.ExtensionType.SERVER_NAME: Opaque(ServerName), enums.ExtensionType.SIGNATURE_ALGORITHMS: Opaque( SupportedSignatureAlgorithms ), From 53af807bc6e6250b5cd02e8e2b44a8e948545d21 Mon Sep 17 00:00:00 2001 From: Ashwini Oruganti Date: Thu, 17 Nov 2016 12:15:22 +0530 Subject: [PATCH 02/13] Implmenet constructs for server_name extension --- tls/_constructs.py | 21 +++++++--------- tls/test/test_hello_message.py | 44 +++++++++++++++++++++++++--------- 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/tls/_constructs.py b/tls/_constructs.py index 01f86d3..fab292c 100644 --- a/tls/_constructs.py +++ b/tls/_constructs.py @@ -68,28 +68,23 @@ Array(lambda ctx: ctx.length, UBInt8("compression_methods")) ) +HostName = Struct( + "hostname", + PrefixedBytes("data", UBInt16("length")), +) + ServerName = Struct( - "server_name_list", - # TODO: Use an EnumSwitch here. + "server_name", EnumClass(UBInt8("name_type"), enums.NameType), Switch( "name", lambda ctx: ctx.name_type, { - enums.NameType.HOST_NAME: PascalString( - "HostName", - length_field=UBInt16("length") - ) + enums.NameType.HOST_NAME: HostName } ) ) -HostName = Struct( - "name", - UBInt16("length"), - Bytes("name", lambda ctx: ctx.length), -) - ServerNameList = TLSPrefixedArray("server_name_list", ServerName) SignatureAndHashAlgorithm = Struct( @@ -111,7 +106,7 @@ type_enum=enums.ExtensionType, value_field="data", value_choices={ - enums.ExtensionType.SERVER_NAME: Opaque(ServerName), + enums.ExtensionType.SERVER_NAME: Opaque(ServerNameList), enums.ExtensionType.SIGNATURE_ALGORITHMS: Opaque( SupportedSignatureAlgorithms ), diff --git a/tls/test/test_hello_message.py b/tls/test/test_hello_message.py index 8b5efc6..32ed1a6 100644 --- a/tls/test/test_hello_message.py +++ b/tls/test/test_hello_message.py @@ -20,7 +20,7 @@ class TestClientHello(object): Tests for the parsing of ClientHello messages. """ - no_extensions_packet = ( + common_client_hello_data = ( b'\x03\x00' # client_version b'\x01\x02\x03\x04' # random.gmt_unix_time b'0123456789012345678901234567' # random.random_bytes @@ -30,6 +30,9 @@ class TestClientHello(object): b'\x00\x6B' # cipher_suites b'\x01' # compression_methods length b'\x00' # compression_methods + ) + + no_extensions_packet = common_client_hello_data + ( b'\x00\x00' # extensions.length b'' # extensions.extension_type b'' # extensions.extensions @@ -51,16 +54,7 @@ class TestClientHello(object): b'\x02\x02' # SHA1, DSA ) - extensions_packet = ( - b'\x03\x00' # client_version - b'\x01\x02\x03\x04' # random.gmt_unix_time - b'0123456789012345678901234567' # random.random_bytes - b'\x00' # session_id.length - b'' # session_id.session_id - b'\x00\x02' # cipher_suites length - b'\x00\x6B' # cipher_suites - b'\x01' # compression_methods length - b'\x00' # compression_methods + extensions_packet = common_client_hello_data + ( b'\x00\x1a' # extensions length ) + supported_signature_list_extension_data @@ -93,6 +87,19 @@ class TestClientHello(object): b'' # extensions.extensions.extension_data ) + server_name_extension_data = ( + b'\x00\x00' # Extension Type: Server Name + b'\x00\x0e' # Length + b'\x00\x0c' # Server Name Indication Length + b'\x00' # Server Name Type: host_name + b'\x00\x09' # Length of hostname data + b'localhost' + ) + + client_hello_packet_with_server_name_ext = common_client_hello_data + ( + b'\x00\x12' + ) + server_name_extension_data + def test_resumption_no_extensions(self): """ :func:`parse_client_hello` returns an instance of @@ -173,6 +180,19 @@ def test_as_bytes_client_hello_cipher_suites(self): record.as_bytes() assert exc_info.value.args == ('invalid object', 0) + def test_client_hello_with_server_name_extension(self): + """ + :py:func:`tls.hello_message.ClientHello` parses a packet with a + server_name extension + """ + record = ClientHello.from_bytes(self.client_hello_packet_with_server_name_ext) + assert len(record.extensions) == 1 + assert record.extensions[0].type == enums.ExtensionType.SERVER_NAME + assert len(record.extensions[0].data) == 1 + server_name_list = record.extensions[0].data + assert server_name_list[0].name_type == enums.NameType.HOST_NAME + assert server_name_list[0].name.data == 'localhost' + class TestServerHello(object): """ @@ -258,3 +278,5 @@ def test_as_bytes_with_extensions(self): """ record = ServerHello.from_bytes(self.extensions_packet) assert record.as_bytes() == self.extensions_packet + + From 83c26ccd758d21b8c001f01f71c605bd6d674cfa Mon Sep 17 00:00:00 2001 From: Ashwini Oruganti Date: Thu, 17 Nov 2016 12:25:16 +0530 Subject: [PATCH 03/13] a B for py3 --- tls/test/test_hello_message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tls/test/test_hello_message.py b/tls/test/test_hello_message.py index 32ed1a6..d840909 100644 --- a/tls/test/test_hello_message.py +++ b/tls/test/test_hello_message.py @@ -191,7 +191,7 @@ def test_client_hello_with_server_name_extension(self): assert len(record.extensions[0].data) == 1 server_name_list = record.extensions[0].data assert server_name_list[0].name_type == enums.NameType.HOST_NAME - assert server_name_list[0].name.data == 'localhost' + assert server_name_list[0].name.data == b'localhost' class TestServerHello(object): From 07534f89ffad464fe11098460d1fe67d4c67834b Mon Sep 17 00:00:00 2001 From: Ashwini Oruganti Date: Thu, 17 Nov 2016 12:30:17 +0530 Subject: [PATCH 04/13] Remove unused import --- tls/_constructs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tls/_constructs.py b/tls/_constructs.py index fab292c..4923805 100644 --- a/tls/_constructs.py +++ b/tls/_constructs.py @@ -6,8 +6,8 @@ from functools import partial -from construct import (Array, Bytes, PascalString, Pass, Struct, Switch, - UBInt16, UBInt32, UBInt8) +from construct import (Array, Bytes, Pass, Struct, Switch, UBInt16, UBInt32, + UBInt8) from tls._common import enums From 4e4541caa4c01956fa0c35dc1030a204b6186929 Mon Sep 17 00:00:00 2001 From: Ashwini Oruganti Date: Thu, 17 Nov 2016 12:31:33 +0530 Subject: [PATCH 05/13] Extra whitespace --- tls/test/test_hello_message.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tls/test/test_hello_message.py b/tls/test/test_hello_message.py index d840909..86dcd37 100644 --- a/tls/test/test_hello_message.py +++ b/tls/test/test_hello_message.py @@ -278,5 +278,3 @@ def test_as_bytes_with_extensions(self): """ record = ServerHello.from_bytes(self.extensions_packet) assert record.as_bytes() == self.extensions_packet - - From 3562b89d1112e727d88fd329656f4c8c9ff1002e Mon Sep 17 00:00:00 2001 From: Ashwini Oruganti Date: Thu, 17 Nov 2016 13:27:04 +0530 Subject: [PATCH 06/13] pep8 --- tls/test/test_hello_message.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tls/test/test_hello_message.py b/tls/test/test_hello_message.py index 86dcd37..8462ff7 100644 --- a/tls/test/test_hello_message.py +++ b/tls/test/test_hello_message.py @@ -185,7 +185,9 @@ def test_client_hello_with_server_name_extension(self): :py:func:`tls.hello_message.ClientHello` parses a packet with a server_name extension """ - record = ClientHello.from_bytes(self.client_hello_packet_with_server_name_ext) + record = ClientHello.from_bytes( + self.client_hello_packet_with_server_name_ext + ) assert len(record.extensions) == 1 assert record.extensions[0].type == enums.ExtensionType.SERVER_NAME assert len(record.extensions[0].data) == 1 From 7f0488c75d9ccfc31af76ae741037f041a0d11a7 Mon Sep 17 00:00:00 2001 From: Ashwini Oruganti Date: Thu, 17 Nov 2016 23:11:19 +0530 Subject: [PATCH 07/13] HostName doesn't need to be a struct --- tls/_constructs.py | 5 +---- tls/test/test_hello_message.py | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/tls/_constructs.py b/tls/_constructs.py index 4923805..4da2757 100644 --- a/tls/_constructs.py +++ b/tls/_constructs.py @@ -68,10 +68,7 @@ Array(lambda ctx: ctx.length, UBInt8("compression_methods")) ) -HostName = Struct( - "hostname", - PrefixedBytes("data", UBInt16("length")), -) +HostName = PrefixedBytes("hostname", UBInt16("length")) ServerName = Struct( "server_name", diff --git a/tls/test/test_hello_message.py b/tls/test/test_hello_message.py index 8462ff7..5d91b78 100644 --- a/tls/test/test_hello_message.py +++ b/tls/test/test_hello_message.py @@ -193,7 +193,7 @@ def test_client_hello_with_server_name_extension(self): assert len(record.extensions[0].data) == 1 server_name_list = record.extensions[0].data assert server_name_list[0].name_type == enums.NameType.HOST_NAME - assert server_name_list[0].name.data == b'localhost' + assert server_name_list[0].name == b'localhost' class TestServerHello(object): From f83661802b7cb24188fdfd7c9794771dea44ce56 Mon Sep 17 00:00:00 2001 From: Ashwini Oruganti Date: Thu, 17 Nov 2016 23:23:47 +0530 Subject: [PATCH 08/13] Add a failing test for server_name extension's presence in ServerHello --- tls/test/test_hello_message.py | 38 ++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/tls/test/test_hello_message.py b/tls/test/test_hello_message.py index 5d91b78..7749912 100644 --- a/tls/test/test_hello_message.py +++ b/tls/test/test_hello_message.py @@ -200,7 +200,7 @@ class TestServerHello(object): """ Tests for the parsing of ServerHello messages. """ - no_extensions_packet = ( + common_server_hello_data = ( b'\x03\x00' # server_version b'\x01\x02\x03\x04' # random.gmt_unix_time b'0123456789012345678901234567' # random.random_bytes @@ -208,6 +208,9 @@ class TestServerHello(object): b'01234567890123456789012345678901' # session_id b'\x00\x6B' # cipher_suite b'\x00' # compression_method + ) + + no_extensions_packet = common_server_hello_data + ( b'\x00\x00' # extensions.length b'' # extensions.extension_type b'' # extensions.extensions @@ -229,14 +232,7 @@ class TestServerHello(object): b'\x02\x02' # SHA1, DSA ) - extensions_packet = ( - b'\x03\x00' # server_version - b'\x01\x02\x03\x04' # random.gmt_unix_time - b'0123456789012345678901234567' # random.random_bytes - b'\x20' # session_id.length - b'01234567890123456789012345678901' # session_id - b'\x00\x6B' # cipher_suite - b'\x00' # compression_method + extensions_packet = common_server_hello_data + ( b'\x00\x1a' # extensions length ) + supported_signature_list_extension_data @@ -280,3 +276,27 @@ def test_as_bytes_with_extensions(self): """ record = ServerHello.from_bytes(self.extensions_packet) assert record.as_bytes() == self.extensions_packet + + def test_server_hello_fails_with_server_name_extension(self): + """ + :py:func:`tls.hello_message.ServerHello` does not parse a packet + with a server_name extension, and raises an error. + """ + server_name_extension_data = ( + b'\x00\x00' # Extension Type: Server Name + b'\x00\x0e' # Length + b'\x00\x0c' # Server Name Indication Length + b'\x00' # Server Name Type: host_name + b'\x00\x09' # Length of hostname data + b'localhost' + ) + + server_hello_with_server_name_ext_packet = self.common_server_hello_data + ( + b'\x00\x12' + ) + server_name_extension_data + + with pytest.raises(ValidationError) as exc_info: + ServerHello.from_bytes( + server_hello_with_server_name_ext_packet + ) + assert exc_info.value.args == ('invalid object', 0) From 4538855e9bbeaf8909e02a0846d0e7351729c9e3 Mon Sep 17 00:00:00 2001 From: Ashwini Oruganti Date: Fri, 18 Nov 2016 00:38:39 +0530 Subject: [PATCH 09/13] Correctly validate extensions that should go in client hello vs. server hello. Also fix the bug where signature_algorithm extension was parsed and tested with serverhello. --- tls/exceptions.py | 4 ++++ tls/hello_message.py | 26 ++++++++++++++++++++++++++ tls/test/test_hello_message.py | 27 +++++++++------------------ tls/test/test_message.py | 8 ++++---- 4 files changed, 43 insertions(+), 22 deletions(-) diff --git a/tls/exceptions.py b/tls/exceptions.py index 5803a27..417cbe4 100644 --- a/tls/exceptions.py +++ b/tls/exceptions.py @@ -7,3 +7,7 @@ class UnsupportedCipherException(Exception): pass + + +class UnsupportedExtensionException(Exception): + pass diff --git a/tls/hello_message.py b/tls/hello_message.py index 23d55ae..1f2e6e4 100644 --- a/tls/hello_message.py +++ b/tls/hello_message.py @@ -12,6 +12,8 @@ from tls._common import enums +from tls.exceptions import UnsupportedExtensionException + @attr.s class ProtocolVersion(object): @@ -31,6 +33,15 @@ class Random(object): random_bytes = attr.ib() +@attr.s +class ServerName(object): + """ + An object representing a ServerName struct. + """ + name_type = attr.ib() + name = attr.ib() + + @attr.s class ClientHello(object): """ @@ -42,6 +53,14 @@ class ClientHello(object): cipher_suites = attr.ib() compression_methods = attr.ib() extensions = attr.ib() + allowed_extensions = frozenset([ + enums.ExtensionType.SERVER_NAME, + enums.ExtensionType.MAX_FRAGMENT_LENGTH, + enums.ExtensionType.CLIENT_CERTIFICATE_URL, + enums.ExtensionType.SIGNATURE_ALGORITHMS, + # XXX Incomplete list, needs to be populated as we implement more + # extensions. + ]) def as_bytes(self): return _constructs.ClientHello.build( @@ -72,6 +91,9 @@ def from_bytes(cls, bytes): :return: ClientHello object. """ construct = _constructs.ClientHello.parse(bytes) + if any(extension.type not in cls.allowed_extensions + for extension in construct.extensions): + raise UnsupportedExtensionException return ClientHello( client_version=ProtocolVersion( major=construct.version.major, @@ -101,6 +123,7 @@ class ServerHello(object): cipher_suite = attr.ib() compression_method = attr.ib() extensions = attr.ib() + allowed_extensions = frozenset([]) def as_bytes(self): return _constructs.ServerHello.build( @@ -128,6 +151,9 @@ def from_bytes(cls, bytes): :return: ServerHello object. """ construct = _constructs.ServerHello.parse(bytes) + if any(extension.type not in cls.allowed_extensions + for extension in construct.extensions): + raise UnsupportedExtensionException return ServerHello( server_version=ProtocolVersion( major=construct.version.major, diff --git a/tls/test/test_hello_message.py b/tls/test/test_hello_message.py index 7749912..b7798fd 100644 --- a/tls/test/test_hello_message.py +++ b/tls/test/test_hello_message.py @@ -12,6 +12,8 @@ from tls.ciphersuites import CipherSuites +from tls.exceptions import UnsupportedExtensionException + from tls.hello_message import ClientHello, ServerHello @@ -254,14 +256,11 @@ def test_parse_server_hello(self): def test_parse_server_hello_extensions(self): """ - :func:`parse_server_hello` returns an instance of :class:`ServerHello` - with extensions, when the extension bytes are present in the input. + :func:`parse_server_hello` fails to parse when + SIGNATURE_ALGORITHMS extention bytes are present in the packet """ - record = ServerHello.from_bytes(self.extensions_packet) - assert len(record.extensions) == 1 - assert (record.extensions[0].type == - enums.ExtensionType.SIGNATURE_ALGORITHMS) - assert len(record.extensions[0].data) == 10 + with pytest.raises(UnsupportedExtensionException): + ServerHello.from_bytes(self.extensions_packet) def test_as_bytes_no_extensions(self): """ @@ -270,13 +269,6 @@ def test_as_bytes_no_extensions(self): record = ServerHello.from_bytes(self.no_extensions_packet) assert record.as_bytes() == self.no_extensions_packet - def test_as_bytes_with_extensions(self): - """ - :func:`ServerHello.as_bytes` returns the bytes it was created with - """ - record = ServerHello.from_bytes(self.extensions_packet) - assert record.as_bytes() == self.extensions_packet - def test_server_hello_fails_with_server_name_extension(self): """ :py:func:`tls.hello_message.ServerHello` does not parse a packet @@ -291,12 +283,11 @@ def test_server_hello_fails_with_server_name_extension(self): b'localhost' ) - server_hello_with_server_name_ext_packet = self.common_server_hello_data + ( + server_hello_packet = self.common_server_hello_data + ( b'\x00\x12' ) + server_name_extension_data - with pytest.raises(ValidationError) as exc_info: + with pytest.raises(UnsupportedExtensionException): ServerHello.from_bytes( - server_hello_with_server_name_ext_packet + server_hello_packet ) - assert exc_info.value.args == ('invalid object', 0) diff --git a/tls/test/test_message.py b/tls/test/test_message.py index ab8e16f..c58b93d 100644 --- a/tls/test/test_message.py +++ b/tls/test/test_message.py @@ -329,12 +329,12 @@ class TestHandshakeStructParsing(object): b'01234567890123456789012345678901' # session_id b'\x00\x6B' # cipher_suite b'\x00' # compression_method - b'\x00\x1a' # extensions length - ) + supported_signature_list_extension_data + b'\x00\x00' # extensions.length + ) server_hello_handshake_packet = ( b'\x02' # msg_type - b'\x00\x00\x62' # body length + b'\x00\x00\x48' # body length ) + server_hello_packet certificate_packet = ( @@ -398,7 +398,7 @@ def test_parse_server_hello_in_handshake(self): record = Handshake.from_bytes(self.server_hello_handshake_packet) assert isinstance(record, Handshake) assert record.msg_type == enums.HandshakeType.SERVER_HELLO - assert record.length == 98 + assert record.length == 72 assert isinstance(record.body, ServerHello) def test_parse_certificate_request_in_handshake(self): From 01b6f2a78c252ecb5e999c87b1143ae6db6c8d27 Mon Sep 17 00:00:00 2001 From: Ashwini Oruganti Date: Sun, 20 Nov 2016 13:33:47 +0530 Subject: [PATCH 10/13] Sometimes, I can spell. --- tls/test/test_hello_message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tls/test/test_hello_message.py b/tls/test/test_hello_message.py index b7798fd..11686a8 100644 --- a/tls/test/test_hello_message.py +++ b/tls/test/test_hello_message.py @@ -257,7 +257,7 @@ def test_parse_server_hello(self): def test_parse_server_hello_extensions(self): """ :func:`parse_server_hello` fails to parse when - SIGNATURE_ALGORITHMS extention bytes are present in the packet + SIGNATURE_ALGORITHMS extension bytes are present in the packet """ with pytest.raises(UnsupportedExtensionException): ServerHello.from_bytes(self.extensions_packet) From 8ac01243338fc79a4c90e7f88419762b9a8582c8 Mon Sep 17 00:00:00 2001 From: Ashwini Oruganti Date: Mon, 21 Nov 2016 00:47:54 +0530 Subject: [PATCH 11/13] Add a check for unsupported extensions in ServerHello.as_bytes() --- tls/hello_message.py | 4 ++++ tls/test/test_hello_message.py | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/tls/hello_message.py b/tls/hello_message.py index 1f2e6e4..d94fb46 100644 --- a/tls/hello_message.py +++ b/tls/hello_message.py @@ -126,6 +126,10 @@ class ServerHello(object): allowed_extensions = frozenset([]) def as_bytes(self): + if any(extension.type not in self.allowed_extensions + for extension in self.extensions): + raise UnsupportedExtensionException + return _constructs.ServerHello.build( Container( version=Container(major=self.server_version.major, diff --git a/tls/test/test_hello_message.py b/tls/test/test_hello_message.py index 11686a8..37378dd 100644 --- a/tls/test/test_hello_message.py +++ b/tls/test/test_hello_message.py @@ -8,6 +8,8 @@ import pytest +from tls import _constructs + from tls._common import enums from tls.ciphersuites import CipherSuites @@ -291,3 +293,24 @@ def test_server_hello_fails_with_server_name_extension(self): ServerHello.from_bytes( server_hello_packet ) + + def test_as_bytes_unsupported_extension(self): + """ + :func:`ServerHello.as_bytes` fails to serialize a message that + contains invalid extensions + """ + extensions_data = ( + b'\x00\x12' + b'\x00\x00' # Extension Type: Server Name + b'\x00\x0e' # Length + b'\x00\x0c' # Server Name Indication Length + b'\x00' # Server Name Type: host_name + b'\x00\x09' # Length of hostname data + b'localhost' + ) + + record = ServerHello.from_bytes(self.no_extensions_packet) + extensions = _constructs.Extensions.parse(extensions_data) + record.extensions = extensions + with pytest.raises(UnsupportedExtensionException): + record.as_bytes() From 5de80f8abd1c7a99fcf07cdb960665eaa08e06f5 Mon Sep 17 00:00:00 2001 From: Ashwini Oruganti Date: Mon, 21 Nov 2016 14:06:33 +0530 Subject: [PATCH 12/13] Test ClientHello.from_bytes() with an unsupported extension --- tls/test/test_hello_message.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tls/test/test_hello_message.py b/tls/test/test_hello_message.py index 37378dd..88bb351 100644 --- a/tls/test/test_hello_message.py +++ b/tls/test/test_hello_message.py @@ -199,6 +199,26 @@ def test_client_hello_with_server_name_extension(self): assert server_name_list[0].name_type == enums.NameType.HOST_NAME assert server_name_list[0].name == b'localhost' + def test_client_hello_fails_with_unsupported_extension(self): + """ + :py:func:`tls.hello_message.ClientHello` does not parse a packet + with an unsupported extension, and raises an error. + """ + server_certificate_type_extension_data = ( + b'\x00\x14' # Extension Type: Server Certificate Type + b'\x00\x00' # Length + b'' # Data + ) + + client_hello_packet = self.common_client_hello_data + ( + b'\x00\x04' + ) + server_certificate_type_extension_data + + with pytest.raises(UnsupportedExtensionException): + ClientHello.from_bytes( + client_hello_packet + ) + class TestServerHello(object): """ From 7c77cc85cb99784ed5c4e007173613990904977e Mon Sep 17 00:00:00 2001 From: Ashwini Oruganti Date: Mon, 21 Nov 2016 14:17:23 +0530 Subject: [PATCH 13/13] Reject unsupported extensions in ClientHello.as_bytes() --- tls/hello_message.py | 4 ++++ tls/test/test_hello_message.py | 20 +++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/tls/hello_message.py b/tls/hello_message.py index d94fb46..6fa1908 100644 --- a/tls/hello_message.py +++ b/tls/hello_message.py @@ -63,6 +63,10 @@ class ClientHello(object): ]) def as_bytes(self): + if any(extension.type not in self.allowed_extensions + for extension in self.extensions): + raise UnsupportedExtensionException + return _constructs.ClientHello.build( Container( version=Container(major=self.client_version.major, diff --git a/tls/test/test_hello_message.py b/tls/test/test_hello_message.py index 88bb351..1fa7862 100644 --- a/tls/test/test_hello_message.py +++ b/tls/test/test_hello_message.py @@ -199,7 +199,7 @@ def test_client_hello_with_server_name_extension(self): assert server_name_list[0].name_type == enums.NameType.HOST_NAME assert server_name_list[0].name == b'localhost' - def test_client_hello_fails_with_unsupported_extension(self): + def test_hello_from_bytes_with_unsupported_extension(self): """ :py:func:`tls.hello_message.ClientHello` does not parse a packet with an unsupported extension, and raises an error. @@ -219,6 +219,24 @@ def test_client_hello_fails_with_unsupported_extension(self): client_hello_packet ) + def test_as_bytes_unsupported_extension(self): + """ + :func:`ClientHello.as_bytes` fails to serialize a message that + contains invalid extensions + """ + extensions_data = ( + b'\x00\x04' + b'\x00\x14' # Extension Type: Server Certificate Type + b'\x00\x00' # Length + b'' # Data + ) + + record = ClientHello.from_bytes(self.no_extensions_packet) + extensions = _constructs.Extensions.parse(extensions_data) + record.extensions = extensions + with pytest.raises(UnsupportedExtensionException): + record.as_bytes() + class TestServerHello(object): """