Hello community,
here is the log from the commit of package python-ntlm-auth for openSUSE:Factory checked in at 2019-01-03 18:07:17
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-ntlm-auth (Old)
and /work/SRC/openSUSE:Factory/.python-ntlm-auth.new.28833 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-ntlm-auth"
Thu Jan 3 18:07:17 2019 rev:4 rq:662307 version:1.2.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-ntlm-auth/python-ntlm-auth.changes 2018-12-24 11:40:26.317502869 +0100
+++ /work/SRC/openSUSE:Factory/.python-ntlm-auth.new.28833/python-ntlm-auth.changes 2019-01-03 18:07:21.104101779 +0100
@@ -1,0 +2,10 @@
+Fri Dec 28 15:12:54 UTC 2018 - mardnh@gmx.de
+
+- Update to version 1.2.0
+ * Deprecated ntlm_auth.ntlm.Ntlm in favour of
+ ntlm_auth.ntlm.NtlmContext
+ This is because Ntlm is heavily geared towards HTTP auth which
+ is not always the case, `NtlmContext` makes things more generic
+ Updated docs and tests to reflect this
+
+-------------------------------------------------------------------
Old:
----
python-ntlm-auth-1.1.0.tar.gz
New:
----
python-ntlm-auth-1.2.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-ntlm-auth.spec ++++++
--- /var/tmp/diff_new_pack.jabN2w/_old 2019-01-03 18:07:21.644101299 +0100
+++ /var/tmp/diff_new_pack.jabN2w/_new 2019-01-03 18:07:21.648101296 +0100
@@ -12,13 +12,13 @@
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.
-# Please submit bugfixes or comments via https://bugs.opensuse.org/
+# Please submit bugfixes or comments via http://bugs.opensuse.org/
#
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
Name: python-ntlm-auth
-Version: 1.1.0
+Version: 1.2.0
Release: 0
Summary: NTLM low-level Python library
License: MIT
++++++ python-ntlm-auth-1.1.0.tar.gz -> python-ntlm-auth-1.2.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ntlm-auth-1.1.0/.travis.yml new/ntlm-auth-1.2.0/.travis.yml
--- old/ntlm-auth-1.1.0/.travis.yml 2018-03-06 23:38:38.000000000 +0100
+++ new/ntlm-auth-1.2.0/.travis.yml 2018-06-07 09:32:00.000000000 +0200
@@ -3,7 +3,6 @@
python:
- "2.6"
- "2.7"
-- "3.3"
- "3.4"
- "3.5"
- "3.6"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ntlm-auth-1.1.0/CHANGES.md new/ntlm-auth-1.2.0/CHANGES.md
--- old/ntlm-auth-1.1.0/CHANGES.md 2018-03-06 23:38:38.000000000 +0100
+++ new/ntlm-auth-1.2.0/CHANGES.md 2018-06-07 09:32:00.000000000 +0200
@@ -1,5 +1,12 @@
# Changes
+## 1.2.0 (Jun 7, 2018)
+
+* Deprecated ntlm_auth.ntlm.Ntlm in favour of ntlm_auth.ntlm.NtlmContext
+* This is because `Ntlm` is heavily geared towards HTTP auth which is not always the case, `NtlmContext` makes things more generic
+* Updated docs and tests to reflect this
+* Dropped support for Python 3.3
+
## 1.1.0 (Mar 7, 2018)
* Removed DES code as the license was found to be incorrect from the source
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ntlm-auth-1.1.0/README.md new/ntlm-auth-1.2.0/README.md
--- old/ntlm-auth-1.1.0/README.md 2018-03-06 23:38:38.000000000 +0100
+++ new/ntlm-auth-1.2.0/README.md 2018-06-07 09:32:00.000000000 +0200
@@ -2,11 +2,6 @@
=========
[![Build Status](https://travis-ci.org/jborean93/ntlm-auth.svg?branch=master)](https://travis-ci.org/jborean93/ntlm-auth)[![Build status](https://ci.appveyor.com/api/projects/status/osvvfgmhfk4anvu0/branch/master?svg=true)](https://ci.appveyor.com/project/jborean93/ntlm-auth/branch/master)[![Coverage Status](https://coveralls.io/repos/github/jborean93/ntlm-auth/badge.svg?branch=master)](https://coveralls.io/github/jborean93/ntlm-auth?branch=master)
-This was originally a fork of
-[python-ntlm3](https://github.com/trustrachel/python-ntlm3) but has changed
-substantially since to support newer features like NTLMv2 and encryption
-that was not present before.
-
About this library
------------------
@@ -56,11 +51,11 @@
Extended Session Security is a security feature designed to increase the security of LM and NTLMv1 auth. It is no substitution for NTLMv2 but is better than nothing and should be used if possible when you need NTLMv1 compatibility.
The variables required are outlined below;
-* `user_name` - The username to authenticate with, should not have the domain prefix, i.e. USER not DOMAIN\\USER
+* `username` - The username to authenticate with, should not have the domain prefix, i.e. USER not DOMAIN\\USER
* `password` - The password of the user to authenticate with
-* `domain_name` - The domain of the user, i.e. DOMAIN. Can be blank if not in a domain environment
+* `domain` - The domain of the user, i.e. DOMAIN. Can be blank if not in a domain environment
* `workstation` - The workstation you are running on. Can be blank if you do not wish to send this
-* `server_certificate_hash` - (NTLMv2 only) The SHA256 hash of the servers DER encoded certificate. Used to calculate the Channel Binding Tokens and should be added even if it isn't required. Can be blank but auth will fail if the server requires this hash.
+* `cbt_data` - (NTLMv2 only) The `gss_channel_bindings.GssChannelBindingsStruct` used to bind with the auth response. Can be None if no binding needs to occur
#### LM Auth/NTLMv1 Auth
@@ -70,20 +65,20 @@
```python
import socket
-from ntlm_auth.ntlm import Ntlm
+from ntlm_auth.ntlm import NtlmContext
-user_name = 'User'
+username = 'User'
password = 'Password'
-domain_name = 'Domain' # Can be blank if you are not in a domain
+domain = 'Domain' # Can be blank if you are not in a domain
workstation = socket.gethostname().upper() # Can be blank if you wish to not send this info
-ntlm_context = Ntlm(ntlm_compatibility=0) # Put the ntlm_compatibility level here, 0-2 for LM Auth/NTLMv1 Auth
-negotiate_message = ntlm_context.create_negotiate_message(domain_name, workstation).decode()
+ntlm_context = NtlmContext(username, password, domain, workstation, ntlm_compatibility=0) # Put the ntlm_compatibility level here, 0-2 for LM Auth/NTLMv1 Auth
+negotiate_message = ntlm_context.step()
# Attach the negotiate_message to your NTLM/NEGOTIATE HTTP header and send to the server. Get the challenge response back from the server
challenge_message = http.response.headers['HEADERFIELD']
-authenticate_message = ntlm_context.create_authenticate_message(user_name, password, domain_name, workstation).decode()
+authenticate_message = ntlm_context.step(challenge_message)
# Attach the authenticate_message ot your NTLM_NEGOTIATE HTTP header and send to the server. You are now authenticated with NTLMv1
```
@@ -93,23 +88,30 @@
NTLMv2 Auth is the newest NTLM auth method from Microsoft and should be the option chosen by default unless you require an older auth method. The implementation is the same as NTLMv1 but with the addition of the optional `server_certificate_hash` variable and the `ntlm_compatibility` is not specified.
```python
+import base64
import socket
-from ntlm_auth.ntlm import Ntlm
+from ntlm_auth.gss_channel_bindings import GssChannelBindingsStruct
+from ntlm_auth.ntlm import NtlmContext
-user_name = 'User'
+username = 'User'
password = 'Password'
-domain_name = 'Domain' # Can be blank if you are not in a domain
+domain = 'Domain' # Can be blank if you are not in a domain
workstation = socket.gethostname().upper() # Can be blank if you wish to not send this info
-server_certificate_hash = '96B2FC1EC30792619286A0C7FD62863E81A6564E72829CBC0A46F7B1D5D92A18' # Can be blank if you don't want CBT sent
-ntlm_context = Ntlm()
-negotiate_message = ntlm_context.create_negotiate_message(domain_name, workstation).decode()
+# create the CBT struct if you wish to bind it with the auth response
+server_certificate_hash = '96B2FC1EC30792619286A0C7FD62863E81A6564E72829CBC0A46F7B1D5D92A18'
+certificate_digest = base64.b16decode(server_certificate_hash)
+cbt_data = GssChannelBindingsStruct()
+cbt_data[cbt_data.APPLICATION_DATA] = b'tls-server-end-point:' + certificate_digest
+
+ntlm_context = NtlmContext(username, password, domain, workstation, cbt_data, ntlm_compatibility=3)
+negotiate_message = ntlm_context.step()
# Attach the negotiate_message to your NTLM/NEGOTIATE HTTP header and send to the server. Get the challenge response back from the server
challenge_message = http.response.headers['HEADERFIELD']
-authenticate_message = ntlm_context.create_authenticate_message(user_name, password, domain_name, workstation, server_certificate_hash).decode()
+authenticate_message = ntlm_context.step(challenge_message)
# Attach the authenticate_message ot your NTLM_NEGOTIATE HTTP header and send to the server. You are now authenticated with NTLMv1
```
@@ -119,40 +121,40 @@
All version of NTLM supports signing (integrity) and sealing (confidentiality) of message content. This function can add these improvements to a message that is sent and received from the server. While it does encrypt the data if supported by the server it is only done with RC4 with a 128-bit key which is not very secure and on older systems this key length could be 56 or 40 bit. This functionality while tested and conforms with the Microsoft documentation has yet to be fully tested in an integrated environment. Once again this has not been thoroughly tested and has only passed unit tests and their expections.
```python
+import base64
import socket
-from ntlm_auth.ntlm import Ntlm
+from ntlm_auth.ntlm import NtlmContext
-user_name = 'User'
+username = 'User'
password = 'Password'
-domain_name = 'Domain' # Can be blank if you are not in a domain
+domain = 'Domain' # Can be blank if you are not in a domain
workstation = socket.gethostname().upper() # Can be blank if you wish to not send this info
-msg_data = "Message to send to the server"
-server_certificate_hash = '96B2FC1EC30792619286A0C7FD62863E81A6564E72829CBC0A46F7B1D5D92A18' # Can be blank if you don't want CBT sent
-ntlm_context = Ntlm()
-negotiate_message = ntlm_context.create_negotiate_message(domain_name, workstation).decode()
+# create the CBT struct if you wish to bind it with the auth response
+server_certificate_hash = '96B2FC1EC30792619286A0C7FD62863E81A6564E72829CBC0A46F7B1D5D92A18'
+certificate_digest = base64.b16decode(server_certificate_hash)
+cbt_data = GssChannelBindingsStruct()
+cbt_data[cbt_data.APPLICATION_DATA] = b'tls-server-end-point:' + certificate_digest
+
+ntlm_context = NtlmContext(username, password, domain, workstation, cbt_data, ntlm_compatibility=3)
+negotiate_message = ntlm_context.step()
# Attach the negotiate_message to your NTLM/NEGOTIATE HTTP header and send to the server. Get the challenge response back from the server
challenge_message = http.response.headers['HEADERFIELD']
-authenticate_message = ntlm_context.create_authenticate_message(user_name, password, domain_name, workstation, server_certificate_hash).decode()
+authenticate_message = ntlm_context.step(challenge_message)
-if ntlm_context.session_security is None:
- raise Exception("Server does not support signing and sealing")
-else:
- session_security = ntlm_context.session_security
+# Attach the authenticate_message ot your NTLM_NEGOTIATE HTTP header and send to the server. You are now authenticated with NTLMv1
-# Encrypt the msg with the sealing function and send the message
-msg_data, msg_signature = session_security.wrap(msg_data)
+# Encrypt the message with the wrapping function and send the message
+enc_message = ntlm_context.wrap("Message to send", encrypt=True)
request.body = msg_data
-request.header = "NTLM %s" % authenticate_message
request.send
-# Receive the response the from the server
-response_msg = response.body[bodyindex]
-response_signature = response.body[signatureindex]
-response_msg = session_security.unwrap(response_msg, response_signature)
+# Receive the response from the server and decrypt
+response_msg = response.content
+response = ntlm_context.unwrap(response_msg)
```
Backlog
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ntlm-auth-1.1.0/appveyor.yml new/ntlm-auth-1.2.0/appveyor.yml
--- old/ntlm-auth-1.1.0/appveyor.yml 2018-03-06 23:38:38.000000000 +0100
+++ new/ntlm-auth-1.2.0/appveyor.yml 2018-06-07 09:32:00.000000000 +0200
@@ -13,8 +13,6 @@
# https://www.appveyor.com/docs/installed-software/#python
- PYTHON: Python27
- PYTHON: Python27-x64
- - PYTHON: Python33
- - PYTHON: Python33-x64
- PYTHON: Python34
- PYTHON: Python34-x64
- PYTHON: Python35
@@ -27,7 +25,6 @@
init:
- ps: |
- $ErrorActionPreference = "Stop"
# Override default Python version/architecture
$env:Path="C:\$env:PYTHON;C:\$env:PYTHON\Scripts;$env:PATH"
python -c "import platform; print('Python', platform.python_version(), platform.architecture()[0])"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ntlm-auth-1.1.0/ntlm_auth/compute_response.py new/ntlm-auth-1.2.0/ntlm_auth/compute_response.py
--- old/ntlm-auth-1.1.0/ntlm_auth/compute_response.py 2018-03-06 23:38:38.000000000 +0100
+++ new/ntlm-auth-1.2.0/ntlm_auth/compute_response.py 2018-06-07 09:32:00.000000000 +0200
@@ -8,6 +8,7 @@
import os
import struct
import time
+import warnings
import ntlm_auth.compute_hash as comphash
import ntlm_auth.compute_keys as compkeys
@@ -101,7 +102,7 @@
return response
def get_nt_challenge_response(self, lm_challenge_response,
- server_certificate_hash):
+ server_certificate_hash=None, cbt_data=None):
"""
[MS-NLMP] v28.0 2016-07-14
@@ -117,10 +118,10 @@
:param lm_challenge_response: The LmChallengeResponse calculated
beforehand, used to get the key_exchange_key value
- :param server_certificate_hash: The SHA256 hash of the server
- certificate (DER encoded) NTLM is authenticated to. Used in Channel
- Binding Tokens if present, default value is None. See
- AuthenticateMessage in messages.py for more details
+ :param server_certificate_hash: This is deprecated and will be removed
+ in a future version, use cbt_data instead
+ :param cbt_data: The GssChannelBindingsStruct to bind in the NTLM
+ response
:return response: (NtChallengeResponse) - The NT response to the server
challenge. Computed by the client
:return session_base_key: (SessionBaseKey) - A session key calculated
@@ -178,11 +179,21 @@
target_info[AvId.MSV_AV_FLAGS] = \
struct.pack("
+# MIT License (see LICENSE or https://opensource.org/licenses/MIT)
+
+
+class NoAuthContextError(Exception):
+ pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ntlm-auth-1.1.0/ntlm_auth/messages.py new/ntlm-auth-1.2.0/ntlm_auth/messages.py
--- old/ntlm-auth-1.1.0/ntlm_auth/messages.py 2018-03-06 23:38:38.000000000 +0100
+++ new/ntlm-auth-1.2.0/ntlm_auth/messages.py 2018-06-07 09:32:00.000000000 +0200
@@ -254,7 +254,7 @@
def __init__(self, user_name, password, domain_name, workstation,
challenge_message, ntlm_compatibility,
- server_certificate_hash):
+ server_certificate_hash=None, cbt_data=None):
"""
[MS-NLMP] v28.0 2016-07-14
@@ -276,13 +276,9 @@
:param ntlm_compatibility: The Lan Manager Compatibility Level, used to
determine what NTLM auth version to use, see Ntlm in ntlm.py for
more details
- :param server_certificate_hash: The SHA256 hash string of the server
- certificate (DER encoded) NTLM is authenticating to. This is used
- to add to the gss_channel_bindings_struct for Channel Binding
- Tokens support. If none is passed through then ntlm-auth will not
- use Channel Binding Tokens when authenticating with the server
- which could cause issues if it is set to only authenticate when
- these are present. This is only used for NTLMv2 authentication.
+ :param server_certificate_hash: Deprecated, used cbt_data instead
+ :param cbt_data: The GssChannelBindingsStruct that contains the CBT
+ data to bind in the auth response
Message Attributes (Attributes used to compute the message structure):
signature: An 8-byte character array that MUST contain the ASCII
@@ -351,7 +347,7 @@
compute_response.get_lm_challenge_response()
self.nt_challenge_response, key_exchange_key, target_info = \
compute_response.get_nt_challenge_response(
- self.lm_challenge_response, server_certificate_hash)
+ self.lm_challenge_response, server_certificate_hash, cbt_data)
self.target_info = target_info
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_KEY_EXCH:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ntlm-auth-1.1.0/ntlm_auth/ntlm.py new/ntlm-auth-1.2.0/ntlm_auth/ntlm.py
--- old/ntlm-auth-1.1.0/ntlm_auth/ntlm.py 2018-03-06 23:38:38.000000000 +0100
+++ new/ntlm-auth-1.2.0/ntlm_auth/ntlm.py 2018-06-07 09:32:00.000000000 +0200
@@ -3,22 +3,34 @@
import base64
import struct
+import warnings
+
from ntlm_auth.constants import NegotiateFlags
+from ntlm_auth.exceptions import NoAuthContextError
from ntlm_auth.messages import AuthenticateMessage, ChallengeMessage, \
NegotiateMessage
from ntlm_auth.session_security import SessionSecurity
-class Ntlm(object):
+class NtlmContext(object):
- def __init__(self, ntlm_compatibility=3):
+ def __init__(self, username, password, domain=None, workstation=None,
+ cbt_data=None, ntlm_compatibility=3):
"""
+ Initialises a NTLM context to use when authenticating using the NTLM
+ protocol.
Initialises the NTLM context to use when sending and receiving messages
to and from the server. You should be using this object as it supports
NTLMv2 authenticate and it easier to use than before. It also brings in
the ability to use signing and sealing with session_security and
generate a MIC structure.
+ :param username: The username to authenticate with
+ :param password: The password for the username
+ :param domain: The domain part of the username (None if n/a)
+ :param workstation: The localworkstation (None if n/a)
+ :param cbt_data: A GssChannelBindingsStruct or None to bind channel
+ data with the auth process
:param ntlm_compatibility: (Default 3)
The Lan Manager Compatibility Level to use with the auth message
This is set by an Administrator in the registry key
@@ -29,23 +41,15 @@
2 : NTLMv1 and NTLMv1 with Extended Session Security
3-5 : NTLMv2 Only
Note: Values 3 to 5 are no different from a client perspective
-
- Attributes:
- negotiate_flags: A NEGOTIATE structure that contains a set of bit
- flags. These flags are the options the client supports and are
- sent in the negotiate_message
- ntlm_compatibility: The Lan Manager Compatibility Level, same as
- the input if supplied
- negotiate_message: A NegotiateMessage object that is sent to the
- server
- challenge_message: A ChallengeMessage object that has been created
- from the server response
- authenticate_message: An AuthenticateMessage object that is sent to
- the server based on the ChallengeMessage
- session_security: A SessionSecurity structure that can be used to
- sign and seal messages sent after the authentication challenge
"""
+ self.username = username
+ self.password = password
+ self.domain = domain
+ self.workstation = workstation
+ self.cbt_data = cbt_data
+ self._server_certificate_hash = None # deprecated for backwards compat
self.ntlm_compatibility = ntlm_compatibility
+ self.complete = False
# Setting up our flags so the challenge message returns the target info
# block if supported
@@ -62,77 +66,55 @@
# Setting the message types based on the ntlm_compatibility level
self._set_ntlm_compatibility_flags(self.ntlm_compatibility)
- self.negotiate_message = None
- self.challenge_message = None
- self.authenticate_message = None
- self.session_security = None
-
- def create_negotiate_message(self, domain_name=None, workstation=None):
- """
- Create an NTLM NEGOTIATE_MESSAGE
-
- :param domain_name: The domain name of the user account we are
- authenticating with, default is None
- :param worksation: The workstation we are using to authenticate with,
- default is None
- :return: A base64 encoded string of the NEGOTIATE_MESSAGE
- """
- self.negotiate_message = NegotiateMessage(self.negotiate_flags,
- domain_name, workstation)
-
- return base64.b64encode(self.negotiate_message.get_data())
-
- def parse_challenge_message(self, msg2):
- """
- Parse the NTLM CHALLENGE_MESSAGE from the server and add it to the Ntlm
- context fields
-
- :param msg2: A base64 encoded string of the CHALLENGE_MESSAGE
- """
- msg2 = base64.b64decode(msg2)
- self.challenge_message = ChallengeMessage(msg2)
-
- def create_authenticate_message(self, user_name, password,
- domain_name=None, workstation=None,
- server_certificate_hash=None):
- """
- Create an NTLM AUTHENTICATE_MESSAGE based on the Ntlm context and the
- previous messages sent and received
-
- :param user_name: The user name of the user we are trying to
- authenticate with
- :param password: The password of the user we are trying to authenticate
- with
- :param domain_name: The domain name of the user account we are
- authenticated with, default is None
- :param workstation: The workstation we are using to authenticate with,
- default is None
- :param server_certificate_hash: The SHA256 hash string of the server
- certificate (DER encoded) NTLM is authenticating to. Used for
- Channel Binding Tokens. If nothing is supplied then the CBT hash
- will not be sent. See messages.py AuthenticateMessage for more
- details
- :return: A base64 encoded string of the AUTHENTICATE_MESSAGE
- """
- self.authenticate_message = \
- AuthenticateMessage(user_name, password, domain_name, workstation,
- self.challenge_message,
- self.ntlm_compatibility,
- server_certificate_hash)
- self.authenticate_message.add_mic(self.negotiate_message,
- self.challenge_message)
-
- # Setups up the session_security context used to sign and seal messages
- # if wanted
- if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SEAL or \
- self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SIGN:
- flags = self.authenticate_message.negotiate_flags
- flag_bytes = struct.unpack("= 0) and (ntlm_compatibility <= 5):
@@ -150,3 +132,86 @@
else:
raise Exception("Unknown ntlm_compatibility level - "
"expecting value between 0 and 5")
+
+
+# Deprecated in favour of NtlmContext - this current class is heavily geared
+# towards a HTTP API which is not always the case with NTLM. This is currently
+# just a thin wrapper over NtlmContext and will be removed in future ntlm-auth
+# versions
+class Ntlm(object):
+
+ def __init__(self, ntlm_compatibility=3):
+ self._context = NtlmContext(None, None,
+ ntlm_compatibility=ntlm_compatibility)
+ self._challenge_token = None
+ warnings.warn("Using Ntlm() is deprecated and will be removed in a "
+ "future version. Use NtlmContext() instead",
+ DeprecationWarning)
+
+ @property
+ def negotiate_flags(self):
+ return self._context.negotiate_flags
+
+ @negotiate_flags.setter
+ def negotiate_flags(self, value):
+ self._context.negotiate_flags = value
+
+ @property
+ def ntlm_compatibility(self):
+ return self._context.ntlm_compatibility
+
+ @ntlm_compatibility.setter
+ def ntlm_compatibility(self, value):
+ self._context.ntlm_compatibility = value
+
+ @property
+ def negotiate_message(self):
+ return self._context._negotiate_message
+
+ @negotiate_message.setter
+ def negotiate_message(self, value):
+ self._context._negotiate_message = value
+
+ @property
+ def challenge_message(self):
+ return self._context._challenge_message
+
+ @challenge_message.setter
+ def challenge_message(self, value):
+ self._context._challenge_message = value
+
+ @property
+ def authenticate_message(self):
+ return self._context._authenticate_message
+
+ @authenticate_message.setter
+ def authenticate_message(self, value):
+ self._context._authenticate_message = value
+
+ @property
+ def session_security(self):
+ return self._context._session_security
+
+ @session_security.setter
+ def session_security(self, value):
+ self._context._session_security = value
+
+ def create_negotiate_message(self, domain_name=None, workstation=None):
+ self._context.domain = domain_name
+ self._context.workstation = workstation
+ msg = self._context.step()
+ return base64.b64encode(msg)
+
+ def parse_challenge_message(self, msg2):
+ self._challenge_token = base64.b64decode(msg2)
+
+ def create_authenticate_message(self, user_name, password,
+ domain_name=None, workstation=None,
+ server_certificate_hash=None):
+ self._context.username = user_name
+ self._context.password = password
+ self._context.domain = domain_name
+ self._context.workstation = workstation
+ self._context._server_certificate_hash = server_certificate_hash
+ msg = self._context.step(self._challenge_token)
+ return base64.b64encode(msg)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ntlm-auth-1.1.0/ntlm_auth/session_security.py new/ntlm-auth-1.2.0/ntlm_auth/session_security.py
--- old/ntlm-auth-1.1.0/ntlm_auth/session_security.py 2018-03-06 23:38:38.000000000 +0100
+++ new/ntlm-auth-1.2.0/ntlm_auth/session_security.py 2018-06-07 09:32:00.000000000 +0200
@@ -99,6 +99,7 @@
when testing out a server sealing and unsealing
"""
self.negotiate_flags = negotiate_flags
+ self.exported_session_key = exported_session_key
self.outgoing_seq_num = 0
self.incoming_seq_num = 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ntlm-auth-1.1.0/setup.py new/ntlm-auth-1.2.0/setup.py
--- old/ntlm-auth-1.1.0/setup.py 2018-03-06 23:38:38.000000000 +0100
+++ new/ntlm-auth-1.2.0/setup.py 2018-06-07 09:32:00.000000000 +0200
@@ -13,7 +13,7 @@
setup(
name='ntlm-auth',
- version='1.1.0',
+ version='1.2.0',
packages=['ntlm_auth'],
install_requires=[],
extras_require={
@@ -35,7 +35,6 @@
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ntlm-auth-1.1.0/tests/test_compute_response.py new/ntlm-auth-1.2.0/tests/test_compute_response.py
--- old/ntlm-auth-1.1.0/tests/test_compute_response.py 2018-03-06 23:38:38.000000000 +0100
+++ new/ntlm-auth-1.2.0/tests/test_compute_response.py 2018-06-07 09:32:00.000000000 +0200
@@ -123,16 +123,6 @@
assert actual_response == expected_response
assert actual_key == expected_key
- def test_channel_bindings_value(self):
- # No example is explicitly set in MS-NLMP, using a random certificate
- # hash and checking with the expected outcome
- expected = b"\x6E\xA1\x9D\xF0\x66\xDA\x46\x22" \
- b"\x05\x1F\x9C\x4F\x92\xC6\xDF\x74"
- cert_hash = \
- "E3CA49271E5089CC48CE82109F1324F41DBEDDC29A777410C738F7868C4FF405"
- actual = ComputeResponse._get_channel_bindings_value(cert_hash)
- assert actual == expected
-
class TestChallengeResults(object):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ntlm-auth-1.1.0/tests/test_ntlm.py new/ntlm-auth-1.2.0/tests/test_ntlm.py
--- old/ntlm-auth-1.1.0/tests/test_ntlm.py 2018-03-06 23:38:38.000000000 +0100
+++ new/ntlm-auth-1.2.0/tests/test_ntlm.py 2018-06-07 09:32:00.000000000 +0200
@@ -9,10 +9,11 @@
from requests.auth import AuthBase
-from ntlm_auth.constants import AvId, MessageTypes, NegotiateFlags, \
- NTLM_SIGNATURE
-from ntlm_auth.messages import TargetInfo
-from ntlm_auth.ntlm import Ntlm
+from ntlm_auth.constants import NegotiateFlags
+from ntlm_auth.exceptions import NoAuthContextError
+from ntlm_auth.gss_channel_bindings import GssChannelBindingsStruct
+from ntlm_auth.ntlm import Ntlm, NtlmContext
+from ntlm_auth.session_security import SessionSecurity
default_negotiate_flags = NegotiateFlags.NTLMSSP_NEGOTIATE_TARGET_INFO | \
NegotiateFlags.NTLMSSP_NEGOTIATE_128 | \
@@ -130,56 +131,6 @@
actual = ntlm_context.create_negotiate_message("Domain", "COMPUTER")
assert actual == expected
- def test_parse_challenge_message(self):
- test_target_info = TargetInfo()
- test_target_info[AvId.MSV_AV_NB_DOMAIN_NAME] = \
- "Domain".encode('utf-16-le')
- test_target_info[AvId.MSV_AV_NB_COMPUTER_NAME] = \
- "Server".encode('utf-16-le')
- test_challenge_string = base64.b64encode(
- b"\x4e\x54\x4c\x4d\x53\x53\x50\x00"
- b"\x02\x00\x00\x00\x03\x00\x0c\x00"
- b"\x38\x00\x00\x00\x33\x82\x8a\xe2"
- b"\x01\x23\x45\x67\x89\xab\xcd\xef"
- b"\x00\x00\x00\x00\x00\x00\x00\x00"
- b"\x24\x00\x24\x00\x44\x00\x00\x00"
- b"\x06\x00\x70\x17\x00\x00\x00\x0f"
- b"\x53\x00\x65\x00\x72\x00\x76\x00"
- b"\x65\x00\x72\x00\x02\x00\x0c\x00"
- b"\x44\x00\x6f\x00\x6d\x00\x61\x00"
- b"\x69\x00\x6e\x00\x01\x00\x0c\x00"
- b"\x53\x00\x65\x00\x72\x00\x76\x00"
- b"\x65\x00\x72\x00\x00\x00\x00\x00"
- )
- test_ntlm_context = Ntlm()
- test_ntlm_context.parse_challenge_message(test_challenge_string)
-
- expected_message_type = MessageTypes.NTLM_CHALLENGE
- expected_negotiate_flags = 3800728115
- expected_server_challenge = b"\x01\x23\x45\x67\x89\xab\xcd\xef"
- expected_signature = NTLM_SIGNATURE
- expected_target_info = test_target_info.pack()
- expected_target_name = None
- expected_version = 1080863910962135046
-
- actual = test_ntlm_context.challenge_message
-
- actual_message_type = actual.message_type
- actual_negotiate_flags = actual.negotiate_flags
- actual_server_challenge = actual.server_challenge
- actual_signature = actual.signature
- actual_target_info = actual.target_info.pack()
- actual_target_name = actual.target_name
- actual_version = actual.version
-
- assert actual_message_type == expected_message_type
- assert actual_negotiate_flags == expected_negotiate_flags
- assert actual_server_challenge == expected_server_challenge
- assert actual_signature == expected_signature
- assert actual_target_info == expected_target_info
- assert actual_target_name == expected_target_name
- assert actual_version == expected_version
-
def test_create_authenticate_message(self, monkeypatch):
monkeypatch.setattr('os.urandom', lambda s: b"\xaa" * 8)
monkeypatch.setattr('ntlm_auth.messages.get_version',
@@ -191,7 +142,7 @@
test_challenge_string = base64.b64encode(
b"\x4e\x54\x4c\x4d\x53\x53\x50\x00"
- b"\x02\x00\x00\x00\x03\x00\x0c\x00"
+ b"\x02\x00\x00\x00\x2f\x82\x88\xe2"
b"\x38\x00\x00\x00\x33\x82\x8a\xe2"
b"\x01\x23\x45\x67\x89\xab\xcd\xef"
b"\x00\x00\x00\x00\x00\x00\x00\x00"
@@ -204,16 +155,10 @@
b"\x53\x00\x65\x00\x72\x00\x76\x00"
b"\x65\x00\x72\x00\x00\x00\x00\x00"
)
+
test_ntlm_context = Ntlm()
test_ntlm_context.create_negotiate_message("Domain", "COMPUTER")
test_ntlm_context.parse_challenge_message(test_challenge_string)
- # Need to override the flags in the challenge message to match the
- # expectation, these flags are inconsequential and are done manually
- # for sanity
- test_ntlm_context.challenge_message.negotiate_flags -= \
- NegotiateFlags.NTLMSSP_TARGET_TYPE_SERVER
- test_ntlm_context.challenge_message.negotiate_flags |= \
- NegotiateFlags.NTLMSSP_REQUEST_TARGET
expected_message = base64.b64encode(
b"\x4e\x54\x4c\x4d\x53\x53\x50\x00"
@@ -223,7 +168,7 @@
b"\x48\x00\x00\x00\x08\x00\x08\x00"
b"\x54\x00\x00\x00\x10\x00\x10\x00"
b"\x5c\x00\x00\x00\x10\x00\x10\x00"
- b"\xd8\x00\x00\x00\x35\x82\x88\xe2"
+ b"\xd8\x00\x00\x00\x31\x82\x8a\xe2"
b"\x05\x01\x28\x0a\x00\x00\x00\x0f"
b"\x44\x00\x6f\x00\x6d\x00\x61\x00"
b"\x69\x00\x6e\x00\x55\x00\x73\x00"
@@ -250,6 +195,7 @@
actual_message = \
test_ntlm_context.create_authenticate_message("User", "Password",
"Domain", "COMPUTER")
+
actual_session_security = test_ntlm_context.session_security
assert actual_message == expected_message
@@ -267,7 +213,7 @@
test_challenge_string = base64.b64encode(
b"\x4e\x54\x4c\x4d\x53\x53\x50\x00"
b"\x02\x00\x00\x00\x03\x00\x0c\x00"
- b"\x38\x00\x00\x00\x33\x82\x8a\xe2"
+ b"\x38\x00\x00\x00\x03\x92\x8a\xe2"
b"\x01\x23\x45\x67\x89\xab\xcd\xef"
b"\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x24\x00\x24\x00\x44\x00\x00\x00"
@@ -282,20 +228,6 @@
test_ntlm_context = Ntlm()
test_ntlm_context.create_negotiate_message("Domain", "COMPUTER")
test_ntlm_context.parse_challenge_message(test_challenge_string)
- # Need to override the sign and seal flags so they don't return a
- # security context
- test_ntlm_context.negotiate_flags -= \
- NegotiateFlags.NTLMSSP_NEGOTIATE_SIGN
- test_ntlm_context.negotiate_flags -=\
- NegotiateFlags.NTLMSSP_NEGOTIATE_SEAL
-
- # Need to override the flags in the challenge message to match the
- # expectation, these flags are inconsequential and are done manualy for
- # sanity
- test_ntlm_context.challenge_message.negotiate_flags -= \
- NegotiateFlags.NTLMSSP_TARGET_TYPE_SERVER
- test_ntlm_context.challenge_message.negotiate_flags |= \
- NegotiateFlags.NTLMSSP_REQUEST_TARGET
expected_message = base64.b64encode(
b"\x4e\x54\x4c\x4d\x53\x53\x50\x00"
@@ -305,7 +237,7 @@
b"\x48\x00\x00\x00\x08\x00\x08\x00"
b"\x54\x00\x00\x00\x10\x00\x10\x00"
b"\x5c\x00\x00\x00\x10\x00\x10\x00"
- b"\xd8\x00\x00\x00\x35\x82\x88\xe2"
+ b"\xd8\x00\x00\x00\x01\x92\x8a\xe2"
b"\x05\x01\x28\x0a\x00\x00\x00\x0f"
b"\x44\x00\x6f\x00\x6d\x00\x61\x00"
b"\x69\x00\x6e\x00\x55\x00\x73\x00"
@@ -337,6 +269,153 @@
assert actual_message == expected_message
assert actual_session_security is None
+ # now test the properties map up the the correct NtlmContext ones
+ assert test_ntlm_context.authenticate_message == \
+ test_ntlm_context._context._authenticate_message
+ test_ntlm_context.authenticate_message = b"1"
+ assert test_ntlm_context._context._authenticate_message == b"1"
+
+ assert test_ntlm_context.challenge_message == \
+ test_ntlm_context._context._challenge_message
+ test_ntlm_context.challenge_message = b"2"
+ assert test_ntlm_context._context._challenge_message == b"2"
+
+ assert test_ntlm_context.negotiate_flags == \
+ test_ntlm_context._context.negotiate_flags
+ test_ntlm_context.negotiate_flags = 1
+ assert test_ntlm_context._context.negotiate_flags == 1
+
+ assert test_ntlm_context.negotiate_message == \
+ test_ntlm_context._context._negotiate_message
+ test_ntlm_context.negotiate_message = b"3"
+ assert test_ntlm_context._context._negotiate_message == b"3"
+
+ assert test_ntlm_context.ntlm_compatibility == \
+ test_ntlm_context._context.ntlm_compatibility
+ test_ntlm_context.ntlm_compatibility = 2
+ assert test_ntlm_context._context.ntlm_compatibility == 2
+
+ assert test_ntlm_context.session_security == \
+ test_ntlm_context._context._session_security
+ test_ntlm_context.session_security = b"4"
+ assert test_ntlm_context._context._session_security == b"4"
+
+
+class TestNtlmContext(object):
+
+ def test_ntlm_context(self, monkeypatch):
+ monkeypatch.setattr('os.urandom', lambda s: b"\xaa" * 8)
+ monkeypatch.setattr('ntlm_auth.messages.get_version',
+ lambda s: b"\x05\x01\x28\x0A\x00\x00\x00\x0F")
+ monkeypatch.setattr('ntlm_auth.messages.get_random_export_session_key',
+ lambda: b"\x55" * 16)
+ monkeypatch.setattr('ntlm_auth.compute_response.get_windows_timestamp',
+ lambda: b"\x00" * 8)
+
+ import binascii
+
+ ch = 'E3CA49271E5089CC48CE82109F1324F41DBEDDC29A777410C738F7868C4FF405'
+ cbt_data = GssChannelBindingsStruct()
+ cbt_data[cbt_data.APPLICATION_DATA] = b"tls-server-end-point:" + \
+ base64.b16decode(ch)
+ ntlm_context = NtlmContext("User", "Password", "Domain", "COMPUTER",
+ cbt_data=cbt_data)
+ actual_nego = ntlm_context.step()
+ expected_nego = b"\x4e\x54\x4c\x4d\x53\x53\x50\x00" \
+ b"\x01\x00\x00\x00\x32\xb0\x88\xe2" \
+ b"\x06\x00\x06\x00\x28\x00\x00\x00" \
+ b"\x08\x00\x08\x00\x2e\x00\x00\x00" \
+ b"\x05\x01\x28\x0a\x00\x00\x00\x0f" \
+ b"\x44\x6f\x6d\x61\x69\x6e\x43\x4f" \
+ b"\x4d\x50\x55\x54\x45\x52"
+ assert actual_nego == expected_nego
+ assert not ntlm_context.complete
+
+ challenge_msg = b"\x4e\x54\x4c\x4d\x53\x53\x50\x00" \
+ b"\x02\x00\x00\x00\x2f\x82\x88\xe2" \
+ b"\x38\x00\x00\x00\x33\x82\x8a\xe2" \
+ b"\x01\x23\x45\x67\x89\xab\xcd\xef" \
+ b"\x00\x00\x00\x00\x00\x00\x00\x00" \
+ b"\x24\x00\x24\x00\x44\x00\x00\x00" \
+ b"\x06\x00\x70\x17\x00\x00\x00\x0f" \
+ b"\x53\x00\x65\x00\x72\x00\x76\x00" \
+ b"\x65\x00\x72\x00\x02\x00\x0c\x00" \
+ b"\x44\x00\x6f\x00\x6d\x00\x61\x00" \
+ b"\x69\x00\x6e\x00\x01\x00\x0c\x00" \
+ b"\x53\x00\x65\x00\x72\x00\x76\x00" \
+ b"\x65\x00\x72\x00\x00\x00\x00\x00"
+ actual_auth = ntlm_context.step(challenge_msg)
+ expected_auth = b'\x4e\x54\x4c\x4d\x53\x53\x50\x00' \
+ b'\x03\x00\x00\x00\x18\x00\x18\x00' \
+ b'\x6c\x00\x00\x00\x68\x00\x68\x00' \
+ b'\x84\x00\x00\x00\x0c\x00\x0c\x00' \
+ b'\x48\x00\x00\x00\x08\x00\x08\x00' \
+ b'\x54\x00\x00\x00\x10\x00\x10\x00' \
+ b'\x5c\x00\x00\x00\x10\x00\x10\x00' \
+ b'\xec\x00\x00\x00\x31\x82\x8a\xe2' \
+ b'\x05\x01\x28\x0a\x00\x00\x00\x0f' \
+ b'\x44\x00\x6f\x00\x6d\x00\x61\x00' \
+ b'\x69\x00\x6e\x00\x55\x00\x73\x00' \
+ b'\x65\x00\x72\x00\x43\x00\x4f\x00' \
+ b'\x4d\x00\x50\x00\x55\x00\x54\x00' \
+ b'\x45\x00\x52\x00\x86\xc3\x50\x97' \
+ b'\xac\x9c\xec\x10\x25\x54\x76\x4a' \
+ b'\x57\xcc\xcc\x19\xaa\xaa\xaa\xaa' \
+ b'\xaa\xaa\xaa\xaa\x04\x10\xc4\x7a' \
+ b'\xcf\x19\x97\x89\xde\x7f\x20\x11' \
+ b'\x95\x7a\xea\x50\x01\x01\x00\x00' \
+ b'\x00\x00\x00\x00\x00\x00\x00\x00' \
+ b'\x00\x00\x00\x00\xaa\xaa\xaa\xaa' \
+ b'\xaa\xaa\xaa\xaa\x00\x00\x00\x00' \
+ b'\x02\x00\x0c\x00\x44\x00\x6f\x00' \
+ b'\x6d\x00\x61\x00\x69\x00\x6e\x00' \
+ b'\x01\x00\x0c\x00\x53\x00\x65\x00' \
+ b'\x72\x00\x76\x00\x65\x00\x72\x00' \
+ b'\x0a\x00\x10\x00\x6e\xa1\x9d\xf0' \
+ b'\x66\xda\x46\x22\x05\x1f\x9c\x4f' \
+ b'\x92\xc6\xdf\x74\x00\x00\x00\x00' \
+ b'\x00\x00\x00\x00\xe5\x69\x95\x1d' \
+ b'\x15\xd4\x73\x5f\x49\xe1\x4c\xf9' \
+ b'\xa7\xd3\xe6\x72'
+
+ assert actual_auth == expected_auth
+ assert ntlm_context.complete
+
+ request_msg = b"test req"
+ response_msg = b"test res"
+ actual_wrapped = ntlm_context.wrap(request_msg)
+ expected_wrapped = b"\x01\x00\x00\x00\xbc\xe3\x23\xa1" \
+ b"\x72\x06\x23\x78\x00\x00\x00\x00" \
+ b"\x70\x80\x1e\x11\xfe\x6b\x3a\xad"
+ assert actual_wrapped == expected_wrapped
+
+ server_sec = SessionSecurity(
+ ntlm_context._session_security.negotiate_flags,
+ ntlm_context._session_security.exported_session_key, "server"
+ )
+ server_unwrap = server_sec.unwrap(actual_wrapped[16:],
+ actual_wrapped[0:16])
+ assert server_unwrap == request_msg
+
+ response_wrapped = server_sec.wrap(response_msg)
+
+ actual_unwrap = ntlm_context.unwrap(
+ response_wrapped[1] + response_wrapped[0]
+ )
+ assert actual_unwrap == response_msg
+
+ def test_fail_wrap_no_context(self):
+ ntlm_context = NtlmContext("", "")
+ with pytest.raises(NoAuthContextError) as err:
+ ntlm_context.wrap(b"")
+ assert str(err.value) == \
+ "Cannot wrap data as no security context has been established"
+
+ with pytest.raises(NoAuthContextError) as err:
+ ntlm_context.unwrap(b"")
+ assert str(err.value) == \
+ "Cannot unwrap data as no security context has been established"
+
class TestNtlmFunctional(object):
"""
@@ -483,7 +562,7 @@
assert actual_code == 200
assert actual_content == "contents"
- def test_ntlm_3_http_with_cbt(self, runner):
+ def test_ntlm_3_http_with_cbt_dep(self, runner):
actual = self._send_request(runner[0], runner[1], runner[2], runner[3],
81, 3)
actual_content = actual.content.decode('utf-8')
@@ -492,7 +571,7 @@
assert actual_code == 200
assert actual_content == "contents"
- def test_ntlm_3_http_without_cbt(self, runner):
+ def test_ntlm_3_http_without_cbt_dep(self, runner):
actual = self._send_request(runner[0], runner[1], runner[2], runner[3],
82, 3)
actual_content = actual.content.decode('utf-8')
@@ -501,7 +580,7 @@
assert actual_code == 200
assert actual_content == "contents"
- def test_ntlm_3_https_with_cbt(self, runner):
+ def test_ntlm_3_https_with_cbt_dep(self, runner):
actual = self._send_request(runner[0], runner[1], runner[2], runner[3],
441, 3)
actual_content = actual.content.decode('utf-8')
@@ -512,7 +591,7 @@
assert actual_code == 200
assert actual_content == "contents"
- def test_ntlm_3_https_without_cbt(self, runner):
+ def test_ntlm_3_https_without_cbt_dep(self, runner):
actual = self._send_request(runner[0], runner[1], runner[2], runner[3],
442, 3)
actual_content = actual.content.decode('utf-8')
@@ -521,8 +600,46 @@
assert actual_code == 200
assert actual_content == "contents"
+ def test_ntlm_3_http_with_cbt(self, runner):
+ actual = self._send_request(runner[0], runner[1], runner[2], runner[3],
+ 81, 3, legacy=False)
+ actual_content = actual.content.decode('utf-8')
+ actual_code = actual.status_code
+
+ assert actual_code == 200
+ assert actual_content == "contents"
+
+ def test_ntlm_3_http_without_cbt(self, runner):
+ actual = self._send_request(runner[0], runner[1], runner[2], runner[3],
+ 82, 3, legacy=False)
+ actual_content = actual.content.decode('utf-8')
+ actual_code = actual.status_code
+
+ assert actual_code == 200
+ assert actual_content == "contents"
+
+ def test_ntlm_3_https_with_cbt(self, runner):
+ actual = self._send_request(runner[0], runner[1], runner[2], runner[3],
+ 441, 3, legacy=False)
+ actual_content = actual.content.decode('utf-8')
+ actual_code = actual.status_code
+
+ # Only case where CBT should work as we are using NTLMv2 as the auth
+ # type
+ assert actual_code == 200
+ assert actual_content == "contents"
+
+ def test_ntlm_3_https_without_cbt(self, runner):
+ actual = self._send_request(runner[0], runner[1], runner[2], runner[3],
+ 442, 3, legacy=False)
+ actual_content = actual.content.decode('utf-8')
+ actual_code = actual.status_code
+
+ assert actual_code == 200
+ assert actual_content == "contents"
+
def _send_request(self, server, domain, username, password, port,
- ntlm_compatibility):
+ ntlm_compatibility, legacy=True):
"""
Sends a request to the url with the credentials specified. Returns the
final response
@@ -552,7 +669,8 @@
server, port)
session = requests.Session()
session.verify = False
- session.auth = NtlmAuth(domain, username, password, ntlm_compatibility)
+ session.auth = NtlmAuth(domain, username, password, ntlm_compatibility,
+ legacy)
request = requests.Request('GET', url)
prepared_request = session.prepare_request(request)
response = session.send(prepared_request)
@@ -563,11 +681,12 @@
# used by the functional tests to auth with an NTLM endpoint
class NtlmAuth(AuthBase):
- def __init__(self, domain, username, password, ntlm_compatibility):
+ def __init__(self, domain, username, password, ntlm_compatibility, legacy):
self.username = username
self.domain = domain.upper()
self.password = password
- self.context = Ntlm(ntlm_compatibility=ntlm_compatibility)
+ self.ntlm_compatibility = ntlm_compatibility
+ self.legacy = legacy
def __call__(self, response):
response.headers['Connection'] = 'Keep-Alive'
@@ -576,9 +695,15 @@
def hook(self, response, **kwargs):
if response.status_code == 401:
- return self.retry_with_ntlm_auth('www-authenticate',
- 'Authorization', response,
- 'NTLM', kwargs)
+ if self.legacy:
+ return self.retry_with_ntlm_auth_legacy('www-authenticate',
+ 'Authorization',
+ response, 'NTLM',
+ kwargs)
+ else:
+ return self.retry_with_ntlm_auth('www-authenticate',
+ 'Authorization', response,
+ 'NTLM', kwargs)
else:
return response
@@ -586,9 +711,59 @@
auth_type, args):
try:
cert_hash = self._get_server_cert(response)
+ cbt_data = GssChannelBindingsStruct()
+ cbt_data[cbt_data.APPLICATION_DATA] = b"tls-server-end-point:" + \
+ base64.b16decode(cert_hash)
+ except Exception:
+ cbt_data = None
+
+ context = NtlmContext(self.username, self.password, self.domain,
+ cbt_data=cbt_data,
+ ntlm_compatibility=self.ntlm_compatibility)
+
+ # Consume the original response contents and release the connection for
+ # later
+ response.content
+ response.raw.release_conn()
+
+ # Create the negotiate request
+ msg1_req = response.request.copy()
+ msg1 = context.step()
+ msg1_header = "%s %s" % (auth_type, base64.b64encode(msg1).decode())
+ msg1_req.headers[auth_header] = msg1_header
+
+ # Send the negotiate request and receive the challenge message
+ disable_stream_args = dict(args, stream=False)
+ msg2_resp = response.connection.send(msg1_req, **disable_stream_args)
+ msg2_resp.content
+ msg2_resp.raw.release_conn()
+
+ # Parse the challenge response in the ntlm_context
+ msg2_header = msg2_resp.headers[auth_header_field]
+ msg2 = msg2_header.replace(auth_type + ' ', '')
+ msg3 = context.step(base64.b64decode(msg2))
+
+ # Create the authenticate request
+ msg3_req = msg2_resp.request.copy()
+ msg3_header = auth_type + ' ' + base64.b64encode(msg3).decode()
+ msg3_req.headers[auth_header] = msg3_header
+
+ # Send the authenticate request
+ final_response = msg2_resp.connection.send(msg3_req, **args)
+ final_response.history.append(response)
+ final_response.history.append(msg2_resp)
+
+ return final_response
+
+ def retry_with_ntlm_auth_legacy(self, auth_header_field, auth_header,
+ response, auth_type, args):
+ try:
+ cert_hash = self._get_server_cert(response)
except Exception:
cert_hash = None
+ context = Ntlm(ntlm_compatibility=self.ntlm_compatibility)
+
# Consume the original response contents and release the connection for
# later
response.content
@@ -596,7 +771,7 @@
# Create the negotiate request
msg1_req = response.request.copy()
- msg1 = self.context.create_negotiate_message(self.domain)
+ msg1 = context.create_negotiate_message(self.domain)
msg1_header = "%s %s" % (auth_type, msg1.decode('ascii'))
msg1_req.headers[auth_header] = msg1_header
@@ -609,12 +784,12 @@
# Parse the challenge response in the ntlm_context
msg2_header = msg2_resp.headers[auth_header_field]
msg2 = msg2_header.replace(auth_type + ' ', '')
- self.context.parse_challenge_message(msg2)
+ context.parse_challenge_message(msg2)
# Create the authenticate request
msg3_req = msg2_resp.request.copy()
- msg3 = self.context.create_authenticate_message(
+ msg3 = context.create_authenticate_message(
self.username, self.password, self.domain,
server_certificate_hash=cert_hash
)