Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/ntlm_auth/compute_response.py: 45%
137 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-25 07:03 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-25 07:03 +0000
1# Copyright: (c) 2018, Jordan Borean (@jborean93) <jborean93@gmail.com>
2# MIT License (see LICENSE or https://opensource.org/licenses/MIT)
4import base64
5import calendar
6import hashlib
7import hmac
8import os
9import struct
10import time
12import ntlm_auth.compute_hash as comphash
13import ntlm_auth.compute_keys as compkeys
14import ntlm_auth.messages
16from ntlm_auth.des import DES
17from ntlm_auth.constants import AvId, AvFlags, NegotiateFlags
18from ntlm_auth.gss_channel_bindings import GssChannelBindingsStruct
21class ComputeResponse():
23 def __init__(self, user_name, password, domain_name, challenge_message,
24 ntlm_compatibility):
25 """
26 Constructor for the response computations. This class will compute the
27 various nt and lm challenge responses.
29 :param user_name: The user name of the user we are trying to
30 authenticate with
31 :param password: The password of the user we are trying to authenticate
32 with
33 :param domain_name: The domain name of the user account we are
34 authenticated with, default is None
35 :param challenge_message: A ChallengeMessage object that was received
36 from the server after the negotiate_message
37 :param ntlm_compatibility: The Lan Manager Compatibility Level, used to
38 determine what NTLM auth version to use, see Ntlm in ntlm.py for
39 more details
40 """
41 self._user_name = user_name
42 self._password = password
43 self._domain_name = domain_name
44 self._challenge_message = challenge_message
45 self._negotiate_flags = challenge_message.negotiate_flags
46 self._server_challenge = challenge_message.server_challenge
47 self._server_target_info = challenge_message.target_info
48 self._ntlm_compatibility = ntlm_compatibility
49 self._client_challenge = os.urandom(8)
51 def get_lm_challenge_response(self):
52 """
53 [MS-NLMP] v28.0 2016-07-14
55 3.3.1 - NTLM v1 Authentication
56 3.3.2 - NTLM v2 Authentication
58 This method returns the LmChallengeResponse key based on the
59 ntlm_compatibility chosen and the target_info supplied by the
60 CHALLENGE_MESSAGE. It is quite different from what is set in the
61 document as it combines the NTLMv1, NTLM2 and NTLMv2 methods into one
62 and calls separate methods based on the ntlm_compatibility flag chosen.
64 :return: response (LmChallengeResponse) - The LM response to the server
65 challenge. Computed by the client
66 """
67 if self._negotiate_flags & \
68 NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY and \
69 self._ntlm_compatibility < 3:
70 response = self._get_LMv1_with_session_security_response(
71 self._client_challenge
72 )
73 elif 0 <= self._ntlm_compatibility <= 1:
74 response = self._get_LMv1_response(self._password,
75 self._server_challenge)
76 elif self._ntlm_compatibility == 2:
77 # Based on the compatibility level we don't want to use LM
78 # responses, ignore the session_base_key as it is returned in nt
79 response, ignore_key = \
80 self._get_NTLMv1_response(self._password,
81 self._server_challenge)
82 else:
83 """
84 [MS-NLMP] v28.0 page 45 - 2016-07-14
86 3.1.5.12 Client Received a CHALLENGE_MESSAGE from the Server
87 If NTLMv2 authentication is used and the CHALLENGE_MESSAGE
88 TargetInfo field has an MsvAvTimestamp present, the client SHOULD
89 NOT send the LmChallengeResponse and SHOULD send Z(24) instead.
90 """
91 response = self._get_LMv2_response(self._user_name, self._password,
92 self._domain_name,
93 self._server_challenge,
94 self._client_challenge)
95 if self._server_target_info is not None:
96 timestamp = \
97 self._server_target_info[AvId.MSV_AV_TIMESTAMP]
98 if timestamp is not None:
99 response = b'\x00' * 24
101 return response
103 def get_nt_challenge_response(self, lm_challenge_response,
104 server_certificate_hash=None, cbt_data=None):
105 """
106 [MS-NLMP] v28.0 2016-07-14
108 3.3.1 - NTLM v1 Authentication
109 3.3.2 - NTLM v2 Authentication
111 This method returns the NtChallengeResponse key based on the
112 ntlm_compatibility chosen and the target_info supplied by the
113 CHALLENGE_MESSAGE. It is quite different from what is set in the
114 document as it combines the NTLMv1, NTLM2 and NTLMv2 methods into one
115 and calls separate methods based on the ntlm_compatibility value
116 chosen.
118 :param lm_challenge_response: The LmChallengeResponse calculated
119 beforehand, used to get the key_exchange_key value
120 :param server_certificate_hash: This is deprecated and will be removed
121 in a future version, use cbt_data instead
122 :param cbt_data: The GssChannelBindingsStruct to bind in the NTLM
123 response
124 :return response: (NtChallengeResponse) - The NT response to the server
125 challenge. Computed by the client
126 :return session_base_key: (SessionBaseKey) - A session key calculated
127 from the user password challenge
128 :return target_info: (AV_PAIR) - The AV_PAIR structure used in the
129 nt_challenge calculations
130 """
131 if self._negotiate_flags & \
132 NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY and \
133 self._ntlm_compatibility < 3:
134 # The compatibility level is less than 3 which means it doesn't
135 # support NTLMv2 but we want extended security so use NTLM2 which
136 # is different from NTLMv2
137 # [MS-NLMP] - 3.3.1 NTLMv1 Authentication
138 response, session_base_key = \
139 self._get_NTLM2_response(self._password,
140 self._server_challenge,
141 self._client_challenge)
142 lm_hash = comphash._lmowfv1(self._password)
143 key_exchange_key = \
144 compkeys._get_exchange_key_ntlm_v1(self._negotiate_flags,
145 session_base_key,
146 self._server_challenge,
147 lm_challenge_response,
148 lm_hash)
149 target_info = None
151 elif 0 <= self._ntlm_compatibility < 3:
152 response, session_base_key = \
153 self._get_NTLMv1_response(self._password,
154 self._server_challenge)
156 lm_hash = comphash._lmowfv1(self._password)
157 key_exchange_key = \
158 compkeys._get_exchange_key_ntlm_v1(self._negotiate_flags,
159 session_base_key,
160 self._server_challenge,
161 lm_challenge_response,
162 lm_hash)
163 target_info = None
165 else:
166 if self._server_target_info is None:
167 target_info = ntlm_auth.messages.TargetInfo()
168 else:
169 target_info = self._server_target_info
171 if target_info[AvId.MSV_AV_TIMESTAMP] is None:
172 timestamp = get_windows_timestamp()
173 else:
174 timestamp = target_info[AvId.MSV_AV_TIMESTAMP]
176 # [MS-NLMP] If the CHALLENGE_MESSAGE TargetInfo field has an
177 # MsvAvTimestamp present, the client SHOULD provide a MIC
178 target_info[AvId.MSV_AV_FLAGS] = \
179 struct.pack("<L", AvFlags.MIC_PROVIDED)
181 if server_certificate_hash is not None and cbt_data is None:
182 # Older method of creating CBT struct based on the cert hash.
183 # This should be avoided in favour of an explicit
184 # GssChannelBindingStruct being passed in.
185 certificate_digest = base64.b16decode(server_certificate_hash)
187 cbt_data = GssChannelBindingsStruct()
188 cbt_data[cbt_data.APPLICATION_DATA] = \
189 b'tls-server-end-point:' + certificate_digest
191 if cbt_data is not None:
192 cbt_bytes = cbt_data.get_data()
193 cbt_hash = hashlib.md5(cbt_bytes).digest()
194 target_info[AvId.MSV_AV_CHANNEL_BINDINGS] = cbt_hash
196 response, session_base_key = \
197 self._get_NTLMv2_response(self._user_name, self._password,
198 self._domain_name,
199 self._server_challenge,
200 self._client_challenge,
201 timestamp, target_info)
203 key_exchange_key = \
204 compkeys._get_exchange_key_ntlm_v2(session_base_key)
206 return response, key_exchange_key, target_info
208 @staticmethod
209 def _get_LMv1_response(password, server_challenge):
210 """
211 [MS-NLMP] v28.0 2016-07-14
213 2.2.2.3 LM_RESPONSE
214 The LM_RESPONSE structure defines the NTLM v1 authentication
215 LmChallengeResponse in the AUTHENTICATE_MESSAGE. This response is used
216 only when NTLM v1 authentication is configured.
218 :param password: The password of the user we are trying to authenticate
219 with
220 :param server_challenge: A random 8-byte response generated by the
221 server in the CHALLENGE_MESSAGE
222 :return response: LmChallengeResponse to the server challenge
223 """
224 lm_hash = comphash._lmowfv1(password)
225 response = ComputeResponse._calc_resp(lm_hash, server_challenge)
227 return response
229 @staticmethod
230 def _get_LMv1_with_session_security_response(client_challenge):
231 """
232 [MS-NLMP] v28.0 2016-07-14
234 2.2.2.3 LM_RESPONSE
235 The LM_RESPONSE structure defines the NTLM v1 authentication
236 LmChallengeResponse in the AUTHENTICATE_MESSAGE. This response is used
237 only when NTLM v1 authentication is configured and
238 NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY is flages.
240 :param client_challenge: A random 8-byte response generated by the
241 client for the AUTHENTICATE_MESSAGE
242 :return response: LmChallengeResponse to the server challenge
243 """
245 response = client_challenge + b'\x00' * 16
247 return response
249 @staticmethod
250 def _get_LMv2_response(user_name, password, domain_name, server_challenge,
251 client_challenge):
252 """
253 [MS-NLMP] v28.0 2016-07-14
255 2.2.2.4 LMv2_RESPONSE
256 The LMv2_RESPONSE structure defines the NTLM v2 authentication
257 LmChallengeResponse in the AUTHENTICATE_MESSAGE. This response is used
258 only when NTLM v2 authentication is configured.
260 :param user_name: The user name of the user we are trying to
261 authenticate with
262 :param password: The password of the user we are trying to authenticate
263 with
264 :param domain_name: The domain name of the user account we are
265 authenticated with
266 :param server_challenge: A random 8-byte response generated by the
267 server in the CHALLENGE_MESSAGE
268 :param client_challenge: A random 8-byte response generated by the
269 client for the AUTHENTICATE_MESSAGE
270 :return response: LmChallengeResponse to the server challenge
271 """
272 nt_hash = comphash._ntowfv2(user_name, password, domain_name)
273 challenge = server_challenge + client_challenge
274 lm_hash = hmac.new(nt_hash, challenge, digestmod=hashlib.md5).digest()
275 response = lm_hash + client_challenge
277 return response
279 @staticmethod
280 def _get_NTLMv1_response(password, server_challenge):
281 """
282 [MS-NLMP] v28.0 2016-07-14
284 2.2.2.6 NTLM v1 Response: NTLM_RESPONSE
285 The NTLM_RESPONSE strucutre defines the NTLM v1 authentication
286 NtChallengeResponse in the AUTHENTICATE_MESSAGE. This response is only
287 used when NTLM v1 authentication is configured.
289 :param password: The password of the user we are trying to authenticate
290 with
291 :param server_challenge: A random 8-byte response generated by the
292 server in the CHALLENGE_MESSAGE
293 :return response: NtChallengeResponse to the server_challenge
294 :return session_base_key: A session key calculated from the user
295 password challenge
296 """
297 ntlm_hash = comphash._ntowfv1(password)
298 response = ComputeResponse._calc_resp(ntlm_hash, server_challenge)
300 session_base_key = hashlib.new('md4', ntlm_hash).digest()
302 return response, session_base_key
304 @staticmethod
305 def _get_NTLM2_response(password, server_challenge, client_challenge):
306 """
307 [MS-NLMP] v28.0 2016-07-14
309 This name is really misleading as it isn't NTLM v2 authentication
310 rather this authentication is only used when the ntlm_compatibility
311 level is set to a value < 3 (No NTLMv2 auth) but the
312 NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY flag is set in the negotiate
313 flags section. The documentation for computing this value is on page 56
314 under section 3.3.1 NTLM v1 Authentication
316 :param password: The password of the user we are trying to authenticate
317 with
318 :param server_challenge: A random 8-byte response generated by the
319 server in the CHALLENGE_MESSAGE
320 :param client_challenge: A random 8-byte response generated by the
321 client for the AUTHENTICATE_MESSAGE
322 :return response: NtChallengeResponse to the server_challenge
323 :return session_base_key: A session key calculated from the user
324 password challenge
325 """
326 ntlm_hash = comphash._ntowfv1(password)
327 challenge = server_challenge + client_challenge
328 nt_session_hash = hashlib.md5(challenge).digest()[:8]
329 response = ComputeResponse._calc_resp(ntlm_hash, nt_session_hash[0:8])
331 session_base_key = hashlib.new('md4', ntlm_hash).digest()
333 return response, session_base_key
335 @staticmethod
336 def _get_NTLMv2_response(user_name, password, domain_name,
337 server_challenge, client_challenge, timestamp,
338 target_info):
339 """
340 [MS-NLMP] v28.0 2016-07-14
342 2.2.2.8 NTLM V2 Response: NTLMv2_RESPONSE
343 The NTLMv2_RESPONSE strucutre defines the NTLMv2 authentication
344 NtChallengeResponse in the AUTHENTICATE_MESSAGE. This response is used
345 only when NTLMv2 authentication is configured.
347 The guide on how this is computed is in 3.3.2 NTLM v2 Authentication.
349 :param user_name: The user name of the user we are trying to
350 authenticate with
351 :param password: The password of the user we are trying to authenticate
352 with
353 :param domain_name: The domain name of the user account we are
354 authenticated with
355 :param server_challenge: A random 8-byte response generated by the
356 server in the CHALLENGE_MESSAGE
357 :param client_challenge: A random 8-byte response generated by the
358 client for the AUTHENTICATE_MESSAGE
359 :param timestamp: An 8-byte timestamp in windows format, 100
360 nanoseconds since 1601-01-01
361 :param target_info: The target_info structure from the
362 CHALLENGE_MESSAGE with the CBT attached if required
363 :return response: NtChallengeResponse to the server_challenge
364 :return session_base_key: A session key calculated from the user
365 password challenge
366 """
368 nt_hash = comphash._ntowfv2(user_name, password, domain_name)
369 temp = ComputeResponse._get_NTLMv2_temp(timestamp, client_challenge,
370 target_info)
371 nt_proof_str = hmac.new(nt_hash,
372 (server_challenge + temp),
373 digestmod=hashlib.md5).digest()
374 response = nt_proof_str + temp
376 session_base_key = hmac.new(nt_hash, nt_proof_str,
377 digestmod=hashlib.md5).digest()
379 return response, session_base_key
381 @staticmethod
382 def _get_NTLMv2_temp(timestamp, client_challenge, target_info):
383 """
384 [MS-NLMP] v28.0 2016-07-14
386 2.2.2.7 NTLMv2_CLIENT_CHALLENGE - variable length
387 The NTLMv2_CLIENT_CHALLENGE structure defines the client challenge in
388 the AUTHENTICATE_MESSAGE. This structure is used only when NTLM v2
389 authentication is configured and is transported in the NTLMv2_RESPONSE
390 structure.
392 The method to create this structure is defined in 3.3.2 NTLMv2
393 Authentication. In this method this variable is known as the temp
394 value. The target_info variable corresponds to the ServerName variable
395 used in that documentation. This is in reality a lot more than just the
396 ServerName and contains the AV_PAIRS structure we need to transport
397 with the message like Channel Binding tokens and others. By default
398 this will be the target_info returned from the CHALLENGE_MESSAGE plus
399 MSV_AV_CHANNEL_BINDINGS if specified otherwise it is a new target_info
400 set with MSV_AV_TIMESTAMP to the current time.
402 :param timestamp: An 8-byte timestamp in windows format, 100
403 nanoseconds since 1601-01-01
404 :param client_challenge: A random 8-byte response generated by the
405 `client for the AUTHENTICATE_MESSAGE
406 :param target_info: The target_info structure from the
407 CHALLENGE_MESSAGE with the CBT attached if required
408 :return temp: The CLIENT_CHALLENGE structure that will be added to the
409 NtChallengeResponse structure
410 """
411 resp_type = b'\x01'
412 hi_resp_type = b'\x01'
413 reserved1 = b'\x00' * 2
414 reserved2 = b'\x00' * 4
415 reserved3 = b'\x00' * 4
416 # This byte is not in the structure defined in 2.2.2.7 but is in the
417 # computation guide, works with it present
418 reserved4 = b'\x00' * 4
420 temp = resp_type
421 temp += hi_resp_type
422 temp += reserved1
423 temp += reserved2
424 temp += timestamp
425 temp += client_challenge
426 temp += reserved3
427 temp += target_info.pack()
428 temp += reserved4
430 return temp
432 @staticmethod
433 def _calc_resp(password_hash, server_challenge):
434 """
435 Generate the LM response given a 16-byte password hash and the
436 challenge from the CHALLENGE_MESSAGE
438 :param password_hash: A 16-byte password hash
439 :param server_challenge: A random 8-byte response generated by the
440 server in the CHALLENGE_MESSAGE
441 :return res: A 24-byte buffer to contain the LM response upon return
442 """
443 # padding with zeros to make the hash 21 bytes long
444 password_hash += b'\x00' * (21 - len(password_hash))
446 res = b''
447 dobj = DES(DES.key56_to_key64(password_hash[0:7]))
448 res = res + dobj.encrypt(server_challenge[0:8])
450 dobj = DES(DES.key56_to_key64(password_hash[7:14]))
451 res = res + dobj.encrypt(server_challenge[0:8])
453 dobj = DES(DES.key56_to_key64(password_hash[14:21]))
454 res = res + dobj.encrypt(server_challenge[0:8])
455 return res
458def get_windows_timestamp():
459 # Get Windows Date time, 100 nanoseconds since 1601-01-01 in a 64 bit
460 # structure
461 seconds_since_origin = 116444736000 + calendar.timegm(time.gmtime())
462 timestamp = struct.pack('<q', seconds_since_origin * 10000000)
464 return timestamp