Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correctly handle GSSAPI SASL negotiated max buffer size as per RFC4752 #1156

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ldap3/core/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,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