Skip to content

Commit

Permalink
xMerge remote-tracking branch 'wokis/dev' into bleeding_edge
Browse files Browse the repository at this point in the history
  • Loading branch information
ThePirateWhoSmellsOfSunflowers committed Aug 21, 2024
2 parents 8df6332 + cab5629 commit ada6a9d
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 10 deletions.
1 change: 1 addition & 0 deletions ldap3/core/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
24 changes: 16 additions & 8 deletions ldap3/protocol/sasl/kerberos.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
20 changes: 18 additions & 2 deletions ldap3/strategy/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down

0 comments on commit ada6a9d

Please sign in to comment.