Skip to content

Commit 3d15f05

Browse files
committed
Continuing work and integration
- Focus on Certificate and Server Key Exchange
1 parent 19e03c0 commit 3d15f05

9 files changed

+387
-39
lines changed

Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,8 @@ dist: ## Create source distribution of the python package
138138
python setup.py sdist
139139

140140
upload: clean lint test dist ## Upload test version of python package to test.pypi.org
141-
$(Q) echo "Uploading sdist to test.pypi.org"
142-
twine upload --repository-url https://test.pypi.org/legacy/ dist/*
141+
$(Q) echo "Uploading sdist to legacy.pypi.org"
142+
${PYTHON} -m twine upload --repository tls-packet dist/*
143143

144144
######################################################################
145145
## Utility

tls_packet/auth/eap_tls.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -51,22 +51,31 @@ def tls_length(self) -> int:
5151
return self._tls_length
5252

5353
@property
54-
def tls_data(self) -> int:
54+
def tls_data(self) -> bytes:
5555
return self._tls_data
5656

57+
@property
5758
def length_flag(self) -> bool:
5859
return self._flags & self.LENGTH_FLAG_MASK == self.LENGTH_FLAG_MASK
5960

61+
@property
6062
def more_flag(self) -> bool:
6163
return self._flags & self.MORE_FLAG_MASK == self.MORE_FLAG_MASK
6264

65+
@property
6366
def start_flag(self) -> bool:
6467
return self._flags & self.START_FLAG_MASK == self.START_FLAG_MASK
6568

69+
# Following four are for Scapy decode compatibility
70+
L = length_flag
71+
M = more_flag
72+
S = start_flag
73+
tls_message_len = tls_length
74+
6675
def pack(self, **argv) -> bytes:
6776
buffer = struct.pack("!B", self._flags)
6877

69-
if self.length_flag():
78+
if self.length_flag:
7079
print(f"flags and data: {self._flags:02x}, len: {self._tls_length}", file=sys.stderr)
7180
buffer += struct.pack("!I", self._tls_length) + self._tls_data
7281

tls_packet/auth/security_params.py

+22-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
import copy
1818
import os
19+
1920
from enum import IntEnum
21+
from datetime import datetime
2022
from typing import Optional
2123

2224

@@ -97,7 +99,8 @@ def __init__(self,
9799
client_random: Optional[bytes] = None,
98100
server_random: Optional[bytes] = None):
99101

100-
client_random = client_random or os.urandom(32)
102+
# Desired ones
103+
client_random = client_random or int(datetime.now().timestamp()).to_bytes(4, 'big') + os.urandom(28)
101104

102105
self.prf_algorithm = prf_algorithm
103106
self.bulk_cipher_algorithm = bulk_cipher_algorithm
@@ -114,6 +117,24 @@ def __init__(self,
114117
self.client_random = client_random
115118
self.server_random = server_random
116119

120+
# Ones from ssl book - TODO Get rid of these
121+
# self.active_cipher_suite: Union[CipherSuite, None] = None
122+
# self.proposed_cipher_suite: Union[CipherSuite, None] = None
123+
#
124+
# self.master_key: bytes = b""
125+
# self.connection_id: bytes = b""
126+
# self.challenge: bytes = int(datetime.now().timestamp()).to_bytes(4, 'big') + os.urandom(28)
127+
#
128+
# self.server_public_key: RSAKey = RSAKey()
129+
#
130+
# self.write_sequence_number = 0
131+
# self.read_sequence_number = 0
132+
#
133+
# self.read_key: bytes = b""
134+
# self.write_key: bytes = b""
135+
# self.read_iv: bytes = b""
136+
# self.write_iv: bytes = b""
137+
117138
def copy(self, **kwargs) -> 'SecurityParameters':
118139
""" Create a copy of the security parameters and optionally override any existing values """
119140
dup = copy.copy(self)

tls_packet/auth/tls_certificate.py

+13
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
# -------------------------------------------------------------------------
1616

1717
import struct
18+
import sys
19+
20+
from cryptography.x509 import load_der_x509_certificate
1821
from typing import Union, Optional, List, Tuple, Iterable
1922

2023
from tls_packet.auth.tls_handshake import TLSHandshake, TLSHandshakeType
@@ -158,6 +161,16 @@ def length(self) -> int:
158161
def certificate(self) -> bytes:
159162
return self._data
160163

164+
@property
165+
def x509_certificate(self) -> 'Certificate':
166+
try:
167+
cert = load_der_x509_certificate(self._data)
168+
return cert
169+
170+
except Exception as e:
171+
print(f"Error loading X.509 certificate: {e}", file=sys.stderr)
172+
return None
173+
161174
@staticmethod
162175
def parse(frame: bytes) -> 'ASN_1_Cert':
163176
""" Frame to TLSSessionHello """

tls_packet/auth/tls_client.py

+55-12
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,54 @@ def __init__(self, auth_socket,
5454
# Client random data is 32 bytes long
5555
client_random = random_data or int(datetime.now().timestamp()).to_bytes(4, 'big') + os.urandom(28)
5656

57-
self._security_parameters: SecurityParameters = SecurityParameters().copy(client_random=client_random)
57+
# Keep separate send/receive parameters so we can handle various receive sequences from server
58+
59+
self._receive_security_parameters: SecurityParameters = SecurityParameters().copy(client_random=client_random)
60+
self._send_security_parameters: SecurityParameters = SecurityParameters().copy(client_random=client_random)
61+
62+
63+
# TLS breaks it up into pending and active (Look at state machine and its transitions setting of this)
64+
# it uses the following, so tie the values below into what we save here
65+
# self._k_send = ""
66+
# self._k_recv = ""
67+
#
68+
self._security_parameters = {
69+
'active_tx': SecurityParameters().copy(client_random=client_random),
70+
'active_rx': SecurityParameters().copy(client_random=client_random),
71+
'pending_tx': SecurityParameters().copy(client_random=client_random),
72+
'pending_rx': SecurityParameters().copy(client_random=client_random)
73+
}
74+
# So when we get the server_hello, use the 'pending_send_parameters' to stuff values in and later on during
75+
# server_key_exchange download, use that pending value
76+
#
77+
#
78+
# static void init_protection_parameters( ProtectionParameters *parameters )
79+
# {
80+
# parameters->MAC_secret = NULL;
81+
# parameters->key = NULL;
82+
# parameters->IV = NULL;
83+
# parameters->seq_num = 0;
84+
# parameters->suite = TLS_NULL_WITH_NULL_NULL;
85+
# }
86+
# static void init_parameters( TLSParameters *parameters )
87+
# {
88+
# init_protection_parameters( &parameters->pending_send_parameters );
89+
# init_protection_parameters( &parameters->pending_recv_parameters );
90+
# init_protection_parameters( &parameters->active_send_parameters );
91+
# init_protection_parameters( &parameters->active_recv_parameters );
92+
93+
94+
95+
5896
# self.server_random = None
5997
self.session_id = session_id
6098
self.ciphers = ciphers or get_cipher_suites_by_version(self.tls_version, excluded=("PSK",))
6199
self.extensions = extensions
62100
self.cipher_suite: CipherSuite = None
63-
self.server_certificate = None
101+
102+
# Following are decode from server messages. Any better place for them
103+
self.server_certificates = None
104+
self.server_public_key = b""
64105

65106
# TODO: Next are copied over from the AUTH machine and may or may not be used
66107
# so we need to investigate if they are used or are duplicated above
@@ -75,18 +116,14 @@ def __init__(self, auth_socket,
75116
self.tls_session = None
76117
self.eap_tls_state = None
77118

78-
self.eap_tls_server_data = b'' # Reassembly area for EAP-TLS
79-
self.eap_tls_expected_len = 0
80-
self.eap_tls_last_id = 256
81-
self.eap_tls_client_data_len = 0
82-
# self.eap_tls_client_data_max_len = 994 # TODO: support fragmentation....
119+
83120
print("*** Not enforcing client EAP-TLS fragmentation yet")
84121
self.eap_tls_client_data_max_len = 16000
85122

86123
self._debug = debug
87124
self.is_server_key_exchange = False
88125

89-
# Probably want these
126+
# Probably want these - TODO Eventually calculate the hash on the fly if possible
90127
self._client_handshake_records_sent: List['TLSRecord'] = []
91128
self._server_handshake_records_received: List['TLSRecord'] = []
92129

@@ -111,8 +148,12 @@ def __init__(self, auth_socket,
111148
self.state_machine: TLSClientStateMachine = TLSClientStateMachine(self)
112149

113150
@property
114-
def security_parameters(self) -> SecurityParameters:
115-
return self._security_parameters
151+
def rx_security_parameters(self) -> SecurityParameters:
152+
return self._receive_security_parameters
153+
154+
@property
155+
def tx_security_parameters(self) -> SecurityParameters:
156+
return self._send_security_parameters
116157

117158
@property
118159
def tls_version(self) -> TLS:
@@ -163,10 +204,12 @@ def handle_tls_data(self, eap_id: int, eap_tls: 'EAP_TLS', eap: 'EAP') -> None:
163204
print(f"*** Last EAP ID: {eap_id}, EAP-LAST-ID: {self.eap_tls_last_id}")
164205

165206
if self.state_machine.state == TLSClientStateMachine.INITIAL:
207+
print(f"TLSClient.handle_tls_data: Start: {eap_id}")
166208
self.state_machine.start(eap_id=eap_id)
167209

168210
elif eap_id == self._eap_tls_last_sent_id and self._eap_tls_last_sent_data is not None:
169211
# Handle a retransmit
212+
print(f"TLSClient.handle_tls_data: Rx retransmit: eap_id: {eap_id}")
170213
self.auth_socket.send_response(eap_id, self._eap_tls_last_sent_data)
171214

172215
else:
@@ -203,7 +246,7 @@ def handle_tls_data(self, eap_id: int, eap_tls: 'EAP_TLS', eap: 'EAP') -> None:
203246
self.save_server_record(packet)
204247
self.state_machine.rx_packet(eap_id, packet)
205248

206-
def _rx_server_eap_tls(self, eap_id: int, eap_tls: 'EAP_TLS') -> Union[Packet, List[Packet], None]:
249+
def _rx_server_eap_tls(self, eap_id: int, eap_tls: Union['EAP_TLS', 'EapTls']) -> Union[Packet, List[Packet], None]:
207250
"""
208251
Handle server data
209252
@@ -259,7 +302,7 @@ def _rx_server_eap_tls(self, eap_id: int, eap_tls: 'EAP_TLS') -> Union[Packet, L
259302

260303
try:
261304
print(f"Reassembled packet: {self.eap_tls_server_data.hex()}")
262-
record_list = TLSRecord.parse(self.eap_tls_server_data, self.security_parameters)
305+
record_list = TLSRecord.parse(self.eap_tls_server_data, self.rx_security_parameters)
263306

264307
except Exception as _e:
265308
record_list = None

tls_packet/auth/tls_server_hello.py

+32-8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import os
1818
import struct
1919
from typing import Union, Optional, Iterable
20+
import sys
2021

2122
from tls_packet.auth.cipher_suites import CipherSuite
2223
from tls_packet.auth.security_params import TLSCompressionMethod
@@ -89,26 +90,49 @@ def __init__(self, session,
8990

9091
# Error checks
9192
self.version = version or (int(session.tls_version) if session is not None else int(TLSv1_2()))
92-
self.random_bytes = random_data or os.urandom(32)
93-
self.session_id = session_id
94-
self.cipher = cipher
95-
self.compression = compression
93+
self._random_bytes = random_data or os.urandom(32)
94+
self._session_id = session_id
95+
self._cipher = cipher
96+
self._compression = TLSCompressionMethod(compression)
9697
self.extensions = extensions or []
9798

99+
# TODO: In other client, it created cipher suite with
100+
# self.cipher_suite = CipherSuite.get_from_id(self.tls_version, self.client_random, self.server_random,
101+
# self.server_certificate, server_cipher_suite)
102+
# And used it supported the parse_key_exchange method
103+
98104
# Error checks
99105
if self.version == int(TLSv1_3()):
100106
raise NotImplementedError("TLSServerHello: TLSv1.3 not yet supported")
101107

102-
if len(self.random_bytes) != 32:
108+
if len(self._random_bytes) != 32:
103109
raise ValueError(f"TLSServerHello: Random must be exactly 32 bytes, received {len(self.random_bytes)}")
104110

105-
if self.session_id > 32 or session_id < 0:
106-
raise ValueError(f"TLSServerHello: SessionID is an opaque value: 0..32, found, {self.session_id}")
111+
if self._session_id > 32 or session_id < 0:
112+
raise ValueError(f"TLSServerHello: SessionID is an opaque value: 0..32, found, {self._session_id}")
107113

108114
# Unsupported at this time
109115
if extensions and not isinstance(extensions, PacketPayload): # TODO: not yet supported
110116
raise NotImplementedError("Unsupported parameter")
111117

118+
@property
119+
def cipher_suite(self) -> CipherSuite:
120+
""" Cipher Suite selected by the server """
121+
return self._cipher
122+
123+
@property
124+
def compression_method(self) -> TLSCompressionMethod:
125+
""" Compression method used """
126+
return self._compression
127+
128+
@property
129+
def session_id(self) -> int:
130+
return self._session_id
131+
132+
@property
133+
def random_bytes(self) -> bytes:
134+
return self._random_bytes
135+
112136
@staticmethod
113137
def parse(frame: bytes, *args, max_depth: Optional[int] = PARSE_ALL, **kwargs) -> Union[TLSHandshake, None]:
114138
""" Frame to TLSSessionHello """
@@ -135,7 +159,7 @@ def parse(frame: bytes, *args, max_depth: Optional[int] = PARSE_ALL, **kwargs) -
135159
compression = TLSCompressionMethod(compression)
136160

137161
if extension_length:
138-
print("TODO: Pushing undecoded extensions into a payload object")
162+
print("TODO: Pushing undecoded extensions into a payload object", file=sys.stderr)
139163
payload = PacketPayload(frame[offset:offset + extension_length])
140164
else:
141165
payload = None

tls_packet/auth/tls_server_key_exchange.py

+15
Original file line numberDiff line numberDiff line change
@@ -293,5 +293,20 @@ def parse(frame: bytes, *args, max_depth: Optional[int] = PARSE_ALL, **kwargs) -
293293

294294
return TLSServerKeyExchange(curve_type, named_curve, pubkey, signature, *args, length=msg_len, original_frame=frame, **kwargs)
295295

296+
# TODO: In another client, they had the cipher suite parse this message
297+
298+
# TODO: In other client, it created cipher suite with
299+
# self.cipher_suite = CipherSuite.get_from_id(self.tls_version, self.client_random, self.server_random,
300+
# self.server_certificate, server_cipher_suite)
301+
# And used it supported the parse_key_exchange method
302+
#
303+
# if self.is_server_key_exchange: # Server key exchange
304+
# self.cipher_suite.parse_key_exchange_params(next_bytes)
305+
#
306+
# key_exchange below is ECDH, DH, or RSA
307+
#
308+
# def parse_key_exchange_params(self, params_bytes):
309+
# self.key_exchange.parse_params(params_bytes)
310+
296311
def pack(self, payload: Optional[Union[bytes, None]] = None) -> bytes:
297312
raise NotImplementedError("TODO: Not yet implemented since we are functioning as a client")

0 commit comments

Comments
 (0)