diff --git a/ldap3/core/connection.py b/ldap3/core/connection.py index 8dfa9ab7..773da373 100644 --- a/ldap3/core/connection.py +++ b/ldap3/core/connection.py @@ -298,6 +298,7 @@ def __init__(self, self._digest_md5_kcs_cipher = None self._digest_md5_sec_num = 0 self.krb_ctx = None + self.krb_wrap_size_limit = None if session_security and not (self.authentication == NTLM or self.sasl_mechanism == GSSAPI): self.last_error = '"session_security" option only available for NTLM and GSSAPI' diff --git a/ldap3/protocol/sasl/kerberos.py b/ldap3/protocol/sasl/kerberos.py index add2b73e..49120a5b 100644 --- a/ldap3/protocol/sasl/kerberos.py +++ b/ldap3/protocol/sasl/kerberos.py @@ -27,6 +27,7 @@ # it needs the gssapi package import base64 import socket +import struct from ...core.exceptions import LDAPPackageUnavailableError, LDAPCommunicationError from ...core.rdns import ReverseDnsSetting, get_hostname_by_addr, is_ip_addr @@ -196,8 +197,6 @@ def _common_process_end_token_get_security_layers(negotiated_token, session_secu """ Process the response we got at the end of our SASL negotiation wherein the server told us what minimum security layers we need, and return a bytearray for the client security layers we want. This function throws an error on a malformed token from the server. - The ldap3 library does not support security layers, and only supports authentication with kerberos, - so an error will be thrown for any tokens that indicate a security layer requirement. """ if len(negotiated_token) != 4: raise LDAPCommunicationError("Incorrect response from server") @@ -212,10 +211,17 @@ def _common_process_end_token_get_security_layers(negotiated_token, session_secu if not (server_security_layers & security_layer): raise LDAPCommunicationError("Server doesn't support the security level asked") - # this is here to encourage anyone implementing client security layers to do it - # for both windows and posix - client_security_layers = bytearray([security_layer, 0, 0, 0]) - return client_security_layers + max_output_size_bytes = negotiated_token[1:4] + # We need to pod the data as struct unpack expects a 32-bit value + krb_wrap_size_limit = struct.unpack('!I', b'\x00' + max_output_size_bytes)[0] + + # match the server max buffer size if we have client security layers configured + if security_layer == CONFIDENTIALITY_PROTECTION: + client_security_layers = bytearray([security_layer, negotiated_token[1], negotiated_token[2], negotiated_token[3]]) + else: + client_security_layers = bytearray([security_layer, 0, 0, 0]) + return client_security_layers, krb_wrap_size_limit + def _posix_sasl_gssapi(connection, controls): """ Performs a bind using the Kerberos v5 ("GSSAPI") SASL mechanism @@ -245,9 +251,10 @@ def _posix_sasl_gssapi(connection, controls): pass unwrapped_token = ctx.unwrap(in_token) - client_security_layers = _common_process_end_token_get_security_layers(unwrapped_token.message, connection.session_security) + client_security_layers, krb_wrap_size_limit = _common_process_end_token_get_security_layers(unwrapped_token.message, connection.session_security) out_token = ctx.wrap(bytes(client_security_layers)+authz_id, False) connection.krb_ctx = ctx + connection.krb_wrap_size_limit = krb_wrap_size_limit return send_sasl_negotiation(connection, controls, out_token.message) except (gssapi.exceptions.GSSError, LDAPCommunicationError): abort_sasl_negotiation(connection, controls) @@ -291,13 +298,14 @@ def _windows_sasl_gssapi(connection, controls): negotiated_token = '' if winkerberos.authGSSClientResponse(ctx): negotiated_token = base64.standard_b64decode(winkerberos.authGSSClientResponse(ctx)) - client_security_layers = _common_process_end_token_get_security_layers(negotiated_token, connection.session_security) + client_security_layers, krb_wrap_size_limit = _common_process_end_token_get_security_layers(negotiated_token, connection.session_security) # manually construct a message indicating use of authorization-only layer # see winkerberos example: https://github.com/mongodb/winkerberos/blob/master/test/test_winkerberos.py authz_only_msg = base64.b64encode(bytes(client_security_layers) + authz_id).decode('utf-8') winkerberos.authGSSClientWrap(ctx, authz_only_msg) out_token = winkerberos.authGSSClientResponse(ctx) or '' connection.krb_ctx = ctx + connection.krb_wrap_size_limit = krb_wrap_size_limit return send_sasl_negotiation(connection, controls, base64.b64decode(out_token)) except (winkerberos.GSSError, LDAPCommunicationError): abort_sasl_negotiation(connection, controls) diff --git a/ldap3/strategy/base.py b/ldap3/strategy/base.py index 5da1c179..af32a6a9 100644 --- a/ldap3/strategy/base.py +++ b/ldap3/strategy/base.py @@ -902,14 +902,30 @@ def sending(self, ldap_message): if self.connection.authentication == NTLM: # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/115f9c7d-bc30-4262-ae96-254555c14ea6 encoded_message = self.connection.ntlm_client.seal(encoded_message) + encoded_message = int(len(encoded_message)).to_bytes(4, 'big') + encoded_message elif self.connection.sasl_mechanism == GSSAPI: + # winkerberos does not support GSS_WRAP_size_limit if posix_gssapi_unavailable: import winkerberos winkerberos.authGSSClientWrap(self.connection.krb_ctx, base64.b64encode(encoded_message).decode('utf-8'), None, 1) encoded_message = base64.b64decode(winkerberos.authGSSClientResponse(self.connection.krb_ctx)) + encoded_message = int(len(encoded_message)).to_bytes(4, 'big') + encoded_message else: - encoded_message = self.connection.krb_ctx.wrap(encoded_message, True).message - encoded_message = int(len(encoded_message)).to_bytes(4, 'big') + encoded_message + krb_wrap_size_limit = self.connection.krb_ctx.get_wrap_size_limit(self.connection.krb_wrap_size_limit - 4) + + offset = 0 + wrapped_messages = b'' + while offset < len(encoded_message): + chunk_size = min(krb_wrap_size_limit, len(encoded_message) - offset) + message_chunk = encoded_message[offset:offset + chunk_size] + wrapped_message = self.connection.krb_ctx.wrap(message_chunk, True).message + wrapped_message = int(len(wrapped_message)).to_bytes(4, 'big') + wrapped_message + + wrapped_messages = wrapped_messages + wrapped_message + + offset += chunk_size + + encoded_message = wrapped_messages self.connection.socket.sendall(encoded_message) if log_enabled(EXTENDED):