2424#
2525
2626import re
27+ import struct
2728import socket
2829from binascii import unhexlify
2930import random
@@ -104,6 +105,7 @@ def __init__(self, url, baseDN='', dstIp=None, signing=True):
104105 raise LDAPSessionError (errorString = "Unknown URL prefix: '%s'" % url )
105106
106107 self .__binded = False
108+ self .__channel_binding_value = None
107109
108110 ### SASL Auth LDAP Signing arguments
109111 self .sequenceNumber = 0
@@ -141,26 +143,25 @@ def __init__(self, url, baseDN='', dstIp=None, signing=True):
141143 self ._socket .connect (sa )
142144 self ._socket .do_handshake ()
143145
144- def generateChannelBindingValue (self ):
145- # From: https://github.com/ly4k/ldap3/commit/87f5760e5a68c2f91eac8ba375f4ea3928e2b9e0#diff-c782b790cfa0a948362bf47d72df8ddd6daac12e5757afd9d371d89385b27ef6R1383
146- from hashlib import md5
147- # Ugly but effective, to get the digest of the X509 DER in bytes
148- peer_cert_digest_str = self ._socket .get_peer_certificate ().digest ('sha256' ).decode ()
149- peer_cert_digest_bytes = bytes .fromhex (peer_cert_digest_str .replace (':' , '' ))
150-
151- channel_binding_struct = b''
152- initiator_address = b'\x00 ' * 8
153- acceptor_address = b'\x00 ' * 8
154-
155- # https://datatracker.ietf.org/doc/html/rfc5929#section-4
156- application_data_raw = b'tls-server-end-point:' + peer_cert_digest_bytes
157- len_application_data = len (application_data_raw ).to_bytes (4 , byteorder = 'little' , signed = False )
158- application_data = len_application_data
159- application_data += application_data_raw
160- channel_binding_struct += initiator_address
161- channel_binding_struct += acceptor_address
162- channel_binding_struct += application_data
163- return md5 (channel_binding_struct ).digest ()
146+ # From: https://github.com/ly4k/ldap3/commit/87f5760e5a68c2f91eac8ba375f4ea3928e2b9e0#diff-c782b790cfa0a948362bf47d72df8ddd6daac12e5757afd9d371d89385b27ef6R1383
147+ from hashlib import md5
148+ # Ugly but effective, to get the digest of the X509 DER in bytes
149+ peer_cert_digest_str = self ._socket .get_peer_certificate ().digest ('sha256' ).decode ()
150+ peer_cert_digest_bytes = bytes .fromhex (peer_cert_digest_str .replace (':' , '' ))
151+
152+ channel_binding_struct = b''
153+ initiator_address = b'\x00 ' * 8
154+ acceptor_address = b'\x00 ' * 8
155+
156+ # https://datatracker.ietf.org/doc/html/rfc5929#section-4
157+ application_data_raw = b'tls-server-end-point:' + peer_cert_digest_bytes
158+ len_application_data = len (application_data_raw ).to_bytes (4 , byteorder = 'little' , signed = False )
159+ application_data = len_application_data
160+ application_data += application_data_raw
161+ channel_binding_struct += initiator_address
162+ channel_binding_struct += acceptor_address
163+ channel_binding_struct += application_data
164+ self .__channel_binding_value = md5 (channel_binding_struct ).digest ()
164165
165166 def kerberosLogin (self , user , password , domain = '' , lmhash = '' , nthash = '' , aesKey = '' , kdcHost = None , TGT = None ,
166167 TGS = None , useCache = True ):
@@ -268,8 +269,8 @@ def kerberosLogin(self, user, password, domain='', lmhash='', nthash='', aesKey=
268269
269270 # If TLS is used, setup channel binding
270271
271- if self ._SSL :
272- chkField ['Bnd' ] = self .generateChannelBindingValue ()
272+ if self ._SSL and self . __channel_binding_value is not None :
273+ chkField ['Bnd' ] = self .__channel_binding_value
273274 if self .__signing :
274275 chkField ['Flags' ] |= GSS_C_CONF_FLAG
275276 chkField ['Flags' ] |= GSS_C_INTEG_FLAG
@@ -372,8 +373,8 @@ def login(self, user='', password='', domain='', lmhash='', nthash='', authentic
372373
373374 # If TLS is used, setup channel binding
374375 channel_binding_value = b''
375- if self ._SSL :
376- channel_binding_value = self .generateChannelBindingValue ()
376+ if self ._SSL and self . __channel_binding_value is not None :
377+ channel_binding_value = self .__channel_binding_value
377378
378379 # NTLM Auth
379380 type3 , exportedSessionKey = getNTLMSSPType3 (negotiate , bytes (type2 ), user , password , domain , lmhash , nthash , channel_binding_value = channel_binding_value )
@@ -418,8 +419,8 @@ def login(self, user='', password='', domain='', lmhash='', nthash='', authentic
418419
419420 # channel binding
420421 channel_binding_value = b''
421- if self ._SSL :
422- channel_binding_value = self .generateChannelBindingValue ()
422+ if self ._SSL and self . __channel_binding_value is not None :
423+ channel_binding_value = self .__channel_binding_value
423424
424425 # NTLM Auth
425426 type3 , exportedSessionKey = getNTLMSSPType3 (negotiate , type2 , user , password , domain , lmhash , nthash , service = 'ldap' , version = self .version , use_ntlmv2 = True , channel_binding_value = channel_binding_value )
@@ -557,7 +558,7 @@ def send(self, request, controls=None):
557558 self .sequenceNumber += 1
558559 return self ._socket .sendall (data )
559560
560- def recv (self ):
561+ def recv_raw (self ):
561562 REQUEST_SIZE = 8192
562563 data = b''
563564 done = False
@@ -567,16 +568,31 @@ def recv(self):
567568 done = True
568569 data += recvData
569570
571+ if self .__binded and self .__signing : # we need to decrypt every TCP frames, all at once
572+ message_length = struct .unpack ('!I' , data [:4 ])[0 ]
573+
574+ while message_length != len (data ) - 4 :
575+ done = False
576+ while not done :
577+ recvData = self ._socket .recv (REQUEST_SIZE )
578+ if len (recvData ) < REQUEST_SIZE :
579+ done = True
580+ data += recvData
581+
582+ data = self .decrypt (data )
583+
584+ return data
585+
586+ def recv (self ):
570587 response = []
571- if self .__binded and self .__signing :
572- data = self .decrypt (data )
588+ data = self .recv_raw ()
573589 while len (data ) > 0 :
574590 try :
575591 # need to decrypt before
576592 message , remaining = decoder .decode (data , asn1Spec = LDAPMessage ())
577593 except SubstrateUnderrunError :
578594 # We need more data
579- remaining = data + self ._socket . recv ( REQUEST_SIZE )
595+ remaining = data + self .recv_raw ()
580596 else :
581597 if message ['messageID' ] == 0 : # unsolicited notification
582598 name = message ['protocolOp' ]['extendedResp' ]['responseName' ] or message ['responseName' ]
0 commit comments