diff options
Diffstat (limited to 'ldap3/protocol')
-rw-r--r-- | ldap3/protocol/formatters/validators.py | 6 | ||||
-rw-r--r-- | ldap3/protocol/rfc2849.py | 7 | ||||
-rw-r--r-- | ldap3/protocol/sasl/digestMd5.py | 4 | ||||
-rw-r--r-- | ldap3/protocol/sasl/kerberos.py | 273 |
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 |