support for ntlm proxy
This commit is contained in:
parent
5428942ccf
commit
1fd6169f53
31 changed files with 3029 additions and 58 deletions
250
wakatime/packages/ntlm_auth/session_security.py
Normal file
250
wakatime/packages/ntlm_auth/session_security.py
Normal file
|
@ -0,0 +1,250 @@
|
|||
# This library 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.
|
||||
|
||||
# This library 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 this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||
|
||||
import binascii
|
||||
import hmac
|
||||
import struct
|
||||
import ntlm_auth.compute_keys as compkeys
|
||||
from ntlm_auth.constants import NegotiateFlags, SignSealConstants
|
||||
from ntlm_auth.rc4 import ARC4
|
||||
|
||||
|
||||
class _NtlmMessageSignature1(object):
|
||||
EXPECTED_BODY_LENGTH = 16
|
||||
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2.2.9.1 NTLMSSP_MESSAGE_SIGNATURE
|
||||
This version of the NTLMSSP_MESSAGE_SIGNATURE structure MUST be used when the
|
||||
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY flag is not negotiated.
|
||||
|
||||
:param random_pad: A 4-byte array that contains the random pad for the emssage
|
||||
:param checksum: A 4-byte array that contains the checksum for the message
|
||||
:param seq_num: A 32-bit unsigned integer that contains the NTLM sequence number for this application message
|
||||
"""
|
||||
def __init__(self, random_pad, checksum, seq_num):
|
||||
self.version = struct.pack("<I", 1)
|
||||
self.random_pad = random_pad
|
||||
self.checksum = checksum
|
||||
self.seq_num = seq_num
|
||||
|
||||
def get_data(self):
|
||||
signature = self.version
|
||||
signature += self.random_pad
|
||||
signature += self.checksum
|
||||
signature += self.seq_num
|
||||
|
||||
assert self.EXPECTED_BODY_LENGTH == len(signature), "BODY_LENGTH: %d != signature: %d" % (
|
||||
self.EXPECTED_BODY_LENGTH, len(signature))
|
||||
|
||||
return signature
|
||||
|
||||
class _NtlmMessageSignature2(object):
|
||||
EXPECTED_BODY_LENGTH = 16
|
||||
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2.2.9.2 NTLMSSP_MESSAGE_SIGNATURE for Extended Session Security
|
||||
This version of the NTLMSSP_MESSAGE_SIGNATURE structure MUST be used when the
|
||||
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY flag is negotiated
|
||||
|
||||
:param checksum: An 8-byte array that contains the checksum for the message
|
||||
:param seq_num: A 32-bit unsigned integer that contains the NTLM sequence number for this application message
|
||||
"""
|
||||
|
||||
def __init__(self, checksum, seq_num):
|
||||
self.version = struct.pack("<I", 1)
|
||||
self.checksum = checksum
|
||||
self.seq_num = seq_num
|
||||
|
||||
def get_data(self):
|
||||
signature = self.version
|
||||
signature += self.checksum
|
||||
signature += self.seq_num
|
||||
|
||||
assert self.EXPECTED_BODY_LENGTH == len(signature), "BODY_LENGTH: %d != signature: %d" % (
|
||||
self.EXPECTED_BODY_LENGTH, len(signature))
|
||||
|
||||
return signature
|
||||
|
||||
class SessionSecurity(object):
|
||||
"""
|
||||
Initialises a security session context that can be used by libraries that call ntlm-auth to sign and seal
|
||||
messages send to the server as well as verify and unseal messages that have been received from the server.
|
||||
This is similar to the GSS_Wrap functions specified in the MS-NLMP document which does the same task.
|
||||
|
||||
:param negotiate_flags: The negotiate flag structure that has been negotiated with the server
|
||||
:param exported_session_key: A 128-bit session key used to derive signing and sealing keys
|
||||
:param source: The source of the message, only used in test scenarios when testing out a server sealing and unsealing
|
||||
"""
|
||||
def __init__(self, negotiate_flags, exported_session_key, source="client"):
|
||||
self.negotiate_flags = negotiate_flags
|
||||
self.outgoing_seq_num = 0
|
||||
self.incoming_seq_num = 0
|
||||
|
||||
client_sealing_key = compkeys.get_seal_key(self.negotiate_flags, exported_session_key, SignSealConstants.CLIENT_SEALING)
|
||||
server_sealing_key = compkeys.get_seal_key(self.negotiate_flags, exported_session_key, SignSealConstants.SERVER_SEALING)
|
||||
|
||||
if source == "client":
|
||||
self.outgoing_signing_key = compkeys.get_sign_key(exported_session_key, SignSealConstants.CLIENT_SIGNING)
|
||||
self.incoming_signing_key = compkeys.get_sign_key(exported_session_key, SignSealConstants.SERVER_SIGNING)
|
||||
self.outgoing_handle = ARC4(client_sealing_key)
|
||||
self.incoming_handle = ARC4(server_sealing_key)
|
||||
elif source == "server":
|
||||
self.outgoing_signing_key = compkeys.get_sign_key(exported_session_key, SignSealConstants.SERVER_SIGNING)
|
||||
self.incoming_signing_key = compkeys.get_sign_key(exported_session_key, SignSealConstants.CLIENT_SIGNING)
|
||||
self.outgoing_handle = ARC4(server_sealing_key)
|
||||
self.incoming_handle = ARC4(client_sealing_key)
|
||||
else:
|
||||
raise Exception("Invalid source parameter %s, must be client or server" % source)
|
||||
|
||||
def wrap(self, message):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
3.4.6 GSS_WrapEx()
|
||||
Emulates the GSS_Wrap() implementation to sign and seal messages if the correct flags
|
||||
are set.
|
||||
|
||||
@param message: The message data that will be wrapped
|
||||
@return message: The message that has been sealed if flags are set
|
||||
@return signature: The signature of the message, None if flags are not set
|
||||
"""
|
||||
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SEAL:
|
||||
encrypted_message = self._seal_message(message)
|
||||
signature = self._get_signature(message)
|
||||
message = encrypted_message
|
||||
|
||||
elif self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SIGN:
|
||||
signature = self._get_signature(message)
|
||||
else:
|
||||
signature = None
|
||||
|
||||
return message, signature
|
||||
|
||||
def unwrap(self, message, signature):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
3.4.7 GSS_UnwrapEx()
|
||||
Emulates the GSS_Unwrap() implementation to unseal messages and verify the signature
|
||||
sent matches what has been computed locally. Will throw an Exception if the signature
|
||||
doesn't match
|
||||
|
||||
@param message: The message data received from the server
|
||||
@param signature: The signature of the message
|
||||
@return message: The message that has been unsealed if flags are set
|
||||
"""
|
||||
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SEAL:
|
||||
message = self._unseal_message(message)
|
||||
self._verify_signature(message, signature)
|
||||
|
||||
elif self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SIGN:
|
||||
self._verify_signature(message, signature)
|
||||
|
||||
return message
|
||||
|
||||
def _seal_message(self, message):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
3.4.3 Message Confidentiality
|
||||
Will generate an encrypted message using RC4 based on the ClientSealingKey
|
||||
|
||||
@param message: The message to be sealed (encrypted)
|
||||
@return encrypted_message: The encrypted message
|
||||
"""
|
||||
encrypted_message = self.outgoing_handle.update(message)
|
||||
return encrypted_message
|
||||
|
||||
def _unseal_message(self, message):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
3.4.3 Message Confidentiality
|
||||
Will generate a dencrypted message using RC4 based on the ServerSealingKey
|
||||
|
||||
@param message: The message to be unsealed (dencrypted)
|
||||
@return decrypted_message: The decrypted message
|
||||
"""
|
||||
decrypted_message = self.incoming_handle.update(message)
|
||||
return decrypted_message
|
||||
|
||||
def _get_signature(self, message):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
3.4.4 Message Signature Functions
|
||||
Will create the signature based on the message to send to the server. Depending on the negotiate_flags
|
||||
set this could either be an NTLMv1 signature or NTLMv2 with Extended Session Security signature.
|
||||
|
||||
@param message: The message data that will be signed
|
||||
@return signature: Either _NtlmMessageSignature1 or _NtlmMessageSignature2 depending on the flags set
|
||||
"""
|
||||
signature = calc_signature(message, self.negotiate_flags, self.outgoing_signing_key, self.outgoing_seq_num, self.outgoing_handle)
|
||||
self.outgoing_seq_num += 1
|
||||
|
||||
return signature.get_data()
|
||||
|
||||
def _verify_signature(self, message, signature):
|
||||
"""
|
||||
Will verify that the signature received from the server matches up with the expected signature
|
||||
computed locally. Will throw an exception if they do not match
|
||||
|
||||
@param message: The message data that is received from the server
|
||||
@param signature: The signature of the message received from the server
|
||||
"""
|
||||
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||
actual_checksum = signature[4:12]
|
||||
actual_seq_num = struct.unpack("<I", signature[12:16])[0]
|
||||
else:
|
||||
actual_checksum = signature[8:12]
|
||||
actual_seq_num = struct.unpack("<I", signature[12:16])[0]
|
||||
|
||||
expected_signature = calc_signature(message, self.negotiate_flags, self.incoming_signing_key, self.incoming_seq_num, self.incoming_handle)
|
||||
expected_checksum = expected_signature.checksum
|
||||
expected_seq_num = struct.unpack("<I", expected_signature.seq_num)[0]
|
||||
|
||||
if actual_checksum != expected_checksum:
|
||||
raise Exception("The signature checksum does not match, message has been altered")
|
||||
|
||||
if actual_seq_num != expected_seq_num:
|
||||
raise Exception("The signature sequence number does not match up, message not received in the correct sequence")
|
||||
|
||||
self.incoming_seq_num += 1
|
||||
|
||||
|
||||
def calc_signature(message, negotiate_flags, signing_key, seq_num, handle):
|
||||
seq_num = struct.pack("<I", seq_num)
|
||||
if negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||
checksum_hmac = hmac.new(signing_key, seq_num + message)
|
||||
if negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_KEY_EXCH:
|
||||
checksum = handle.update(checksum_hmac.digest()[:8])
|
||||
else:
|
||||
checksum = checksum_hmac.digest()[:8]
|
||||
|
||||
signature = _NtlmMessageSignature2(checksum, seq_num)
|
||||
|
||||
else:
|
||||
message_crc = binascii.crc32(message) % (1 << 32)
|
||||
checksum = struct.pack("<I", message_crc)
|
||||
random_pad = handle.update(struct.pack("<I", 0))
|
||||
checksum = handle.update(checksum)
|
||||
seq_num = handle.update(seq_num)
|
||||
random_pad = struct.pack("<I", 0)
|
||||
|
||||
signature = _NtlmMessageSignature1(random_pad, checksum, seq_num)
|
||||
|
||||
return signature
|
Loading…
Add table
Add a link
Reference in a new issue