microproduct/atmosphericDelay/ISCEApp/site-packages/ntlm_auth/compute_response.py

465 lines
20 KiB
Python
Raw Normal View History

2023-08-28 10:17:29 +00:00
# Copyright: (c) 2018, Jordan Borean (@jborean93) <jborean93@gmail.com>
# MIT License (see LICENSE or https://opensource.org/licenses/MIT)
import base64
import calendar
import hashlib
import hmac
import os
import struct
import time
import ntlm_auth.compute_hash as comphash
import ntlm_auth.compute_keys as compkeys
import ntlm_auth.messages
from ntlm_auth.des import DES
from ntlm_auth.constants import AvId, AvFlags, NegotiateFlags
from ntlm_auth.gss_channel_bindings import GssChannelBindingsStruct
class ComputeResponse():
def __init__(self, user_name, password, domain_name, challenge_message,
ntlm_compatibility):
"""
Constructor for the response computations. This class will compute the
various nt and lm challenge responses.
: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 challenge_message: A ChallengeMessage object that was received
from the server after the negotiate_message
: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
"""
self._user_name = user_name
self._password = password
self._domain_name = domain_name
self._challenge_message = challenge_message
self._negotiate_flags = challenge_message.negotiate_flags
self._server_challenge = challenge_message.server_challenge
self._server_target_info = challenge_message.target_info
self._ntlm_compatibility = ntlm_compatibility
self._client_challenge = os.urandom(8)
def get_lm_challenge_response(self):
"""
[MS-NLMP] v28.0 2016-07-14
3.3.1 - NTLM v1 Authentication
3.3.2 - NTLM v2 Authentication
This method returns the LmChallengeResponse key based on the
ntlm_compatibility chosen and the target_info supplied by the
CHALLENGE_MESSAGE. It is quite different from what is set in the
document as it combines the NTLMv1, NTLM2 and NTLMv2 methods into one
and calls separate methods based on the ntlm_compatibility flag chosen.
:return: response (LmChallengeResponse) - The LM response to the server
challenge. Computed by the client
"""
if self._negotiate_flags & \
NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY and \
self._ntlm_compatibility < 3:
response = self._get_LMv1_with_session_security_response(
self._client_challenge
)
elif 0 <= self._ntlm_compatibility <= 1:
response = self._get_LMv1_response(self._password,
self._server_challenge)
elif self._ntlm_compatibility == 2:
# Based on the compatibility level we don't want to use LM
# responses, ignore the session_base_key as it is returned in nt
response, ignore_key = \
self._get_NTLMv1_response(self._password,
self._server_challenge)
else:
"""
[MS-NLMP] v28.0 page 45 - 2016-07-14
3.1.5.12 Client Received a CHALLENGE_MESSAGE from the Server
If NTLMv2 authentication is used and the CHALLENGE_MESSAGE
TargetInfo field has an MsvAvTimestamp present, the client SHOULD
NOT send the LmChallengeResponse and SHOULD send Z(24) instead.
"""
response = self._get_LMv2_response(self._user_name, self._password,
self._domain_name,
self._server_challenge,
self._client_challenge)
if self._server_target_info is not None:
timestamp = \
self._server_target_info[AvId.MSV_AV_TIMESTAMP]
if timestamp is not None:
response = b'\x00' * 24
return response
def get_nt_challenge_response(self, lm_challenge_response,
server_certificate_hash=None, cbt_data=None):
"""
[MS-NLMP] v28.0 2016-07-14
3.3.1 - NTLM v1 Authentication
3.3.2 - NTLM v2 Authentication
This method returns the NtChallengeResponse key based on the
ntlm_compatibility chosen and the target_info supplied by the
CHALLENGE_MESSAGE. It is quite different from what is set in the
document as it combines the NTLMv1, NTLM2 and NTLMv2 methods into one
and calls separate methods based on the ntlm_compatibility value
chosen.
:param lm_challenge_response: The LmChallengeResponse calculated
beforehand, used to get the key_exchange_key value
: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
from the user password challenge
:return target_info: (AV_PAIR) - The AV_PAIR structure used in the
nt_challenge calculations
"""
if self._negotiate_flags & \
NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY and \
self._ntlm_compatibility < 3:
# The compatibility level is less than 3 which means it doesn't
# support NTLMv2 but we want extended security so use NTLM2 which
# is different from NTLMv2
# [MS-NLMP] - 3.3.1 NTLMv1 Authentication
response, session_base_key = \
self._get_NTLM2_response(self._password,
self._server_challenge,
self._client_challenge)
lm_hash = comphash._lmowfv1(self._password)
key_exchange_key = \
compkeys._get_exchange_key_ntlm_v1(self._negotiate_flags,
session_base_key,
self._server_challenge,
lm_challenge_response,
lm_hash)
target_info = None
elif 0 <= self._ntlm_compatibility < 3:
response, session_base_key = \
self._get_NTLMv1_response(self._password,
self._server_challenge)
lm_hash = comphash._lmowfv1(self._password)
key_exchange_key = \
compkeys._get_exchange_key_ntlm_v1(self._negotiate_flags,
session_base_key,
self._server_challenge,
lm_challenge_response,
lm_hash)
target_info = None
else:
if self._server_target_info is None:
target_info = ntlm_auth.messages.TargetInfo()
else:
target_info = self._server_target_info
if target_info[AvId.MSV_AV_TIMESTAMP] is None:
timestamp = get_windows_timestamp()
else:
timestamp = target_info[AvId.MSV_AV_TIMESTAMP]
# [MS-NLMP] If the CHALLENGE_MESSAGE TargetInfo field has an
# MsvAvTimestamp present, the client SHOULD provide a MIC
target_info[AvId.MSV_AV_FLAGS] = \
struct.pack("<L", AvFlags.MIC_PROVIDED)
if server_certificate_hash is not None and cbt_data is None:
# Older method of creating CBT struct based on the cert hash.
# This should be avoided in favour of an explicit
# GssChannelBindingStruct being passed in.
certificate_digest = base64.b16decode(server_certificate_hash)
cbt_data = GssChannelBindingsStruct()
cbt_data[cbt_data.APPLICATION_DATA] = \
b'tls-server-end-point:' + certificate_digest
if cbt_data is not None:
cbt_bytes = cbt_data.get_data()
cbt_hash = hashlib.md5(cbt_bytes).digest()
target_info[AvId.MSV_AV_CHANNEL_BINDINGS] = cbt_hash
response, session_base_key = \
self._get_NTLMv2_response(self._user_name, self._password,
self._domain_name,
self._server_challenge,
self._client_challenge,
timestamp, target_info)
key_exchange_key = \
compkeys._get_exchange_key_ntlm_v2(session_base_key)
return response, key_exchange_key, target_info
@staticmethod
def _get_LMv1_response(password, server_challenge):
"""
[MS-NLMP] v28.0 2016-07-14
2.2.2.3 LM_RESPONSE
The LM_RESPONSE structure defines the NTLM v1 authentication
LmChallengeResponse in the AUTHENTICATE_MESSAGE. This response is used
only when NTLM v1 authentication is configured.
:param password: The password of the user we are trying to authenticate
with
:param server_challenge: A random 8-byte response generated by the
server in the CHALLENGE_MESSAGE
:return response: LmChallengeResponse to the server challenge
"""
lm_hash = comphash._lmowfv1(password)
response = ComputeResponse._calc_resp(lm_hash, server_challenge)
return response
@staticmethod
def _get_LMv1_with_session_security_response(client_challenge):
"""
[MS-NLMP] v28.0 2016-07-14
2.2.2.3 LM_RESPONSE
The LM_RESPONSE structure defines the NTLM v1 authentication
LmChallengeResponse in the AUTHENTICATE_MESSAGE. This response is used
only when NTLM v1 authentication is configured and
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY is flages.
:param client_challenge: A random 8-byte response generated by the
client for the AUTHENTICATE_MESSAGE
:return response: LmChallengeResponse to the server challenge
"""
response = client_challenge + b'\x00' * 16
return response
@staticmethod
def _get_LMv2_response(user_name, password, domain_name, server_challenge,
client_challenge):
"""
[MS-NLMP] v28.0 2016-07-14
2.2.2.4 LMv2_RESPONSE
The LMv2_RESPONSE structure defines the NTLM v2 authentication
LmChallengeResponse in the AUTHENTICATE_MESSAGE. This response is used
only when NTLM v2 authentication is configured.
: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
:param server_challenge: A random 8-byte response generated by the
server in the CHALLENGE_MESSAGE
:param client_challenge: A random 8-byte response generated by the
client for the AUTHENTICATE_MESSAGE
:return response: LmChallengeResponse to the server challenge
"""
nt_hash = comphash._ntowfv2(user_name, password, domain_name)
challenge = server_challenge + client_challenge
lm_hash = hmac.new(nt_hash, challenge, digestmod=hashlib.md5).digest()
response = lm_hash + client_challenge
return response
@staticmethod
def _get_NTLMv1_response(password, server_challenge):
"""
[MS-NLMP] v28.0 2016-07-14
2.2.2.6 NTLM v1 Response: NTLM_RESPONSE
The NTLM_RESPONSE strucutre defines the NTLM v1 authentication
NtChallengeResponse in the AUTHENTICATE_MESSAGE. This response is only
used when NTLM v1 authentication is configured.
:param password: The password of the user we are trying to authenticate
with
:param server_challenge: A random 8-byte response generated by the
server in the CHALLENGE_MESSAGE
:return response: NtChallengeResponse to the server_challenge
:return session_base_key: A session key calculated from the user
password challenge
"""
ntlm_hash = comphash._ntowfv1(password)
response = ComputeResponse._calc_resp(ntlm_hash, server_challenge)
session_base_key = hashlib.new('md4', ntlm_hash).digest()
return response, session_base_key
@staticmethod
def _get_NTLM2_response(password, server_challenge, client_challenge):
"""
[MS-NLMP] v28.0 2016-07-14
This name is really misleading as it isn't NTLM v2 authentication
rather this authentication is only used when the ntlm_compatibility
level is set to a value < 3 (No NTLMv2 auth) but the
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY flag is set in the negotiate
flags section. The documentation for computing this value is on page 56
under section 3.3.1 NTLM v1 Authentication
:param password: The password of the user we are trying to authenticate
with
:param server_challenge: A random 8-byte response generated by the
server in the CHALLENGE_MESSAGE
:param client_challenge: A random 8-byte response generated by the
client for the AUTHENTICATE_MESSAGE
:return response: NtChallengeResponse to the server_challenge
:return session_base_key: A session key calculated from the user
password challenge
"""
ntlm_hash = comphash._ntowfv1(password)
challenge = server_challenge + client_challenge
nt_session_hash = hashlib.md5(challenge).digest()[:8]
response = ComputeResponse._calc_resp(ntlm_hash, nt_session_hash[0:8])
session_base_key = hashlib.new('md4', ntlm_hash).digest()
return response, session_base_key
@staticmethod
def _get_NTLMv2_response(user_name, password, domain_name,
server_challenge, client_challenge, timestamp,
target_info):
"""
[MS-NLMP] v28.0 2016-07-14
2.2.2.8 NTLM V2 Response: NTLMv2_RESPONSE
The NTLMv2_RESPONSE strucutre defines the NTLMv2 authentication
NtChallengeResponse in the AUTHENTICATE_MESSAGE. This response is used
only when NTLMv2 authentication is configured.
The guide on how this is computed is in 3.3.2 NTLM v2 Authentication.
: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
:param server_challenge: A random 8-byte response generated by the
server in the CHALLENGE_MESSAGE
:param client_challenge: A random 8-byte response generated by the
client for the AUTHENTICATE_MESSAGE
:param timestamp: An 8-byte timestamp in windows format, 100
nanoseconds since 1601-01-01
:param target_info: The target_info structure from the
CHALLENGE_MESSAGE with the CBT attached if required
:return response: NtChallengeResponse to the server_challenge
:return session_base_key: A session key calculated from the user
password challenge
"""
nt_hash = comphash._ntowfv2(user_name, password, domain_name)
temp = ComputeResponse._get_NTLMv2_temp(timestamp, client_challenge,
target_info)
nt_proof_str = hmac.new(nt_hash,
(server_challenge + temp),
digestmod=hashlib.md5).digest()
response = nt_proof_str + temp
session_base_key = hmac.new(nt_hash, nt_proof_str,
digestmod=hashlib.md5).digest()
return response, session_base_key
@staticmethod
def _get_NTLMv2_temp(timestamp, client_challenge, target_info):
"""
[MS-NLMP] v28.0 2016-07-14
2.2.2.7 NTLMv2_CLIENT_CHALLENGE - variable length
The NTLMv2_CLIENT_CHALLENGE structure defines the client challenge in
the AUTHENTICATE_MESSAGE. This structure is used only when NTLM v2
authentication is configured and is transported in the NTLMv2_RESPONSE
structure.
The method to create this structure is defined in 3.3.2 NTLMv2
Authentication. In this method this variable is known as the temp
value. The target_info variable corresponds to the ServerName variable
used in that documentation. This is in reality a lot more than just the
ServerName and contains the AV_PAIRS structure we need to transport
with the message like Channel Binding tokens and others. By default
this will be the target_info returned from the CHALLENGE_MESSAGE plus
MSV_AV_CHANNEL_BINDINGS if specified otherwise it is a new target_info
set with MSV_AV_TIMESTAMP to the current time.
:param timestamp: An 8-byte timestamp in windows format, 100
nanoseconds since 1601-01-01
:param client_challenge: A random 8-byte response generated by the
`client for the AUTHENTICATE_MESSAGE
:param target_info: The target_info structure from the
CHALLENGE_MESSAGE with the CBT attached if required
:return temp: The CLIENT_CHALLENGE structure that will be added to the
NtChallengeResponse structure
"""
resp_type = b'\x01'
hi_resp_type = b'\x01'
reserved1 = b'\x00' * 2
reserved2 = b'\x00' * 4
reserved3 = b'\x00' * 4
# This byte is not in the structure defined in 2.2.2.7 but is in the
# computation guide, works with it present
reserved4 = b'\x00' * 4
temp = resp_type
temp += hi_resp_type
temp += reserved1
temp += reserved2
temp += timestamp
temp += client_challenge
temp += reserved3
temp += target_info.pack()
temp += reserved4
return temp
@staticmethod
def _calc_resp(password_hash, server_challenge):
"""
Generate the LM response given a 16-byte password hash and the
challenge from the CHALLENGE_MESSAGE
:param password_hash: A 16-byte password hash
:param server_challenge: A random 8-byte response generated by the
server in the CHALLENGE_MESSAGE
:return res: A 24-byte buffer to contain the LM response upon return
"""
# padding with zeros to make the hash 21 bytes long
password_hash += b'\x00' * (21 - len(password_hash))
res = b''
dobj = DES(DES.key56_to_key64(password_hash[0:7]))
res = res + dobj.encrypt(server_challenge[0:8])
dobj = DES(DES.key56_to_key64(password_hash[7:14]))
res = res + dobj.encrypt(server_challenge[0:8])
dobj = DES(DES.key56_to_key64(password_hash[14:21]))
res = res + dobj.encrypt(server_challenge[0:8])
return res
def get_windows_timestamp():
# Get Windows Date time, 100 nanoseconds since 1601-01-01 in a 64 bit
# structure
seconds_since_origin = 116444736000 + calendar.timegm(time.gmtime())
timestamp = struct.pack('<q', seconds_since_origin * 10000000)
return timestamp