summaryrefslogtreecommitdiff
path: root/ldap3/protocol
diff options
context:
space:
mode:
Diffstat (limited to 'ldap3/protocol')
-rw-r--r--ldap3/protocol/formatters/validators.py6
-rw-r--r--ldap3/protocol/rfc2849.py7
-rw-r--r--ldap3/protocol/sasl/digestMd5.py4
-rw-r--r--ldap3/protocol/sasl/kerberos.py273
4 files changed, 158 insertions, 132 deletions
diff --git a/ldap3/protocol/formatters/validators.py b/ldap3/protocol/formatters/validators.py
index 3ab300d..bbc459f 100644
--- a/ldap3/protocol/formatters/validators.py
+++ b/ldap3/protocol/formatters/validators.py
@@ -368,7 +368,7 @@ def validate_uuid(input_value):
def validate_uuid_le(input_value):
- """
+ r"""
Active Directory stores objectGUID in uuid_le format, follows RFC4122 and MS-DTYP:
"{07039e68-4373-264d-a0a7-07039e684373}": string representation big endian, converted to little endian (with or without brace curles)
"689e030773434d26a7a007039e684373": packet representation, already in little endian
@@ -401,9 +401,7 @@ def validate_uuid_le(input_value):
error = True
elif '\\' in element:
try:
- uuid = UUID(bytes_le=ldap_escape_to_bytes(element)).bytes_le
- uuid = escape_bytes(uuid)
- valid_values.append(uuid) # byte representation, value in little endian
+ valid_values.append(UUID(bytes_le=ldap_escape_to_bytes(element)).bytes_le) # byte representation, value in little endian
changed = True
except ValueError:
error = True
diff --git a/ldap3/protocol/rfc2849.py b/ldap3/protocol/rfc2849.py
index 953be33..588af9e 100644
--- a/ldap3/protocol/rfc2849.py
+++ b/ldap3/protocol/rfc2849.py
@@ -30,10 +30,11 @@ from .. import STRING_TYPES
from ..core.exceptions import LDAPLDIFError, LDAPExtensionError
from ..protocol.persistentSearch import EntryChangeNotificationControl
from ..utils.asn1 import decoder
+from ..utils.config import get_config_parameter
# LDIF converter RFC 2849 compliant
-LDIF_LINE_LENGTH = 78
+conf_ldif_line_length = get_config_parameter('LDIF_LINE_LENGTH')
def safe_ldif_string(bytes_value):
@@ -233,8 +234,8 @@ def operation_to_ldif(operation_type, entries, all_base64=False, sort_order=None
# check max line length and split as per note 2 of RFC 2849
for line in lines:
if line:
- ldif_record.append(line[0:LDIF_LINE_LENGTH])
- ldif_record.extend([' ' + line[i: i + LDIF_LINE_LENGTH - 1] for i in range(LDIF_LINE_LENGTH, len(line), LDIF_LINE_LENGTH - 1)] if len(line) > LDIF_LINE_LENGTH else [])
+ ldif_record.append(line[0:conf_ldif_line_length])
+ ldif_record.extend([' ' + line[i: i + conf_ldif_line_length - 1] for i in range(conf_ldif_line_length, len(line), conf_ldif_line_length - 1)] if len(line) > conf_ldif_line_length else [])
else:
ldif_record.append('')
diff --git a/ldap3/protocol/sasl/digestMd5.py b/ldap3/protocol/sasl/digestMd5.py
index 48235d6..520be3c 100644
--- a/ldap3/protocol/sasl/digestMd5.py
+++ b/ldap3/protocol/sasl/digestMd5.py
@@ -94,7 +94,7 @@ def sasl_digest_md5(connection, controls):
authz_id = connection.sasl_credentials[3].encode(charset) if connection.sasl_credentials[3] else b''
nonce = server_directives['nonce'].encode(charset)
cnonce = random_hex_string(16).encode(charset)
- uri = b'ldap/'
+ uri = b'ldap/' + connection.server.host.encode(charset)
qop = b'auth'
digest_response = b'username="' + user + b'",'
@@ -110,7 +110,7 @@ def sasl_digest_md5(connection, controls):
a0 = md5_h(b':'.join([user, realm, password]))
a1 = b':'.join([a0, nonce, cnonce, authz_id]) if authz_id else b':'.join([a0, nonce, cnonce])
- a2 = b'AUTHENTICATE:' + uri + (':00000000000000000000000000000000' if qop in [b'auth-int', b'auth-conf'] else b'')
+ a2 = b'AUTHENTICATE:' + uri + (b':00000000000000000000000000000000' if qop in [b'auth-int', b'auth-conf'] else b'')
digest_response += b'response="' + md5_hex(md5_kd(md5_hex(md5_h(a1)), b':'.join([nonce, b'00000001', cnonce, qop, md5_hex(md5_h(a2))]))) + b'"'
diff --git a/ldap3/protocol/sasl/kerberos.py b/ldap3/protocol/sasl/kerberos.py
index 07db583..14a307e 100644
--- a/ldap3/protocol/sasl/kerberos.py
+++ b/ldap3/protocol/sasl/kerberos.py
@@ -1,123 +1,150 @@
-"""
-"""
-
-# Created on 2015.04.08
-#
-# Author: Giovanni Cannata
-#
-# Copyright 2015 - 2020 Giovanni Cannata
-#
-# This file is part of ldap3.
-#
-# ldap3 is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# ldap3 is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with ldap3 in the COPYING and COPYING.LESSER files.
-# If not, see <http://www.gnu.org/licenses/>.
-
-# original code by Hugh Cole-Baker, modified by Peter Foley
-# it needs the gssapi package
-import socket
-
-from ...core.exceptions import LDAPPackageUnavailableError, LDAPCommunicationError
-
-try:
- # noinspection PyPackageRequirements,PyUnresolvedReferences
- import gssapi
-except ImportError:
- raise LDAPPackageUnavailableError('package gssapi missing')
-
-from .sasl import send_sasl_negotiation, abort_sasl_negotiation
-
-NO_SECURITY_LAYER = 1
-INTEGRITY_PROTECTION = 2
-CONFIDENTIALITY_PROTECTION = 4
-
-
-def sasl_gssapi(connection, controls):
- """
- Performs a bind using the Kerberos v5 ("GSSAPI") SASL mechanism
- from RFC 4752. Does not support any security layers, only authentication!
-
- sasl_credentials can be empty or a tuple with one or two elements.
- The first element determines which service principal to request a ticket for and can be one of the following:
-
- - None or False, to use the hostname from the Server object
- - True to perform a reverse DNS lookup to retrieve the canonical hostname for the hosts IP address
- - A string containing the hostname
-
- The optional second element is what authorization ID to request.
-
- - If omitted or None, the authentication ID is used as the authorization ID
- - If a string, the authorization ID to use. Should start with "dn:" or "user:".
-
- The optional third element is a raw gssapi credentials structure which can be used over
- the implicit use of a krb ccache.
- """
- target_name = None
- authz_id = b""
- raw_creds = None
- creds = None
- if connection.sasl_credentials:
- if len(connection.sasl_credentials) >= 1 and connection.sasl_credentials[0]:
- if connection.sasl_credentials[0] is True:
- hostname = socket.gethostbyaddr(connection.socket.getpeername()[0])[0]
- target_name = gssapi.Name('ldap@' + hostname, gssapi.NameType.hostbased_service)
- else:
- target_name = gssapi.Name('ldap@' + connection.sasl_credentials[0], gssapi.NameType.hostbased_service)
- if len(connection.sasl_credentials) >= 2 and connection.sasl_credentials[1]:
- authz_id = connection.sasl_credentials[1].encode("utf-8")
- if len(connection.sasl_credentials) >= 3 and connection.sasl_credentials[2]:
- raw_creds = connection.sasl_credentials[2]
- if target_name is None:
- target_name = gssapi.Name('ldap@' + connection.server.host, gssapi.NameType.hostbased_service)
-
- if raw_creds is not None:
- creds = gssapi.Credentials(base=raw_creds, usage='initiate', store=connection.cred_store)
- else:
- creds = gssapi.Credentials(name=gssapi.Name(connection.user), usage='initiate', store=connection.cred_store) if connection.user else None
- ctx = gssapi.SecurityContext(name=target_name, mech=gssapi.MechType.kerberos, creds=creds)
- in_token = None
- try:
- while True:
- out_token = ctx.step(in_token)
- if out_token is None:
- out_token = ''
- result = send_sasl_negotiation(connection, controls, out_token)
- in_token = result['saslCreds']
- try:
- # This raised an exception in gssapi<1.1.2 if the context was
- # incomplete, but was fixed in
- # https://github.com/pythongssapi/python-gssapi/pull/70
- if ctx.complete:
- break
- except gssapi.exceptions.MissingContextError:
- pass
-
- unwrapped_token = ctx.unwrap(in_token)
- if len(unwrapped_token.message) != 4:
- raise LDAPCommunicationError("Incorrect response from server")
-
- server_security_layers = unwrapped_token.message[0]
- if not isinstance(server_security_layers, int):
- server_security_layers = ord(server_security_layers)
- if server_security_layers in (0, NO_SECURITY_LAYER):
- if unwrapped_token.message[1:] != '\x00\x00\x00':
- raise LDAPCommunicationError("Server max buffer size must be 0 if no security layer")
- if not (server_security_layers & NO_SECURITY_LAYER):
- raise LDAPCommunicationError("Server requires a security layer, but this is not implemented")
-
- client_security_layers = bytearray([NO_SECURITY_LAYER, 0, 0, 0])
- out_token = ctx.wrap(bytes(client_security_layers)+authz_id, False)
- return send_sasl_negotiation(connection, controls, out_token.message)
- except (gssapi.exceptions.GSSError, LDAPCommunicationError):
- abort_sasl_negotiation(connection, controls)
- raise
+"""
+"""
+
+# Created on 2015.04.08
+#
+# Author: Giovanni Cannata
+#
+# Copyright 2015 - 2020 Giovanni Cannata
+#
+# This file is part of ldap3.
+#
+# ldap3 is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# ldap3 is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ldap3 in the COPYING and COPYING.LESSER files.
+# If not, see <http://www.gnu.org/licenses/>.
+
+# original code by Hugh Cole-Baker, modified by Peter Foley
+# it needs the gssapi package
+import socket
+
+from ...core.exceptions import LDAPPackageUnavailableError, LDAPCommunicationError
+
+try:
+ # noinspection PyPackageRequirements,PyUnresolvedReferences
+ import gssapi
+ from gssapi.raw import ChannelBindings
+except ImportError:
+ raise LDAPPackageUnavailableError('package gssapi missing')
+
+from .sasl import send_sasl_negotiation, abort_sasl_negotiation
+
+
+NO_SECURITY_LAYER = 1
+INTEGRITY_PROTECTION = 2
+CONFIDENTIALITY_PROTECTION = 4
+
+
+def get_channel_bindings(ssl_socket):
+ try:
+ server_certificate = ssl_socket.getpeercert(True)
+ except:
+ # it is not SSL socket
+ return None
+ try:
+ from cryptography import x509
+ from cryptography.hazmat.backends import default_backend
+ from cryptography.hazmat.primitives import hashes
+ except ImportError:
+ raise LDAPPackageUnavailableError('package cryptography missing')
+ cert = x509.load_der_x509_certificate(server_certificate, default_backend())
+ hash_algorithm = cert.signature_hash_algorithm
+ # According to https://tools.ietf.org/html/rfc5929#section-4.1, we have to convert the the hash function for md5 and sha1
+ if hash_algorithm.name in ('md5', 'sha1'):
+ digest = hashes.Hash(hashes.SHA256(), default_backend())
+ else:
+ digest = hashes.Hash(hash_algorithm, default_backend())
+ digest.update(server_certificate)
+ application_data = b'tls-server-end-point:' + digest.finalize()
+ return ChannelBindings(application_data=application_data)
+
+
+def sasl_gssapi(connection, controls):
+ """
+ Performs a bind using the Kerberos v5 ("GSSAPI") SASL mechanism
+ from RFC 4752. Does not support any security layers, only authentication!
+
+ sasl_credentials can be empty or a tuple with one or two elements.
+ The first element determines which service principal to request a ticket for and can be one of the following:
+
+ - None or False, to use the hostname from the Server object
+ - True to perform a reverse DNS lookup to retrieve the canonical hostname for the hosts IP address
+ - A string containing the hostname
+
+ The optional second element is what authorization ID to request.
+
+ - If omitted or None, the authentication ID is used as the authorization ID
+ - If a string, the authorization ID to use. Should start with "dn:" or "user:".
+
+ The optional third element is a raw gssapi credentials structure which can be used over
+ the implicit use of a krb ccache.
+ """
+ target_name = None
+ authz_id = b""
+ raw_creds = None
+ creds = None
+ if connection.sasl_credentials:
+ if len(connection.sasl_credentials) >= 1 and connection.sasl_credentials[0]:
+ if connection.sasl_credentials[0] is True:
+ hostname = socket.gethostbyaddr(connection.socket.getpeername()[0])[0]
+ target_name = gssapi.Name('ldap@' + hostname, gssapi.NameType.hostbased_service)
+ else:
+ target_name = gssapi.Name('ldap@' + connection.sasl_credentials[0], gssapi.NameType.hostbased_service)
+ if len(connection.sasl_credentials) >= 2 and connection.sasl_credentials[1]:
+ authz_id = connection.sasl_credentials[1].encode("utf-8")
+ if len(connection.sasl_credentials) >= 3 and connection.sasl_credentials[2]:
+ raw_creds = connection.sasl_credentials[2]
+ if target_name is None:
+ target_name = gssapi.Name('ldap@' + connection.server.host, gssapi.NameType.hostbased_service)
+
+ if raw_creds is not None:
+ creds = gssapi.Credentials(base=raw_creds, usage='initiate', store=connection.cred_store)
+ else:
+ creds = gssapi.Credentials(name=gssapi.Name(connection.user), usage='initiate', store=connection.cred_store) if connection.user else None
+
+ ctx = gssapi.SecurityContext(name=target_name, mech=gssapi.MechType.kerberos, creds=creds, channel_bindings=get_channel_bindings(connection.socket))
+ in_token = None
+ try:
+ while True:
+ out_token = ctx.step(in_token)
+ if out_token is None:
+ out_token = ''
+ result = send_sasl_negotiation(connection, controls, out_token)
+ in_token = result['saslCreds']
+ try:
+ # This raised an exception in gssapi<1.1.2 if the context was
+ # incomplete, but was fixed in
+ # https://github.com/pythongssapi/python-gssapi/pull/70
+ if ctx.complete:
+ break
+ except gssapi.exceptions.MissingContextError:
+ pass
+
+ unwrapped_token = ctx.unwrap(in_token)
+ if len(unwrapped_token.message) != 4:
+ raise LDAPCommunicationError("Incorrect response from server")
+
+ server_security_layers = unwrapped_token.message[0]
+ if not isinstance(server_security_layers, int):
+ server_security_layers = ord(server_security_layers)
+ if server_security_layers in (0, NO_SECURITY_LAYER):
+ if unwrapped_token.message[1:] != '\x00\x00\x00':
+ raise LDAPCommunicationError("Server max buffer size must be 0 if no security layer")
+ if not (server_security_layers & NO_SECURITY_LAYER):
+ raise LDAPCommunicationError("Server requires a security layer, but this is not implemented")
+
+ client_security_layers = bytearray([NO_SECURITY_LAYER, 0, 0, 0])
+ out_token = ctx.wrap(bytes(client_security_layers)+authz_id, False)
+ return send_sasl_negotiation(connection, controls, out_token.message)
+ except (gssapi.exceptions.GSSError, LDAPCommunicationError):
+ abort_sasl_negotiation(connection, controls)
+ raise