support for ntlm proxy
This commit is contained in:
parent
5428942ccf
commit
1fd6169f53
31 changed files with 3029 additions and 58 deletions
|
@ -1,3 +1,3 @@
|
|||
[settings]
|
||||
debug = true
|
||||
api_key = 1234
|
||||
api_key = 35fe2c34-252c-4760-bab9-ff85610e459d
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
[settings]
|
||||
debug = false
|
||||
api_key = 1234
|
||||
api_key = 1090a6ae-855f-4be7-b8fb-3edbaf1aa3ec
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[settings]
|
||||
verbose = true
|
||||
api_key = 1234567
|
||||
api_key = d491a956-c8f2-44a9-98a7-987814bd71ba
|
||||
logfile = /tmp/waka
|
||||
hidefilenames = true
|
||||
exclude =
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
[settings]
|
||||
debug = false
|
||||
api_key = 1234
|
||||
ignore =
|
||||
api_key = a77fdb4a-4bc9-40fe-baac-b369ca7de98e
|
||||
ignore =
|
||||
COMMIT_EDITMSG$
|
||||
TAG_EDITMSG$
|
||||
exclude =
|
||||
exclude =
|
||||
excludeme
|
||||
\(invalid regex)
|
||||
include =
|
||||
include =
|
||||
\(invalid regex)
|
||||
includeme
|
||||
offline = true
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[settings]
|
||||
debug = false
|
||||
api_key = 1234
|
||||
api_key = 033c47c9-0441-4eb5-8b3f-b51f27b31049
|
||||
hidefilenames =
|
||||
missingfile
|
||||
twolinefile\.txt$
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
[settings]
|
||||
debug = false
|
||||
api_key = 1234
|
||||
api_key = c21f8ebd-6a6a-48a0-900b-0870db3d7afe
|
||||
api_url = https://api.wakatime.com/api/v1/heartbeats
|
||||
ignore =
|
||||
ignore =
|
||||
COMMIT_EDITMSG$
|
||||
TAG_EDITMSG$
|
||||
exclude =
|
||||
exclude =
|
||||
excludeme
|
||||
\(invalid regex)
|
||||
include =
|
||||
include =
|
||||
\(invalid regex)
|
||||
includeme
|
||||
offline = true
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[settings]
|
||||
debug = false
|
||||
api_key = 1234
|
||||
api_key = ba50f683-bb6b-4f31-9c84-7c70412234f7
|
||||
[projectmap]
|
||||
samples/projects/proj.{3}_map/ = proj-map
|
||||
samples/projects/project_map(\d+)/ = proj-map{0}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[settings]
|
||||
debug = false
|
||||
api_key = 1234
|
||||
api_key = 3ac03597-a707-4dd2-95a1-25430f9486ba
|
||||
[projectmap]
|
||||
invalid[({regex = proj-map
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[settings]
|
||||
debug = false
|
||||
api_key = 1234
|
||||
api_key = 4b59ee97-144f-46d0-a2c0-167e61a9c518
|
||||
[projectmap]
|
||||
samples/projects/project_map(\d+)/ = proj-map{3}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
[settings]
|
||||
debug = false
|
||||
apikey = 1234
|
||||
ignore =
|
||||
apikey = b5acbb37-8558-4383-8ea4-581837c7371c
|
||||
ignore =
|
||||
COMMIT_EDITMSG$
|
||||
TAG_EDITMSG$
|
||||
exclude =
|
||||
exclude =
|
||||
excludeme
|
||||
\(invalid regex)
|
||||
include =
|
||||
include =
|
||||
\(invalid regex)
|
||||
includeme
|
||||
offline = true
|
||||
|
|
|
@ -30,7 +30,7 @@ optional arguments:
|
|||
"domain", or "app"; defaults to file.
|
||||
--proxy PROXY optional proxy configuration. Supports HTTPS and SOCKS
|
||||
proxies. For example: https://user:pass@host:port or
|
||||
socks5://user:pass@host:port
|
||||
socks5://user:pass@host:port or domain\user:pass
|
||||
--project PROJECT optional project name
|
||||
--alternate-project ALTERNATE_PROJECT
|
||||
optional alternate project name; auto-discovered
|
||||
|
|
|
@ -10,6 +10,7 @@ import time
|
|||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import uuid
|
||||
from testfixtures import log_capture
|
||||
from wakatime.compat import u, is_py3
|
||||
from wakatime.constants import (
|
||||
|
@ -68,9 +69,10 @@ class MainTestCase(utils.TestCase):
|
|||
entity = 'tests/samples/codefiles/twolinefile.txt'
|
||||
shutil.copy(entity, os.path.join(tempdir, 'twolinefile.txt'))
|
||||
entity = os.path.realpath(os.path.join(tempdir, 'twolinefile.txt'))
|
||||
|
||||
key = str(uuid.uuid4())
|
||||
config = 'tests/samples/configs/good_config.cfg'
|
||||
args = ['--file', entity, '--key', '123', '--config', config]
|
||||
|
||||
args = ['--file', entity, '--key', key, '--config', config]
|
||||
|
||||
retval = execute(args)
|
||||
self.assertEquals(retval, SUCCESS)
|
||||
|
@ -192,7 +194,11 @@ class MainTestCase(utils.TestCase):
|
|||
self.patched['wakatime.offlinequeue.Queue.push'].assert_not_called()
|
||||
self.patched['wakatime.offlinequeue.Queue.pop'].assert_called_once_with()
|
||||
|
||||
def test_api_key_without_underscore_accepted(self):
|
||||
def test_api_key_setting_without_underscore_accepted(self):
|
||||
"""Api key in wakatime.cfg should also work without an underscore:
|
||||
apikey = XXX
|
||||
"""
|
||||
|
||||
response = Response()
|
||||
response.status_code = 201
|
||||
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = response
|
||||
|
@ -288,11 +294,11 @@ class MainTestCase(utils.TestCase):
|
|||
entity = 'tests/samples/codefiles/twolinefile.txt'
|
||||
shutil.copy(entity, os.path.join(tempdir, 'twolinefile.txt'))
|
||||
entity = os.path.realpath(os.path.join(tempdir, 'twolinefile.txt'))
|
||||
|
||||
now = u(int(time.time()))
|
||||
config = 'tests/samples/configs/good_config.cfg'
|
||||
key = str(uuid.uuid4())
|
||||
|
||||
args = ['--file', entity, '--key', '123', '--config', config, '--time', now]
|
||||
args = ['--file', entity, '--key', key, '--config', config, '--time', now]
|
||||
|
||||
retval = execute(args)
|
||||
self.assertEquals(retval, API_ERROR)
|
||||
|
@ -334,11 +340,11 @@ class MainTestCase(utils.TestCase):
|
|||
entity = 'tests/samples/codefiles/twolinefile.txt'
|
||||
shutil.copy(entity, os.path.join(tempdir, 'twolinefile.txt'))
|
||||
entity = os.path.realpath(os.path.join(tempdir, 'twolinefile.txt'))
|
||||
|
||||
now = u(int(time.time()))
|
||||
config = 'tests/samples/configs/paranoid.cfg'
|
||||
key = str(uuid.uuid4())
|
||||
|
||||
args = ['--file', entity, '--key', '123', '--config', config, '--time', now]
|
||||
args = ['--file', entity, '--key', key, '--config', config, '--time', now]
|
||||
|
||||
retval = execute(args)
|
||||
self.assertEquals(retval, API_ERROR)
|
||||
|
@ -380,11 +386,11 @@ class MainTestCase(utils.TestCase):
|
|||
entity = 'tests/samples/codefiles/twolinefile.txt'
|
||||
shutil.copy(entity, os.path.join(tempdir, 'twolinefile.txt'))
|
||||
entity = os.path.realpath(os.path.join(tempdir, 'twolinefile.txt'))
|
||||
|
||||
now = u(int(time.time()))
|
||||
config = 'tests/samples/configs/hide_file_names.cfg'
|
||||
key = str(uuid.uuid4())
|
||||
|
||||
args = ['--file', entity, '--key', '123', '--config', config, '--time', now]
|
||||
args = ['--file', entity, '--key', key, '--config', config, '--time', now]
|
||||
|
||||
retval = execute(args)
|
||||
self.assertEquals(retval, API_ERROR)
|
||||
|
@ -426,11 +432,11 @@ class MainTestCase(utils.TestCase):
|
|||
entity = 'tests/samples/codefiles/emptyfile.txt'
|
||||
shutil.copy(entity, os.path.join(tempdir, 'emptyfile.txt'))
|
||||
entity = os.path.realpath(os.path.join(tempdir, 'emptyfile.txt'))
|
||||
|
||||
now = u(int(time.time()))
|
||||
config = 'tests/samples/configs/hide_file_names.cfg'
|
||||
key = str(uuid.uuid4())
|
||||
|
||||
args = ['--file', entity, '--key', '123', '--config', config, '--time', now]
|
||||
args = ['--file', entity, '--key', key, '--config', config, '--time', now]
|
||||
|
||||
retval = execute(args)
|
||||
self.assertEquals(retval, API_ERROR)
|
||||
|
@ -472,9 +478,9 @@ class MainTestCase(utils.TestCase):
|
|||
entity = 'tests/samples/codefiles/twolinefile.txt'
|
||||
shutil.copy(entity, os.path.join(tempdir, 'twolinefile.txt'))
|
||||
entity = os.path.realpath(os.path.join(tempdir, 'twolinefile.txt'))
|
||||
|
||||
config = 'tests/samples/configs/good_config.cfg'
|
||||
args = ['--file', entity, '--key', '123', '--config', config, '--timeout', 'abc']
|
||||
key = str(uuid.uuid4())
|
||||
args = ['--file', entity, '--key', key, '--config', config, '--timeout', 'abc']
|
||||
|
||||
with self.assertRaises(SystemExit) as e:
|
||||
execute(args)
|
||||
|
@ -500,8 +506,8 @@ class MainTestCase(utils.TestCase):
|
|||
entity = 'tests/samples/codefiles/emptyfile.txt'
|
||||
shutil.copy(entity, os.path.join(tempdir, 'emptyfile.txt'))
|
||||
entity = os.path.realpath(os.path.join(tempdir, 'emptyfile.txt'))
|
||||
|
||||
config = 'tests/samples/configs/good_config.cfg'
|
||||
|
||||
args = ['--file', entity, '--config', config, '--exclude', 'empty', '--verbose']
|
||||
retval = execute(args)
|
||||
self.assertEquals(retval, SUCCESS)
|
||||
|
@ -529,10 +535,10 @@ class MainTestCase(utils.TestCase):
|
|||
entity = 'tests/samples/codefiles/twolinefile.txt'
|
||||
shutil.copy(entity, os.path.join(tempdir, 'twolinefile.txt'))
|
||||
entity = os.path.realpath(os.path.join(tempdir, 'twolinefile.txt'))
|
||||
|
||||
now = u(int(time.time()))
|
||||
key = str(uuid.uuid4())
|
||||
|
||||
args = ['--file', entity, '--key', '123',
|
||||
args = ['--file', entity, '--key', key,
|
||||
'--config', 'tests/samples/configs/paranoid.cfg', '--time', now]
|
||||
|
||||
retval = execute(args)
|
||||
|
@ -575,10 +581,10 @@ class MainTestCase(utils.TestCase):
|
|||
entity = 'tests/samples/codefiles/twolinefile.txt'
|
||||
shutil.copy(entity, os.path.join(tempdir, 'twolinefile.txt'))
|
||||
entity = os.path.realpath(os.path.join(tempdir, 'twolinefile.txt'))
|
||||
|
||||
now = u(int(time.time()))
|
||||
key = str(uuid.uuid4())
|
||||
|
||||
args = ['--file', entity, '--key', '123',
|
||||
args = ['--file', entity, '--key', key,
|
||||
'--config', 'tests/samples/configs/paranoid.cfg', '--time', now]
|
||||
|
||||
retval = execute(args)
|
||||
|
@ -602,10 +608,10 @@ class MainTestCase(utils.TestCase):
|
|||
entity = 'tests/samples/codefiles/twolinefile.txt'
|
||||
shutil.copy(entity, os.path.join(tempdir, 'twolinefile.txt'))
|
||||
entity = os.path.realpath(os.path.join(tempdir, 'twolinefile.txt'))
|
||||
|
||||
now = u(int(time.time()))
|
||||
key = str(uuid.uuid4())
|
||||
|
||||
args = ['--file', entity, '--key', '123',
|
||||
args = ['--file', entity, '--key', key,
|
||||
'--config', 'tests/samples/configs/paranoid.cfg', '--time', now]
|
||||
|
||||
retval = execute(args)
|
||||
|
@ -654,10 +660,10 @@ class MainTestCase(utils.TestCase):
|
|||
entity = 'tests/samples/codefiles/twolinefile.txt'
|
||||
shutil.copy(entity, os.path.join(tempdir, 'twolinefile.txt'))
|
||||
entity = os.path.realpath(os.path.join(tempdir, 'twolinefile.txt'))
|
||||
|
||||
now = u(int(time.time()))
|
||||
key = str(uuid.uuid4())
|
||||
|
||||
args = ['--file', entity, '--key', '123', '--disableoffline',
|
||||
args = ['--file', entity, '--key', key, '--disableoffline',
|
||||
'--config', 'tests/samples/configs/good_config.cfg', '--time', now]
|
||||
|
||||
retval = execute(args)
|
||||
|
@ -692,10 +698,10 @@ class MainTestCase(utils.TestCase):
|
|||
entity = 'tests/samples/codefiles/twolinefile.txt'
|
||||
shutil.copy(entity, os.path.join(tempdir, 'twolinefile.txt'))
|
||||
entity = os.path.realpath(os.path.join(tempdir, 'twolinefile.txt'))
|
||||
|
||||
now = u(int(time.time()))
|
||||
key = str(uuid.uuid4())
|
||||
|
||||
args = ['--file', entity, '--key', '123', '--verbose',
|
||||
args = ['--file', entity, '--key', key, '--verbose',
|
||||
'--config', 'tests/samples/configs/good_config.cfg', '--time', now]
|
||||
|
||||
retval = execute(args)
|
||||
|
@ -749,10 +755,10 @@ class MainTestCase(utils.TestCase):
|
|||
entity = 'tests/samples/codefiles/twolinefile.txt'
|
||||
shutil.copy(entity, os.path.join(tempdir, 'twolinefile.txt'))
|
||||
entity = os.path.realpath(os.path.join(tempdir, 'twolinefile.txt'))
|
||||
|
||||
now = u(int(time.time()))
|
||||
key = str(uuid.uuid4())
|
||||
|
||||
args = ['--file', entity, '--key', '123', '--disableoffline',
|
||||
args = ['--file', entity, '--key', key, '--disableoffline',
|
||||
'--config', 'tests/samples/configs/good_config.cfg', '--time', now]
|
||||
|
||||
retval = execute(args)
|
||||
|
@ -847,7 +853,37 @@ class MainTestCase(utils.TestCase):
|
|||
|
||||
self.assertEquals(int(str(e.exception)), AUTH_ERROR)
|
||||
self.assertEquals(sys.stdout.getvalue(), '')
|
||||
expected = 'error: Missing api key'
|
||||
expected = 'error: Missing api key. Find your api key from wakatime.com/settings.'
|
||||
self.assertIn(expected, sys.stderr.getvalue())
|
||||
|
||||
log_output = u("\n").join([u(' ').join(x) for x in logs.actual()])
|
||||
expected = ''
|
||||
self.assertEquals(log_output, expected)
|
||||
|
||||
self.patched['wakatime.session_cache.SessionCache.get'].assert_not_called()
|
||||
self.patched['wakatime.session_cache.SessionCache.delete'].assert_not_called()
|
||||
self.patched['wakatime.session_cache.SessionCache.save'].assert_not_called()
|
||||
|
||||
self.patched['wakatime.offlinequeue.Queue.push'].assert_not_called()
|
||||
self.patched['wakatime.offlinequeue.Queue.pop'].assert_not_called()
|
||||
|
||||
@log_capture()
|
||||
def test_invalid_api_key(self, logs):
|
||||
logging.disable(logging.NOTSET)
|
||||
|
||||
response = Response()
|
||||
response.status_code = 201
|
||||
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = response
|
||||
|
||||
config = 'tests/samples/configs/missing_api_key.cfg'
|
||||
args = ['--config', config, '--key', 'invalid-api-key']
|
||||
|
||||
with self.assertRaises(SystemExit) as e:
|
||||
execute(args)
|
||||
|
||||
self.assertEquals(int(str(e.exception)), AUTH_ERROR)
|
||||
self.assertEquals(sys.stdout.getvalue(), '')
|
||||
expected = 'error: Invalid api key. Find your api key from wakatime.com/settings.'
|
||||
self.assertIn(expected, sys.stderr.getvalue())
|
||||
|
||||
log_output = u("\n").join([u(' ').join(x) for x in logs.actual()])
|
||||
|
@ -870,9 +906,10 @@ class MainTestCase(utils.TestCase):
|
|||
entity = 'tests/samples/codefiles/emptyfile.txt'
|
||||
shutil.copy(entity, os.path.join(tempdir, 'emptyfile.txt'))
|
||||
entity = os.path.realpath(os.path.join(tempdir, 'emptyfile.txt'))
|
||||
proxy = 'localhost:1337'
|
||||
|
||||
config = 'tests/samples/configs/good_config.cfg'
|
||||
args = ['--file', entity, '--config', config, '--proxy', 'localhost:1234']
|
||||
args = ['--file', entity, '--config', config, '--proxy', proxy]
|
||||
retval = execute(args)
|
||||
self.assertEquals(retval, SUCCESS)
|
||||
self.assertEquals(sys.stdout.getvalue(), '')
|
||||
|
@ -885,7 +922,7 @@ class MainTestCase(utils.TestCase):
|
|||
self.patched['wakatime.offlinequeue.Queue.push'].assert_not_called()
|
||||
self.patched['wakatime.offlinequeue.Queue.pop'].assert_called_once_with()
|
||||
|
||||
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].assert_called_once_with(ANY, cert=None, proxies={'https': 'localhost:1234'}, stream=False, timeout=60, verify=True)
|
||||
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].assert_called_once_with(ANY, cert=None, proxies={'https': proxy}, stream=False, timeout=60, verify=True)
|
||||
|
||||
def test_write_argument(self):
|
||||
response = Response()
|
||||
|
@ -897,8 +934,9 @@ class MainTestCase(utils.TestCase):
|
|||
shutil.copy(entity, os.path.join(tempdir, 'emptyfile.txt'))
|
||||
entity = os.path.realpath(os.path.join(tempdir, 'emptyfile.txt'))
|
||||
now = u(int(time.time()))
|
||||
key = str(uuid.uuid4())
|
||||
|
||||
args = ['--file', entity, '--key', '123', '--write', '--verbose',
|
||||
args = ['--file', entity, '--key', key, '--write', '--verbose',
|
||||
'--config', 'tests/samples/configs/good_config.cfg', '--time', now]
|
||||
|
||||
retval = execute(args)
|
||||
|
@ -1292,11 +1330,11 @@ class MainTestCase(utils.TestCase):
|
|||
entity = os.path.join('tests/samples/codefiles/unicode', filename)
|
||||
shutil.copy(entity, os.path.join(tempdir, filename))
|
||||
entity = os.path.realpath(os.path.join(tempdir, filename))
|
||||
|
||||
now = u(int(time.time()))
|
||||
config = 'tests/samples/configs/good_config.cfg'
|
||||
key = str(uuid.uuid4())
|
||||
|
||||
args = ['--file', entity, '--key', '123', '--config', config, '--time', now]
|
||||
args = ['--file', entity, '--key', key, '--config', config, '--time', now]
|
||||
|
||||
retval = execute(args)
|
||||
self.assertEquals(retval, API_ERROR)
|
||||
|
@ -1342,7 +1380,9 @@ class MainTestCase(utils.TestCase):
|
|||
|
||||
entity = 'tests/samples/codefiles/twolinefile.txt'
|
||||
config = 'tests/samples/configs/good_config.cfg'
|
||||
args = ['--entity', entity, '--key', '123', '--config', config]
|
||||
key = str(uuid.uuid4())
|
||||
|
||||
args = ['--entity', entity, '--key', key, '--config', config]
|
||||
|
||||
execute(args)
|
||||
|
||||
|
|
176
tests/test_proxy.py
Normal file
176
tests/test_proxy.py
Normal file
|
@ -0,0 +1,176 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from wakatime.main import execute
|
||||
from wakatime.packages import requests
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from testfixtures import log_capture
|
||||
from wakatime.compat import u
|
||||
from wakatime.constants import SUCCESS
|
||||
from wakatime.packages.requests.models import Response
|
||||
from . import utils
|
||||
|
||||
try:
|
||||
from mock import ANY
|
||||
except ImportError:
|
||||
from unittest.mock import ANY
|
||||
|
||||
|
||||
class MainTestCase(utils.TestCase):
|
||||
patch_these = [
|
||||
'wakatime.packages.requests.adapters.HTTPAdapter.send',
|
||||
'wakatime.offlinequeue.Queue.push',
|
||||
['wakatime.offlinequeue.Queue.pop', None],
|
||||
['wakatime.offlinequeue.Queue.connect', None],
|
||||
'wakatime.session_cache.SessionCache.save',
|
||||
'wakatime.session_cache.SessionCache.delete',
|
||||
['wakatime.session_cache.SessionCache.get', requests.session],
|
||||
['wakatime.session_cache.SessionCache.connect', None],
|
||||
]
|
||||
|
||||
def test_proxy_without_protocol(self):
|
||||
response = Response()
|
||||
response.status_code = 201
|
||||
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = response
|
||||
|
||||
with utils.TemporaryDirectory() as tempdir:
|
||||
entity = 'tests/samples/codefiles/emptyfile.txt'
|
||||
shutil.copy(entity, os.path.join(tempdir, 'emptyfile.txt'))
|
||||
entity = os.path.realpath(os.path.join(tempdir, 'emptyfile.txt'))
|
||||
proxy = 'user:pass@localhost:12345'
|
||||
config = 'tests/samples/configs/good_config.cfg'
|
||||
args = ['--file', entity, '--config', config, '--proxy', proxy]
|
||||
|
||||
retval = execute(args)
|
||||
self.assertEquals(retval, SUCCESS)
|
||||
self.assertEquals(sys.stdout.getvalue(), '')
|
||||
self.assertEquals(sys.stderr.getvalue(), '')
|
||||
|
||||
self.patched['wakatime.session_cache.SessionCache.get'].assert_called_once_with()
|
||||
self.patched['wakatime.session_cache.SessionCache.delete'].assert_not_called()
|
||||
self.patched['wakatime.session_cache.SessionCache.save'].assert_called_once_with(ANY)
|
||||
|
||||
self.patched['wakatime.offlinequeue.Queue.push'].assert_not_called()
|
||||
self.patched['wakatime.offlinequeue.Queue.pop'].assert_called_once_with()
|
||||
|
||||
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].assert_called_once_with(ANY, cert=None, proxies={'https': proxy}, stream=False, timeout=60, verify=True)
|
||||
|
||||
def test_https_proxy(self):
|
||||
response = Response()
|
||||
response.status_code = 201
|
||||
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = response
|
||||
|
||||
with utils.TemporaryDirectory() as tempdir:
|
||||
entity = 'tests/samples/codefiles/emptyfile.txt'
|
||||
shutil.copy(entity, os.path.join(tempdir, 'emptyfile.txt'))
|
||||
entity = os.path.realpath(os.path.join(tempdir, 'emptyfile.txt'))
|
||||
proxy = 'https://user:pass@localhost:12345'
|
||||
config = 'tests/samples/configs/good_config.cfg'
|
||||
args = ['--file', entity, '--config', config, '--proxy', proxy]
|
||||
|
||||
retval = execute(args)
|
||||
self.assertEquals(retval, SUCCESS)
|
||||
self.assertEquals(sys.stdout.getvalue(), '')
|
||||
self.assertEquals(sys.stderr.getvalue(), '')
|
||||
|
||||
self.patched['wakatime.session_cache.SessionCache.get'].assert_called_once_with()
|
||||
self.patched['wakatime.session_cache.SessionCache.delete'].assert_not_called()
|
||||
self.patched['wakatime.session_cache.SessionCache.save'].assert_called_once_with(ANY)
|
||||
|
||||
self.patched['wakatime.offlinequeue.Queue.push'].assert_not_called()
|
||||
self.patched['wakatime.offlinequeue.Queue.pop'].assert_called_once_with()
|
||||
|
||||
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].assert_called_once_with(ANY, cert=None, proxies={'https': proxy}, stream=False, timeout=60, verify=True)
|
||||
|
||||
def test_socks_proxy(self):
|
||||
response = Response()
|
||||
response.status_code = 201
|
||||
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = response
|
||||
|
||||
with utils.TemporaryDirectory() as tempdir:
|
||||
entity = 'tests/samples/codefiles/emptyfile.txt'
|
||||
shutil.copy(entity, os.path.join(tempdir, 'emptyfile.txt'))
|
||||
entity = os.path.realpath(os.path.join(tempdir, 'emptyfile.txt'))
|
||||
proxy = 'socks5://user:pass@localhost:12345'
|
||||
config = 'tests/samples/configs/good_config.cfg'
|
||||
args = ['--file', entity, '--config', config, '--proxy', proxy]
|
||||
|
||||
retval = execute(args)
|
||||
self.assertEquals(retval, SUCCESS)
|
||||
self.assertEquals(sys.stdout.getvalue(), '')
|
||||
self.assertEquals(sys.stderr.getvalue(), '')
|
||||
|
||||
self.patched['wakatime.session_cache.SessionCache.get'].assert_called_once_with()
|
||||
self.patched['wakatime.session_cache.SessionCache.delete'].assert_not_called()
|
||||
self.patched['wakatime.session_cache.SessionCache.save'].assert_called_once_with(ANY)
|
||||
|
||||
self.patched['wakatime.offlinequeue.Queue.push'].assert_not_called()
|
||||
self.patched['wakatime.offlinequeue.Queue.pop'].assert_called_once_with()
|
||||
|
||||
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].assert_called_once_with(ANY, cert=None, proxies={'https': proxy}, stream=False, timeout=60, verify=True)
|
||||
|
||||
def test_ntlm_proxy(self):
|
||||
response = Response()
|
||||
response.status_code = 201
|
||||
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = response
|
||||
|
||||
with utils.TemporaryDirectory() as tempdir:
|
||||
entity = 'tests/samples/codefiles/emptyfile.txt'
|
||||
shutil.copy(entity, os.path.join(tempdir, 'emptyfile.txt'))
|
||||
entity = os.path.realpath(os.path.join(tempdir, 'emptyfile.txt'))
|
||||
proxy = 'domain\\user:pass'
|
||||
config = 'tests/samples/configs/good_config.cfg'
|
||||
args = ['--file', entity, '--config', config, '--proxy', proxy]
|
||||
|
||||
retval = execute(args)
|
||||
self.assertEquals(retval, SUCCESS)
|
||||
self.assertEquals(sys.stdout.getvalue(), '')
|
||||
self.assertEquals(sys.stderr.getvalue(), '')
|
||||
|
||||
self.patched['wakatime.session_cache.SessionCache.get'].assert_called_once_with()
|
||||
self.patched['wakatime.session_cache.SessionCache.delete'].assert_not_called()
|
||||
self.patched['wakatime.session_cache.SessionCache.save'].assert_called_once_with(ANY)
|
||||
|
||||
self.patched['wakatime.offlinequeue.Queue.push'].assert_not_called()
|
||||
self.patched['wakatime.offlinequeue.Queue.pop'].assert_called_once_with()
|
||||
|
||||
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].assert_called_once_with(ANY, cert=None, proxies={}, stream=False, timeout=60, verify=True)
|
||||
|
||||
@log_capture()
|
||||
def test_invalid_proxy(self, logs):
|
||||
logging.disable(logging.NOTSET)
|
||||
|
||||
response = Response()
|
||||
response.status_code = 201
|
||||
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = response
|
||||
|
||||
with utils.TemporaryDirectory() as tempdir:
|
||||
entity = 'tests/samples/codefiles/emptyfile.txt'
|
||||
shutil.copy(entity, os.path.join(tempdir, 'emptyfile.txt'))
|
||||
entity = os.path.realpath(os.path.join(tempdir, 'emptyfile.txt'))
|
||||
proxy = 'invaliddd:proxyarg'
|
||||
config = 'tests/samples/configs/good_config.cfg'
|
||||
args = ['--file', entity, '--config', config, '--proxy', proxy]
|
||||
|
||||
with self.assertRaises(SystemExit) as e:
|
||||
execute(args)
|
||||
|
||||
self.assertEquals(int(str(e.exception)), 2)
|
||||
self.assertEquals(sys.stdout.getvalue(), '')
|
||||
expected = 'error: Invalid proxy. Must be in format https://user:pass@host:port or socks5://user:pass@host:port or domain\user:pass.'
|
||||
self.assertIn(expected, sys.stderr.getvalue())
|
||||
|
||||
log_output = u("\n").join([u(' ').join(x) for x in logs.actual()])
|
||||
expected = ''
|
||||
self.assertEquals(log_output, expected)
|
||||
|
||||
self.patched['wakatime.session_cache.SessionCache.get'].assert_not_called()
|
||||
self.patched['wakatime.session_cache.SessionCache.delete'].assert_not_called()
|
||||
self.patched['wakatime.session_cache.SessionCache.save'].assert_not_called()
|
||||
|
||||
self.patched['wakatime.offlinequeue.Queue.push'].assert_not_called()
|
||||
self.patched['wakatime.offlinequeue.Queue.pop'].assert_not_called()
|
|
@ -137,7 +137,8 @@ def parseArguments():
|
|||
help='optional proxy configuration. Supports HTTPS '+
|
||||
'and SOCKS proxies. For example: '+
|
||||
'https://user:pass@host:port or '+
|
||||
'socks5://user:pass@host:port')
|
||||
'socks5://user:pass@host:port or ' +
|
||||
'domain\\user:pass')
|
||||
parser.add_argument('--project', dest='project',
|
||||
help='optional project name')
|
||||
parser.add_argument('--alternate-project', dest='alternate_project',
|
||||
|
@ -205,9 +206,21 @@ def parseArguments():
|
|||
args.key = default_key
|
||||
else:
|
||||
try:
|
||||
parser.error('Missing api key')
|
||||
parser.error('Missing api key. Find your api key from wakatime.com/settings.')
|
||||
except SystemExit:
|
||||
raise SystemExit(AUTH_ERROR)
|
||||
|
||||
is_valid = False
|
||||
try:
|
||||
is_valid = not not re.match(r'^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$', args.key, re.I)
|
||||
except:
|
||||
pass
|
||||
if not is_valid:
|
||||
try:
|
||||
parser.error('Invalid api key. Find your api key from wakatime.com/settings.')
|
||||
except SystemExit:
|
||||
raise SystemExit(AUTH_ERROR)
|
||||
|
||||
if not args.entity:
|
||||
if args.file:
|
||||
args.entity = args.file
|
||||
|
@ -257,6 +270,20 @@ def parseArguments():
|
|||
args.offline = configs.getboolean('settings', 'offline')
|
||||
if not args.proxy and configs.has_option('settings', 'proxy'):
|
||||
args.proxy = configs.get('settings', 'proxy')
|
||||
if args.proxy:
|
||||
is_valid = False
|
||||
try:
|
||||
pattern = r'^((https?|socks5)://)?([^:@]+(:([^:@])+)?@)?[^:]+(:\d+)?$'
|
||||
if '\\' in args.proxy:
|
||||
pattern = r'^.*\\.+$'
|
||||
is_valid = not not re.match(pattern, args.proxy, re.I)
|
||||
except:
|
||||
pass
|
||||
if not is_valid:
|
||||
parser.error('Invalid proxy. Must be in format ' +
|
||||
'https://user:pass@host:port or ' +
|
||||
'socks5://user:pass@host:port or ' +
|
||||
'domain\\user:pass.')
|
||||
if not args.verbose and configs.has_option('settings', 'verbose'):
|
||||
args.verbose = configs.getboolean('settings', 'verbose')
|
||||
if not args.verbose and configs.has_option('settings', 'debug'):
|
||||
|
@ -385,9 +412,6 @@ def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None,
|
|||
}
|
||||
if hostname:
|
||||
headers['X-Machine-Name'] = u(hostname).encode('utf-8')
|
||||
proxies = {}
|
||||
if proxy:
|
||||
proxies['https'] = proxy
|
||||
|
||||
# add Olson timezone to request
|
||||
try:
|
||||
|
@ -400,6 +424,19 @@ def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None,
|
|||
session_cache = SessionCache()
|
||||
session = session_cache.get()
|
||||
|
||||
proxies = {}
|
||||
if proxy:
|
||||
if '\\' in proxy:
|
||||
from .packages.requests_ntlm import HttpNtlmAuth
|
||||
username = proxy.rsplit(':', 1)
|
||||
password = ''
|
||||
if len(username) == 2:
|
||||
password = username[1]
|
||||
username = username[0]
|
||||
session.auth = HttpNtlmAuth(username, password, session)
|
||||
else:
|
||||
proxies['https'] = proxy
|
||||
|
||||
# log time to api
|
||||
response = None
|
||||
try:
|
||||
|
|
156
wakatime/packages/ntlm_auth/U32.py
Normal file
156
wakatime/packages/ntlm_auth/U32.py
Normal file
|
@ -0,0 +1,156 @@
|
|||
# This file is part of 'NTLM Authorization Proxy Server' http://sourceforge.net/projects/ntlmaps/
|
||||
# Copyright 2001 Dmitry A. Rozmanov <dima@xenon.spb.ru>
|
||||
#
|
||||
# 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>.
|
||||
|
||||
from __future__ import division
|
||||
import six
|
||||
|
||||
C = 0x1000000000
|
||||
|
||||
|
||||
def norm(n):
|
||||
return n & 0xFFFFFFFF
|
||||
|
||||
|
||||
class U32:
|
||||
v = 0
|
||||
|
||||
def __init__(self, value=0):
|
||||
if not isinstance(value, six.integer_types):
|
||||
value = six.byte2int(value)
|
||||
|
||||
self.v = C + norm(abs(int(value)))
|
||||
|
||||
def set(self, value=0):
|
||||
self.v = C + norm(abs(int(value)))
|
||||
|
||||
def __repr__(self):
|
||||
return hex(norm(self.v))
|
||||
|
||||
def __long__(self):
|
||||
return int(norm(self.v))
|
||||
|
||||
def __int__(self):
|
||||
return int(norm(self.v))
|
||||
|
||||
def __chr__(self):
|
||||
return chr(norm(self.v))
|
||||
|
||||
def __add__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v + b.v)
|
||||
return r
|
||||
|
||||
def __sub__(self, b):
|
||||
r = U32()
|
||||
if self.v < b.v:
|
||||
r.v = C + norm(0x100000000 - (b.v - self.v))
|
||||
else:
|
||||
r.v = C + norm(self.v - b.v)
|
||||
return r
|
||||
|
||||
def __mul__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v * b.v)
|
||||
return r
|
||||
|
||||
def __div__(self, b):
|
||||
r = U32()
|
||||
r.v = C + (norm(self.v) // norm(b.v))
|
||||
return r
|
||||
|
||||
def __truediv__(self, b):
|
||||
r = U32()
|
||||
r.v = C + (norm(self.v) / norm(b.v))
|
||||
return r
|
||||
|
||||
def __mod__(self, b):
|
||||
r = U32()
|
||||
r.v = C + (norm(self.v) % norm(b.v))
|
||||
return r
|
||||
|
||||
def __neg__(self):
|
||||
return U32(self.v)
|
||||
|
||||
def __pos__(self):
|
||||
return U32(self.v)
|
||||
|
||||
def __abs__(self):
|
||||
return U32(self.v)
|
||||
|
||||
def __invert__(self):
|
||||
r = U32()
|
||||
r.v = C + norm(~self.v)
|
||||
return r
|
||||
|
||||
def __lshift__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v << b)
|
||||
return r
|
||||
|
||||
def __rshift__(self, b):
|
||||
r = U32()
|
||||
r.v = C + (norm(self.v) >> b)
|
||||
return r
|
||||
|
||||
def __and__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v & b.v)
|
||||
return r
|
||||
|
||||
def __or__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v | b.v)
|
||||
return r
|
||||
|
||||
def __xor__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v ^ b.v)
|
||||
return r
|
||||
|
||||
def __not__(self):
|
||||
return U32(not norm(self.v))
|
||||
|
||||
def truth(self):
|
||||
return norm(self.v)
|
||||
|
||||
def __cmp__(self, b):
|
||||
if norm(self.v) > norm(b.v):
|
||||
return 1
|
||||
elif norm(self.v) < norm(b.v):
|
||||
return -1
|
||||
else:
|
||||
return 0
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.v < other.v
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.v > other.v
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.v == other.v
|
||||
|
||||
def __le__(self, other):
|
||||
return self.v <= other.v
|
||||
|
||||
def __ge__(self, other):
|
||||
return self.v >= other.v
|
||||
|
||||
def __ne__(self, other):
|
||||
return self.v != other.v
|
||||
|
||||
def __nonzero__(self):
|
||||
return norm(self.v)
|
3
wakatime/packages/ntlm_auth/__init__.py
Normal file
3
wakatime/packages/ntlm_auth/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from . import ntlm, session_security
|
||||
|
||||
__all__ = ('ntlm', 'session_security')
|
80
wakatime/packages/ntlm_auth/compute_hash.py
Normal file
80
wakatime/packages/ntlm_auth/compute_hash.py
Normal file
|
@ -0,0 +1,80 @@
|
|||
# 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 hashlib
|
||||
import hmac
|
||||
import re
|
||||
from ntlm_auth import des
|
||||
|
||||
|
||||
def _lmowfv1(password):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
3.3.1 NTLM v1 Authentication
|
||||
Same function as LMOWFv1 in document to create a one way hash of the password. Only
|
||||
used in NTLMv1 auth without session security
|
||||
|
||||
:param password: The password of the user we are trying to authenticate with
|
||||
:return res: A Lan Manager hash of the password supplied
|
||||
"""
|
||||
|
||||
# fix the password length to 14 bytes
|
||||
password = password.upper()
|
||||
lm_pw = password[0:14]
|
||||
|
||||
# do hash
|
||||
magic_str = b"KGS!@#$%" # page 56 in [MS-NLMP v28.0]
|
||||
|
||||
res = b''
|
||||
dobj = des.DES(lm_pw[0:7])
|
||||
res = res + dobj.encrypt(magic_str)
|
||||
|
||||
dobj = des.DES(lm_pw[7:14])
|
||||
res = res + dobj.encrypt(magic_str)
|
||||
|
||||
return res
|
||||
|
||||
def _ntowfv1(password):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
3.3.1 NTLM v1 Authentication
|
||||
Same function as NTOWFv1 in document to create a one way hash of the password. Only
|
||||
used in NTLMv1 auth without session security
|
||||
|
||||
:param password: The password of the user we are trying to authenticate with
|
||||
:return digest: An NT hash of the password supplied
|
||||
"""
|
||||
|
||||
digest = hashlib.new('md4', password.encode('utf-16le')).digest()
|
||||
return digest
|
||||
|
||||
def _ntowfv2(user_name, password, domain_name):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
3.3.2 NTLM v2 Authentication
|
||||
Same function as NTOWFv2 (and LMOWFv2) in document to create a one way hash of the password.
|
||||
This combines some extra security features over the v1 calculations used in NTLMv2 auth.
|
||||
|
||||
: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
|
||||
:return digest: An NT hash of the parameters supplied
|
||||
"""
|
||||
digest = _ntowfv1(password)
|
||||
digest = hmac.new(digest, (user_name.upper() + domain_name).encode('utf-16le')).digest()
|
||||
|
||||
return digest
|
138
wakatime/packages/ntlm_auth/compute_keys.py
Normal file
138
wakatime/packages/ntlm_auth/compute_keys.py
Normal file
|
@ -0,0 +1,138 @@
|
|||
# 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 hashlib
|
||||
import hmac
|
||||
from ntlm_auth import des
|
||||
from ntlm_auth.constants import NegotiateFlags
|
||||
|
||||
def _get_exchange_key_ntlm_v1(negotiate_flags, session_base_key, server_challenge, lm_challenge_response, lm_hash):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
4.3.5.1 KXKEY
|
||||
Calculates the Key Exchange Key for NTLMv1 authentication. Used for signing and sealing messages
|
||||
|
||||
@param negotiate_flags:
|
||||
@param session_base_key: A session key calculated from the user password challenge
|
||||
@param server_challenge: A random 8-byte response generated by the server in the CHALLENGE_MESSAGE
|
||||
@param lm_challenge_response: The LmChallengeResponse value computed in ComputeResponse
|
||||
@param lm_hash: The LMOWF computed in Compute Response
|
||||
@return key_exchange_key: The Key Exchange Key (KXKEY) used to sign and seal messages and compute the ExportedSessionKey
|
||||
"""
|
||||
if negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||
key_exchange_key = hmac.new(session_base_key, server_challenge + lm_challenge_response[:8]).digest()
|
||||
elif negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_LM_KEY:
|
||||
des_handler = des.DES(lm_hash[:7])
|
||||
first_des = des_handler.encrypt(lm_challenge_response[:8])
|
||||
des_handler = des.DES(lm_hash[7:8] + binascii.unhexlify('bdbdbdbdbdbdbd'))
|
||||
second_des = des_handler.encrypt(lm_challenge_response[:8])
|
||||
|
||||
key_exchange_key = first_des + second_des
|
||||
elif negotiate_flags & NegotiateFlags.NTLMSSP_REQUEST_NON_NT_SESSION_KEY:
|
||||
key_exchange_key = lm_hash[:8] + b'\0' * 8
|
||||
else:
|
||||
key_exchange_key = session_base_key
|
||||
|
||||
return key_exchange_key
|
||||
|
||||
def _get_exchange_key_ntlm_v2(session_base_key):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
4.3.5.1 KXKEY
|
||||
Calculates the Key Exchange Key for NTLMv2 authentication. Used for signing and sealing messages.
|
||||
According to docs, 'If NTLM v2 is used, KeyExchangeKey MUST be set to the given 128-bit SessionBaseKey
|
||||
|
||||
@param session_base_key: A session key calculated from the user password challenge
|
||||
@return key_exchange_key: The Key Exchange Key (KXKEY) used to sign and seal messages
|
||||
"""
|
||||
return session_base_key
|
||||
|
||||
def get_sign_key(exported_session_key, magic_constant):
|
||||
"""
|
||||
3.4.5.2 SIGNKEY
|
||||
|
||||
@param exported_session_key: A 128-bit session key used to derive signing and sealing keys
|
||||
@param magic_constant: A constant value set in the MS-NLMP documentation (constants.SignSealConstants)
|
||||
@return sign_key: Key used to sign messages
|
||||
"""
|
||||
|
||||
sign_key = hashlib.md5(exported_session_key + magic_constant).digest()
|
||||
|
||||
return sign_key
|
||||
|
||||
def get_seal_key(negotiate_flags, exported_session_key, magic_constant):
|
||||
"""
|
||||
3.4.5.3. SEALKEY
|
||||
Main method to use to calculate the seal_key used to seal (encrypt) messages. This will determine
|
||||
the correct method below to use based on the compatibility flags set and should be called instead
|
||||
of the others
|
||||
|
||||
@param exported_session_key: A 128-bit session key used to derive signing and sealing keys
|
||||
@param negotiate_flags: The negotiate_flags structure sent by the server
|
||||
@param magic_constant: A constant value set in the MS-NLMP documentation (constants.SignSealConstants)
|
||||
@return seal_key: Key used to seal messages
|
||||
"""
|
||||
|
||||
if negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||
seal_key = _get_seal_key_ntlm2(negotiate_flags, exported_session_key, magic_constant)
|
||||
elif negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_LM_KEY:
|
||||
seal_key = _get_seal_key_ntlm1(negotiate_flags, exported_session_key)
|
||||
else:
|
||||
seal_key = exported_session_key
|
||||
|
||||
return seal_key
|
||||
|
||||
def _get_seal_key_ntlm1(negotiate_flags, exported_session_key):
|
||||
"""
|
||||
3.4.5.3 SEALKEY
|
||||
Calculates the seal_key used to seal (encrypt) messages. This for authentication where
|
||||
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY has not been negotiated. Will weaken the keys
|
||||
if NTLMSSP_NEGOTIATE_56 is not negotiated it will default to the 40-bit key
|
||||
|
||||
@param negotiate_flags: The negotiate_flags structure sent by the server
|
||||
@param exported_session_key: A 128-bit session key used to derive signing and sealing keys
|
||||
@return seal_key: Key used to seal messages
|
||||
"""
|
||||
if negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_56:
|
||||
seal_key = exported_session_key[:7] + binascii.unhexlify('a0')
|
||||
else:
|
||||
seal_key = exported_session_key[:5] + binascii.unhexlify('e538b0')
|
||||
|
||||
return seal_key
|
||||
|
||||
def _get_seal_key_ntlm2(negotiate_flags, exported_session_key, magic_constant):
|
||||
"""
|
||||
3.4.5.3 SEALKEY
|
||||
Calculates the seal_key used to seal (encrypt) messages. This for authentication where
|
||||
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY has been negotiated. Will weaken the keys
|
||||
if NTLMSSP_NEGOTIATE_128 is not negotiated, will try NEGOTIATE_56 and then will default
|
||||
to the 40-bit key
|
||||
|
||||
@param negotiate_flags: The negotiate_flags structure sent by the server
|
||||
@param exported_session_key: A 128-bit session key used to derive signing and sealing keys
|
||||
@param magic_constant: A constant value set in the MS-NLMP documentation (constants.SignSealConstants)
|
||||
@return seal_key: Key used to seal messages
|
||||
"""
|
||||
if negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_128:
|
||||
seal_key = exported_session_key
|
||||
elif negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_56:
|
||||
seal_key = exported_session_key[:7]
|
||||
else:
|
||||
seal_key = exported_session_key[:5]
|
||||
|
||||
seal_key = hashlib.md5(seal_key + magic_constant).digest()
|
||||
|
||||
return seal_key
|
397
wakatime/packages/ntlm_auth/compute_response.py
Normal file
397
wakatime/packages/ntlm_auth/compute_response.py
Normal file
|
@ -0,0 +1,397 @@
|
|||
# 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 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
|
||||
from ntlm_auth import des
|
||||
from ntlm_auth.constants import NegotiateFlags, AvFlags
|
||||
from ntlm_auth.gss_channel_bindings import GssChannelBindingsStruct
|
||||
from ntlm_auth.target_info import TargetInfo
|
||||
|
||||
class ComputeResponse():
|
||||
"""
|
||||
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
|
||||
"""
|
||||
def __init__(self, user_name, password, domain_name, challenge_message, ntlm_compatibility):
|
||||
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 = ComputeResponse._get_LMv1_with_session_security_response(self._client_challenge)
|
||||
|
||||
elif 0 <= self._ntlm_compatibility <= 1:
|
||||
response = ComputeResponse._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 = ComputeResponse._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 = ComputeResponse._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[TargetInfo.MSV_AV_TIMESTAMP]
|
||||
if timestamp is not None:
|
||||
response = b'\0' * 24
|
||||
|
||||
return response
|
||||
|
||||
def get_nt_challenge_response(self, lm_challenge_response, server_certificate_hash):
|
||||
"""
|
||||
[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 beforeand, 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
|
||||
: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 = ComputeResponse._get_NTLM2_response(self._password, self._server_challenge, self._client_challenge)
|
||||
key_exchange_key = compkeys._get_exchange_key_ntlm_v1(self._negotiate_flags, session_base_key,
|
||||
self._server_challenge, lm_challenge_response,
|
||||
comphash._lmowfv1(self._password))
|
||||
target_info = None
|
||||
|
||||
elif 0 <= self._ntlm_compatibility < 3:
|
||||
response, session_base_key = ComputeResponse._get_NTLMv1_response(self._password, self._server_challenge)
|
||||
key_exchange_key = compkeys._get_exchange_key_ntlm_v1(self._negotiate_flags, session_base_key,
|
||||
self._server_challenge, lm_challenge_response,
|
||||
comphash._lmowfv1(self._password))
|
||||
target_info = None
|
||||
else:
|
||||
if self._server_target_info is None:
|
||||
target_info = TargetInfo()
|
||||
else:
|
||||
target_info = self._server_target_info
|
||||
|
||||
if target_info[TargetInfo.MSV_AV_TIMESTAMP] is None:
|
||||
timestamp = get_windows_timestamp()
|
||||
else:
|
||||
timestamp = target_info[TargetInfo.MSV_AV_TIMESTAMP][1]
|
||||
|
||||
# [MS-NLMP] If the CHALLENGE_MESSAGE TargetInfo field has an MsvAvTimestamp present, the client SHOULD provide a MIC
|
||||
target_info[TargetInfo.MSV_AV_FLAGS] = struct.pack("<L", AvFlags.MIC_PROVIDED)
|
||||
|
||||
if server_certificate_hash != None:
|
||||
channel_bindings_hash = ComputeResponse._get_channel_bindings_value(server_certificate_hash)
|
||||
target_info[TargetInfo.MSV_AV_CHANNEL_BINDINGS] = channel_bindings_hash
|
||||
|
||||
response, session_base_key = ComputeResponse._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'\0' * 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)
|
||||
lm_hash = hmac.new(nt_hash, (server_challenge + client_challenge)).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)
|
||||
nt_session_hash = hashlib.md5(server_challenge + client_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)).digest()
|
||||
response = nt_proof_str + temp
|
||||
|
||||
session_base_key = hmac.new(nt_hash, nt_proof_str).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'\1'
|
||||
hi_resp_type = b'\1'
|
||||
reserved1 = b'\0' * 2
|
||||
reserved2 = b'\0' * 4
|
||||
reserved3 = b'\0' * 4
|
||||
reserved4 = b'\0' * 4 # This byte is not in the structure defined in 2.2.2.7 but is in the computation guide, works with it present
|
||||
|
||||
temp = resp_type + hi_resp_type + reserved1 + \
|
||||
reserved2 + \
|
||||
timestamp + \
|
||||
client_challenge + \
|
||||
reserved3 + \
|
||||
target_info.get_data() + 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'\0' * (21 - len(password_hash))
|
||||
|
||||
res = b''
|
||||
dobj = des.DES(password_hash[0:7])
|
||||
res = res + dobj.encrypt(server_challenge[0:8])
|
||||
|
||||
dobj = des.DES(password_hash[7:14])
|
||||
res = res + dobj.encrypt(server_challenge[0:8])
|
||||
|
||||
dobj = des.DES(password_hash[14:21])
|
||||
res = res + dobj.encrypt(server_challenge[0:8])
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def _get_channel_bindings_value(server_certificate_hash):
|
||||
"""
|
||||
https://msdn.microsoft.com/en-us/library/windows/desktop/dd919963%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
|
||||
https://blogs.msdn.microsoft.com/openspecification/2013/03/26/ntlm-and-channel-binding-hash-aka-extended-protection-for-authentication/
|
||||
|
||||
Get's the MD5 hash of the gss_channel_bindings_struct to add to the AV_PAIR MSV_AV_CHANNEL_BINDINGS.
|
||||
This method takes in the SHA256 hash (Hash of the DER encoded certificate of the server we are connecting to)
|
||||
and add's it to the gss_channel_bindings_struct. It then gets the MD5 hash and converts this to a
|
||||
byte array in preparation of adding it to the AV_PAIR structure.
|
||||
|
||||
:param server_certificate_hash: The SHA256 hash of the server certificate (DER encoded) NTLM is authenticated to
|
||||
:return channel_bindings: An MD5 hash of the gss_channel_bindings_struct to add to the AV_PAIR MsvChannelBindings
|
||||
"""
|
||||
# Channel Binding Tokens support, used for NTLMv2
|
||||
# Decode the SHA256 certificate hash
|
||||
certificate_digest = base64.b16decode(server_certificate_hash)
|
||||
|
||||
# Initialise the GssChannelBindingsStruct and add the certificate_digest to the application_data field
|
||||
gss_channel_bindings = GssChannelBindingsStruct()
|
||||
gss_channel_bindings[gss_channel_bindings.APPLICATION_DATA] = 'tls-server-end-point:'.encode() + certificate_digest
|
||||
|
||||
# Get the gss_channel_bindings_struct and create an MD5 hash
|
||||
channel_bindings_struct_data = gss_channel_bindings.get_data()
|
||||
channel_bindings_hash = hashlib.md5(channel_bindings_struct_data).hexdigest()
|
||||
|
||||
try:
|
||||
cbt_value = bytearray.fromhex(channel_bindings_hash)
|
||||
except TypeError:
|
||||
# Work-around for Python 2.6 bug
|
||||
cbt_value = bytearray.fromhex(unicode(channel_bindings_hash))
|
||||
|
||||
channel_bindings = bytes(cbt_value)
|
||||
return channel_bindings
|
||||
|
||||
|
||||
def get_windows_timestamp():
|
||||
# Get Windows Date time, 100 nanoseconds since 1601-01-01 in a 64 bit structure
|
||||
timestamp = struct.pack('<q', (116444736000000000 + calendar.timegm(time.gmtime()) * 10000000))
|
||||
|
||||
return timestamp
|
92
wakatime/packages/ntlm_auth/constants.py
Normal file
92
wakatime/packages/ntlm_auth/constants.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
# 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>.
|
||||
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2 Message Syntax
|
||||
The signature field used in NTLM messages
|
||||
"""
|
||||
NTLM_SIGNATURE = b'NTLMSSP\0'
|
||||
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2 Message Syntax
|
||||
The 3 message type options you can have in a message.
|
||||
"""
|
||||
class MessageTypes(object):
|
||||
NTLM_NEGOTIATE = 0x1
|
||||
NTLM_CHALLENGE = 0x2
|
||||
NTLM_AUTHENTICATE = 0x3
|
||||
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2.2.1 AV_PAIR (MsvAvFlags)
|
||||
A 32-bit value indicated server or client configuration
|
||||
"""
|
||||
class AvFlags(object):
|
||||
AUTHENTICATION_CONSTRAINED = 0x1
|
||||
MIC_PROVIDED = 0x2
|
||||
UNTRUSTED_SPN_SOURCE = 0x4
|
||||
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2.2.5 NEGOTIATE
|
||||
During NTLM authentication, each of the following flags is a possible value of the
|
||||
NegotiateFlags field of the NEGOTIATE_MESSAGE, CHALLENGE_MESSAGE and AUTHENTICATE_MESSAGE,
|
||||
unless otherwise noted. These flags define client or server NTLM capabilities
|
||||
supported by the sender.
|
||||
"""
|
||||
class NegotiateFlags(object):
|
||||
NTLMSSP_NEGOTIATE_56 = 0x80000000
|
||||
NTLMSSP_NEGOTIATE_KEY_EXCH = 0x40000000
|
||||
NTLMSSP_NEGOTIATE_128 = 0x20000000
|
||||
NTLMSSP_RESERVED_R1 = 0x10000000
|
||||
NTLMSSP_RESERVED_R2 = 0x08000000
|
||||
NTLMSSP_RESERVED_R3 = 0x04000000
|
||||
NTLMSSP_NEGOTIATE_VERSION = 0x02000000
|
||||
NTLMSSP_RESERVED_R4 = 0x01000000
|
||||
NTLMSSP_NEGOTIATE_TARGET_INFO = 0x00800000
|
||||
NTLMSSP_REQUEST_NON_NT_SESSION_KEY = 0x00400000
|
||||
NTLMSSP_RESERVED_R5 = 0x00200000
|
||||
NTLMSSP_NEGOTIATE_IDENTITY = 0x00100000
|
||||
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY = 0x00080000
|
||||
NTLMSSP_RESERVED_R6 = 0x00040000
|
||||
NTLMSSP_TARGET_TYPE_SERVER = 0x00020000
|
||||
NTLMSSP_TARGET_TYPE_DOMAIN = 0x00010000
|
||||
NTLMSSP_NEGOTIATE_ALWAYS_SIGN = 0x00008000
|
||||
NTLMSSP_RESERVED_R7 = 0x00004000
|
||||
NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED = 0x00002000
|
||||
NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED = 0x00001000
|
||||
NTLMSSP_ANOYNMOUS = 0x00000800
|
||||
NTLMSSP_RESERVED_R8 = 0x00000400
|
||||
NTLMSSP_NEGOTIATE_NTLM = 0x00000200
|
||||
NTLMSSP_RESERVED_R9 = 0x00000100
|
||||
NTLMSSP_NEGOTIATE_LM_KEY = 0x00000080
|
||||
NTLMSSP_NEGOTIATE_DATAGRAM = 0x00000040
|
||||
NTLMSSP_NEGOTIATE_SEAL = 0x00000020
|
||||
NTLMSSP_NEGOTIATE_SIGN = 0x00000010
|
||||
NTLMSSP_RESERVED_R10 = 0x00000008
|
||||
NTLMSSP_REQUEST_TARGET = 0x00000004
|
||||
NTLMSSP_NEGOTIATE_OEM = 0x00000002
|
||||
NTLMSSP_NEGOTIATE_UNICODE = 0x00000001
|
||||
|
||||
class SignSealConstants(object):
|
||||
# Magic Contants used to get the signing and sealing key for Extended Session Security
|
||||
CLIENT_SIGNING = b"session key to client-to-server signing key magic constant\0"
|
||||
SERVER_SIGNING = b"session key to server-to-client signing key magic constant\0"
|
||||
CLIENT_SEALING = b"session key to client-to-server sealing key magic constant\0"
|
||||
SERVER_SEALING = b"session key to server-to-client sealing key magic constant\0"
|
88
wakatime/packages/ntlm_auth/des.py
Normal file
88
wakatime/packages/ntlm_auth/des.py
Normal file
|
@ -0,0 +1,88 @@
|
|||
# This file is part of 'NTLM Authorization Proxy Server' http://sourceforge.net/projects/ntlmaps/
|
||||
# Copyright 2001 Dmitry A. Rozmanov <dima@xenon.spb.ru>
|
||||
#
|
||||
# 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 logging
|
||||
import six
|
||||
from ntlm_auth import des_c
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DES:
|
||||
des_c_obj = None
|
||||
|
||||
def __init__(self, key_str):
|
||||
k = str_to_key56(key_str)
|
||||
k = key56_to_key64(k)
|
||||
|
||||
key_str = b''
|
||||
for i in k:
|
||||
key_str += six.int2byte(i & 0xFF)
|
||||
|
||||
self.des_c_obj = des_c.DES(key_str)
|
||||
|
||||
def encrypt(self, plain_text):
|
||||
return self.des_c_obj.encrypt(plain_text)
|
||||
|
||||
def decrypt(self, crypted_text):
|
||||
return self.des_c_obj.decrypt(crypted_text)
|
||||
|
||||
|
||||
DESException = 'DESException'
|
||||
|
||||
|
||||
def str_to_key56(key_str):
|
||||
|
||||
if not type(key_str) == six.binary_type:
|
||||
# TODO rsanders high - figure out how to make this not necessary
|
||||
key_str = key_str.encode('ascii')
|
||||
|
||||
if len(key_str) < 7:
|
||||
key_str = key_str + b'\000\000\000\000\000\000\000'[:(7 - len(key_str))]
|
||||
key_56 = []
|
||||
for i in six.iterbytes(key_str[:7]):
|
||||
key_56.append(i)
|
||||
|
||||
return key_56
|
||||
|
||||
|
||||
def key56_to_key64(key_56):
|
||||
key = []
|
||||
for i in range(8):
|
||||
key.append(0)
|
||||
|
||||
key[0] = key_56[0]
|
||||
key[1] = ((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1)
|
||||
key[2] = ((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2)
|
||||
key[3] = ((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3)
|
||||
key[4] = ((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4)
|
||||
key[5] = ((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5)
|
||||
key[6] = ((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6)
|
||||
key[7] = (key_56[6] << 1) & 0xFF
|
||||
|
||||
key = set_key_odd_parity(key)
|
||||
|
||||
return key
|
||||
|
||||
|
||||
def set_key_odd_parity(key):
|
||||
for i in range(len(key)):
|
||||
for k in range(7):
|
||||
bit = 0
|
||||
t = key[i] >> k
|
||||
bit = (t ^ bit) & 0x1
|
||||
key[i] = (key[i] & 0xFE) | bit
|
||||
|
||||
return key
|
254
wakatime/packages/ntlm_auth/des_c.py
Normal file
254
wakatime/packages/ntlm_auth/des_c.py
Normal file
|
@ -0,0 +1,254 @@
|
|||
# This file is part of 'NTLM Authorization Proxy Server' http://sourceforge.net/projects/ntlmaps/
|
||||
# Copyright 2001 Dmitry A. Rozmanov <dima@xenon.spb.ru>
|
||||
#
|
||||
# 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 six
|
||||
|
||||
from ntlm_auth.U32 import U32
|
||||
from ntlm_auth.des_data import des_SPtrans, des_skb
|
||||
|
||||
def c2l(c):
|
||||
"char[4] to unsigned long"
|
||||
l = U32(c[0])
|
||||
l = l | (U32(c[1]) << 8)
|
||||
l = l | (U32(c[2]) << 16)
|
||||
l = l | (U32(c[3]) << 24)
|
||||
return l
|
||||
|
||||
|
||||
def l2c(l):
|
||||
"unsigned long to char[4]"
|
||||
c = []
|
||||
c.append(int(l & U32(0xFF)))
|
||||
c.append(int((l >> 8) & U32(0xFF)))
|
||||
c.append(int((l >> 16) & U32(0xFF)))
|
||||
c.append(int((l >> 24) & U32(0xFF)))
|
||||
return c
|
||||
|
||||
|
||||
def D_ENCRYPT(tup, u, t, s):
|
||||
L, R, S = tup
|
||||
# print 'LRS1', L, R, S, u, t, '-->',
|
||||
u = (R ^ s[S])
|
||||
t = R ^ s[S + 1]
|
||||
t = ((t >> 4) + (t << 28))
|
||||
L = L ^ (des_SPtrans[1][int((t) & U32(0x3f))] |
|
||||
des_SPtrans[3][int((t >> 8) & U32(0x3f))] |
|
||||
des_SPtrans[5][int((t >> 16) & U32(0x3f))] |
|
||||
des_SPtrans[7][int((t >> 24) & U32(0x3f))] |
|
||||
des_SPtrans[0][int((u) & U32(0x3f))] |
|
||||
des_SPtrans[2][int((u >> 8) & U32(0x3f))] |
|
||||
des_SPtrans[4][int((u >> 16) & U32(0x3f))] |
|
||||
des_SPtrans[6][int((u >> 24) & U32(0x3f))])
|
||||
# print 'LRS:', L, R, S, u, t
|
||||
return (L, R, S), u, t, s
|
||||
|
||||
|
||||
def PERM_OP(tup, n, m):
|
||||
"tup - (a, b, t)"
|
||||
a, b, t = tup
|
||||
t = ((a >> n) ^ b) & m
|
||||
b = b ^ t
|
||||
a = a ^ (t << n)
|
||||
return (a, b, t)
|
||||
|
||||
|
||||
def HPERM_OP(tup, n, m):
|
||||
"tup - (a, t)"
|
||||
a, t = tup
|
||||
t = ((a << (16 - n)) ^ a) & m
|
||||
a = a ^ t ^ (t >> (16 - n))
|
||||
return a, t
|
||||
|
||||
|
||||
shifts2 = [0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0]
|
||||
|
||||
|
||||
class DES:
|
||||
KeySched = None # des_key_schedule
|
||||
|
||||
def __init__(self, key_str):
|
||||
self.KeySched = des_set_key(key_str)
|
||||
|
||||
def decrypt(self, str):
|
||||
# block - UChar[]
|
||||
block = []
|
||||
|
||||
for i in six.iterbytes(str):
|
||||
block.append(i)
|
||||
|
||||
# print block
|
||||
block = des_ecb_encrypt(block, self.KeySched, 0)
|
||||
|
||||
res = b''
|
||||
for i in block:
|
||||
res = res + six.int2byte(i)
|
||||
|
||||
return res
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
# block - UChar[]
|
||||
|
||||
block = []
|
||||
for i in plaintext:
|
||||
block.append(i)
|
||||
|
||||
block = des_ecb_encrypt(block, self.KeySched, 1)
|
||||
|
||||
res = b''
|
||||
|
||||
for i in block:
|
||||
res += six.int2byte(i)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def des_encript(input, ks, encrypt):
|
||||
# input - U32[]
|
||||
# output - U32[]
|
||||
# ks - des_key_shedule - U32[2][16]
|
||||
# encrypt - int
|
||||
# l, r, t, u - U32
|
||||
# i - int
|
||||
# s - U32[]
|
||||
|
||||
l = input[0]
|
||||
r = input[1]
|
||||
t = U32(0)
|
||||
u = U32(0)
|
||||
|
||||
r, l, t = PERM_OP((r, l, t), 4, U32(0x0f0f0f0f))
|
||||
l, r, t = PERM_OP((l, r, t), 16, U32(0x0000ffff))
|
||||
r, l, t = PERM_OP((r, l, t), 2, U32(0x33333333))
|
||||
l, r, t = PERM_OP((l, r, t), 8, U32(0x00ff00ff))
|
||||
r, l, t = PERM_OP((r, l, t), 1, U32(0x55555555))
|
||||
|
||||
t = (r << 1) | (r >> 31)
|
||||
r = (l << 1) | (l >> 31)
|
||||
l = t
|
||||
|
||||
s = ks # ???????????????
|
||||
# print l, r
|
||||
if encrypt:
|
||||
for i in range(0, 32, 4):
|
||||
rtup, u, t, s = D_ENCRYPT((l, r, i + 0), u, t, s)
|
||||
l = rtup[0]
|
||||
r = rtup[1]
|
||||
rtup, u, t, s = D_ENCRYPT((r, l, i + 2), u, t, s)
|
||||
r = rtup[0]
|
||||
l = rtup[1]
|
||||
else:
|
||||
for i in range(30, 0, -4):
|
||||
rtup, u, t, s = D_ENCRYPT((l, r, i - 0), u, t, s)
|
||||
l = rtup[0]
|
||||
r = rtup[1]
|
||||
rtup, u, t, s = D_ENCRYPT((r, l, i - 2), u, t, s)
|
||||
r = rtup[0]
|
||||
l = rtup[1]
|
||||
# print l, r
|
||||
l = (l >> 1) | (l << 31)
|
||||
r = (r >> 1) | (r << 31)
|
||||
|
||||
r, l, t = PERM_OP((r, l, t), 1, U32(0x55555555))
|
||||
l, r, t = PERM_OP((l, r, t), 8, U32(0x00ff00ff))
|
||||
r, l, t = PERM_OP((r, l, t), 2, U32(0x33333333))
|
||||
l, r, t = PERM_OP((l, r, t), 16, U32(0x0000ffff))
|
||||
r, l, t = PERM_OP((r, l, t), 4, U32(0x0f0f0f0f))
|
||||
|
||||
output = [l]
|
||||
output.append(r)
|
||||
l, r, t, u = U32(0), U32(0), U32(0), U32(0)
|
||||
return output
|
||||
|
||||
|
||||
def des_ecb_encrypt(input, ks, encrypt):
|
||||
# input - des_cblock - UChar[8]
|
||||
# output - des_cblock - UChar[8]
|
||||
# ks - des_key_shedule - U32[2][16]
|
||||
# encrypt - int
|
||||
|
||||
# print input
|
||||
l0 = c2l(input[0:4])
|
||||
l1 = c2l(input[4:8])
|
||||
ll = [l0]
|
||||
ll.append(l1)
|
||||
# print ll
|
||||
ll = des_encript(ll, ks, encrypt)
|
||||
# print ll
|
||||
l0 = ll[0]
|
||||
l1 = ll[1]
|
||||
output = l2c(l0)
|
||||
output = output + l2c(l1)
|
||||
# print output
|
||||
l0, l1, ll[0], ll[1] = U32(0), U32(0), U32(0), U32(0)
|
||||
return output
|
||||
|
||||
|
||||
def des_set_key(key):
|
||||
# key - des_cblock - UChar[8]
|
||||
# schedule - des_key_schedule
|
||||
|
||||
# register unsigned long c,d,t,s;
|
||||
# register unsigned char *in;
|
||||
# register unsigned long *k;
|
||||
# register int i;
|
||||
|
||||
# k = schedule
|
||||
# in = key
|
||||
|
||||
k = []
|
||||
c = c2l(key[0:4])
|
||||
d = c2l(key[4:8])
|
||||
t = U32(0)
|
||||
|
||||
d, c, t = PERM_OP((d, c, t), 4, U32(0x0f0f0f0f))
|
||||
c, t = HPERM_OP((c, t), -2, U32(0xcccc0000))
|
||||
d, t = HPERM_OP((d, t), -2, U32(0xcccc0000))
|
||||
d, c, t = PERM_OP((d, c, t), 1, U32(0x55555555))
|
||||
c, d, t = PERM_OP((c, d, t), 8, U32(0x00ff00ff))
|
||||
d, c, t = PERM_OP((d, c, t), 1, U32(0x55555555))
|
||||
|
||||
d = (((d & U32(0x000000ff)) << 16) | (d & U32(0x0000ff00)) | ((d & U32(0x00ff0000)) >> 16) | (
|
||||
(c & U32(0xf0000000)) >> 4))
|
||||
c = c & U32(0x0fffffff)
|
||||
|
||||
for i in range(16):
|
||||
if (shifts2[i]):
|
||||
c = ((c >> 2) | (c << 26))
|
||||
d = ((d >> 2) | (d << 26))
|
||||
else:
|
||||
c = ((c >> 1) | (c << 27))
|
||||
d = ((d >> 1) | (d << 27))
|
||||
c = c & U32(0x0fffffff)
|
||||
d = d & U32(0x0fffffff)
|
||||
|
||||
s = des_skb[0][int((c) & U32(0x3f))] | \
|
||||
des_skb[1][int(((c >> 6) & U32(0x03)) | ((c >> 7) & U32(0x3c)))] | \
|
||||
des_skb[2][int(((c >> 13) & U32(0x0f)) | ((c >> 14) & U32(0x30)))] | \
|
||||
des_skb[3][int(((c >> 20) & U32(0x01)) | ((c >> 21) & U32(0x06)) | ((c >> 22) & U32(0x38)))]
|
||||
|
||||
t = des_skb[4][int((d) & U32(0x3f))] | \
|
||||
des_skb[5][int(((d >> 7) & U32(0x03)) | ((d >> 8) & U32(0x3c)))] | \
|
||||
des_skb[6][int((d >> 15) & U32(0x3f))] | \
|
||||
des_skb[7][int(((d >> 21) & U32(0x0f)) | ((d >> 22) & U32(0x30)))]
|
||||
# print s, t
|
||||
|
||||
k.append(((t << 16) | (s & U32(0x0000ffff))) & U32(0xffffffff))
|
||||
s = ((s >> 16) | (t & U32(0xffff0000)))
|
||||
s = (s << 4) | (s >> 28)
|
||||
k.append(s & U32(0xffffffff))
|
||||
|
||||
schedule = k
|
||||
|
||||
return schedule
|
348
wakatime/packages/ntlm_auth/des_data.py
Normal file
348
wakatime/packages/ntlm_auth/des_data.py
Normal file
|
@ -0,0 +1,348 @@
|
|||
# This file is part of 'NTLM Authorization Proxy Server' http://sourceforge.net/projects/ntlmaps/
|
||||
# Copyright 2001 Dmitry A. Rozmanov <dima@xenon.spb.ru>
|
||||
#
|
||||
# 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>.
|
||||
|
||||
from ntlm_auth.U32 import U32
|
||||
|
||||
# static unsigned long des_SPtrans[8][64]={
|
||||
|
||||
des_SPtrans = \
|
||||
[
|
||||
# nibble 0
|
||||
[
|
||||
U32(0x00820200), U32(0x00020000), U32(0x80800000), U32(0x80820200),
|
||||
U32(0x00800000), U32(0x80020200), U32(0x80020000), U32(0x80800000),
|
||||
U32(0x80020200), U32(0x00820200), U32(0x00820000), U32(0x80000200),
|
||||
U32(0x80800200), U32(0x00800000), U32(0x00000000), U32(0x80020000),
|
||||
U32(0x00020000), U32(0x80000000), U32(0x00800200), U32(0x00020200),
|
||||
U32(0x80820200), U32(0x00820000), U32(0x80000200), U32(0x00800200),
|
||||
U32(0x80000000), U32(0x00000200), U32(0x00020200), U32(0x80820000),
|
||||
U32(0x00000200), U32(0x80800200), U32(0x80820000), U32(0x00000000),
|
||||
U32(0x00000000), U32(0x80820200), U32(0x00800200), U32(0x80020000),
|
||||
U32(0x00820200), U32(0x00020000), U32(0x80000200), U32(0x00800200),
|
||||
U32(0x80820000), U32(0x00000200), U32(0x00020200), U32(0x80800000),
|
||||
U32(0x80020200), U32(0x80000000), U32(0x80800000), U32(0x00820000),
|
||||
U32(0x80820200), U32(0x00020200), U32(0x00820000), U32(0x80800200),
|
||||
U32(0x00800000), U32(0x80000200), U32(0x80020000), U32(0x00000000),
|
||||
U32(0x00020000), U32(0x00800000), U32(0x80800200), U32(0x00820200),
|
||||
U32(0x80000000), U32(0x80820000), U32(0x00000200), U32(0x80020200),
|
||||
],
|
||||
|
||||
# nibble 1
|
||||
[
|
||||
U32(0x10042004), U32(0x00000000), U32(0x00042000), U32(0x10040000),
|
||||
U32(0x10000004), U32(0x00002004), U32(0x10002000), U32(0x00042000),
|
||||
U32(0x00002000), U32(0x10040004), U32(0x00000004), U32(0x10002000),
|
||||
U32(0x00040004), U32(0x10042000), U32(0x10040000), U32(0x00000004),
|
||||
U32(0x00040000), U32(0x10002004), U32(0x10040004), U32(0x00002000),
|
||||
U32(0x00042004), U32(0x10000000), U32(0x00000000), U32(0x00040004),
|
||||
U32(0x10002004), U32(0x00042004), U32(0x10042000), U32(0x10000004),
|
||||
U32(0x10000000), U32(0x00040000), U32(0x00002004), U32(0x10042004),
|
||||
U32(0x00040004), U32(0x10042000), U32(0x10002000), U32(0x00042004),
|
||||
U32(0x10042004), U32(0x00040004), U32(0x10000004), U32(0x00000000),
|
||||
U32(0x10000000), U32(0x00002004), U32(0x00040000), U32(0x10040004),
|
||||
U32(0x00002000), U32(0x10000000), U32(0x00042004), U32(0x10002004),
|
||||
U32(0x10042000), U32(0x00002000), U32(0x00000000), U32(0x10000004),
|
||||
U32(0x00000004), U32(0x10042004), U32(0x00042000), U32(0x10040000),
|
||||
U32(0x10040004), U32(0x00040000), U32(0x00002004), U32(0x10002000),
|
||||
U32(0x10002004), U32(0x00000004), U32(0x10040000), U32(0x00042000),
|
||||
],
|
||||
|
||||
# nibble 2
|
||||
[
|
||||
U32(0x41000000), U32(0x01010040), U32(0x00000040), U32(0x41000040),
|
||||
U32(0x40010000), U32(0x01000000), U32(0x41000040), U32(0x00010040),
|
||||
U32(0x01000040), U32(0x00010000), U32(0x01010000), U32(0x40000000),
|
||||
U32(0x41010040), U32(0x40000040), U32(0x40000000), U32(0x41010000),
|
||||
U32(0x00000000), U32(0x40010000), U32(0x01010040), U32(0x00000040),
|
||||
U32(0x40000040), U32(0x41010040), U32(0x00010000), U32(0x41000000),
|
||||
U32(0x41010000), U32(0x01000040), U32(0x40010040), U32(0x01010000),
|
||||
U32(0x00010040), U32(0x00000000), U32(0x01000000), U32(0x40010040),
|
||||
U32(0x01010040), U32(0x00000040), U32(0x40000000), U32(0x00010000),
|
||||
U32(0x40000040), U32(0x40010000), U32(0x01010000), U32(0x41000040),
|
||||
U32(0x00000000), U32(0x01010040), U32(0x00010040), U32(0x41010000),
|
||||
U32(0x40010000), U32(0x01000000), U32(0x41010040), U32(0x40000000),
|
||||
U32(0x40010040), U32(0x41000000), U32(0x01000000), U32(0x41010040),
|
||||
U32(0x00010000), U32(0x01000040), U32(0x41000040), U32(0x00010040),
|
||||
U32(0x01000040), U32(0x00000000), U32(0x41010000), U32(0x40000040),
|
||||
U32(0x41000000), U32(0x40010040), U32(0x00000040), U32(0x01010000),
|
||||
],
|
||||
|
||||
# nibble 3
|
||||
[
|
||||
U32(0x00100402), U32(0x04000400), U32(0x00000002), U32(0x04100402),
|
||||
U32(0x00000000), U32(0x04100000), U32(0x04000402), U32(0x00100002),
|
||||
U32(0x04100400), U32(0x04000002), U32(0x04000000), U32(0x00000402),
|
||||
U32(0x04000002), U32(0x00100402), U32(0x00100000), U32(0x04000000),
|
||||
U32(0x04100002), U32(0x00100400), U32(0x00000400), U32(0x00000002),
|
||||
U32(0x00100400), U32(0x04000402), U32(0x04100000), U32(0x00000400),
|
||||
U32(0x00000402), U32(0x00000000), U32(0x00100002), U32(0x04100400),
|
||||
U32(0x04000400), U32(0x04100002), U32(0x04100402), U32(0x00100000),
|
||||
U32(0x04100002), U32(0x00000402), U32(0x00100000), U32(0x04000002),
|
||||
U32(0x00100400), U32(0x04000400), U32(0x00000002), U32(0x04100000),
|
||||
U32(0x04000402), U32(0x00000000), U32(0x00000400), U32(0x00100002),
|
||||
U32(0x00000000), U32(0x04100002), U32(0x04100400), U32(0x00000400),
|
||||
U32(0x04000000), U32(0x04100402), U32(0x00100402), U32(0x00100000),
|
||||
U32(0x04100402), U32(0x00000002), U32(0x04000400), U32(0x00100402),
|
||||
U32(0x00100002), U32(0x00100400), U32(0x04100000), U32(0x04000402),
|
||||
U32(0x00000402), U32(0x04000000), U32(0x04000002), U32(0x04100400),
|
||||
],
|
||||
|
||||
# nibble 4
|
||||
[
|
||||
U32(0x02000000), U32(0x00004000), U32(0x00000100), U32(0x02004108),
|
||||
U32(0x02004008), U32(0x02000100), U32(0x00004108), U32(0x02004000),
|
||||
U32(0x00004000), U32(0x00000008), U32(0x02000008), U32(0x00004100),
|
||||
U32(0x02000108), U32(0x02004008), U32(0x02004100), U32(0x00000000),
|
||||
U32(0x00004100), U32(0x02000000), U32(0x00004008), U32(0x00000108),
|
||||
U32(0x02000100), U32(0x00004108), U32(0x00000000), U32(0x02000008),
|
||||
U32(0x00000008), U32(0x02000108), U32(0x02004108), U32(0x00004008),
|
||||
U32(0x02004000), U32(0x00000100), U32(0x00000108), U32(0x02004100),
|
||||
U32(0x02004100), U32(0x02000108), U32(0x00004008), U32(0x02004000),
|
||||
U32(0x00004000), U32(0x00000008), U32(0x02000008), U32(0x02000100),
|
||||
U32(0x02000000), U32(0x00004100), U32(0x02004108), U32(0x00000000),
|
||||
U32(0x00004108), U32(0x02000000), U32(0x00000100), U32(0x00004008),
|
||||
U32(0x02000108), U32(0x00000100), U32(0x00000000), U32(0x02004108),
|
||||
U32(0x02004008), U32(0x02004100), U32(0x00000108), U32(0x00004000),
|
||||
U32(0x00004100), U32(0x02004008), U32(0x02000100), U32(0x00000108),
|
||||
U32(0x00000008), U32(0x00004108), U32(0x02004000), U32(0x02000008),
|
||||
],
|
||||
|
||||
# nibble 5
|
||||
[
|
||||
U32(0x20000010), U32(0x00080010), U32(0x00000000), U32(0x20080800),
|
||||
U32(0x00080010), U32(0x00000800), U32(0x20000810), U32(0x00080000),
|
||||
U32(0x00000810), U32(0x20080810), U32(0x00080800), U32(0x20000000),
|
||||
U32(0x20000800), U32(0x20000010), U32(0x20080000), U32(0x00080810),
|
||||
U32(0x00080000), U32(0x20000810), U32(0x20080010), U32(0x00000000),
|
||||
U32(0x00000800), U32(0x00000010), U32(0x20080800), U32(0x20080010),
|
||||
U32(0x20080810), U32(0x20080000), U32(0x20000000), U32(0x00000810),
|
||||
U32(0x00000010), U32(0x00080800), U32(0x00080810), U32(0x20000800),
|
||||
U32(0x00000810), U32(0x20000000), U32(0x20000800), U32(0x00080810),
|
||||
U32(0x20080800), U32(0x00080010), U32(0x00000000), U32(0x20000800),
|
||||
U32(0x20000000), U32(0x00000800), U32(0x20080010), U32(0x00080000),
|
||||
U32(0x00080010), U32(0x20080810), U32(0x00080800), U32(0x00000010),
|
||||
U32(0x20080810), U32(0x00080800), U32(0x00080000), U32(0x20000810),
|
||||
U32(0x20000010), U32(0x20080000), U32(0x00080810), U32(0x00000000),
|
||||
U32(0x00000800), U32(0x20000010), U32(0x20000810), U32(0x20080800),
|
||||
U32(0x20080000), U32(0x00000810), U32(0x00000010), U32(0x20080010),
|
||||
],
|
||||
|
||||
# nibble 6
|
||||
[
|
||||
U32(0x00001000), U32(0x00000080), U32(0x00400080), U32(0x00400001),
|
||||
U32(0x00401081), U32(0x00001001), U32(0x00001080), U32(0x00000000),
|
||||
U32(0x00400000), U32(0x00400081), U32(0x00000081), U32(0x00401000),
|
||||
U32(0x00000001), U32(0x00401080), U32(0x00401000), U32(0x00000081),
|
||||
U32(0x00400081), U32(0x00001000), U32(0x00001001), U32(0x00401081),
|
||||
U32(0x00000000), U32(0x00400080), U32(0x00400001), U32(0x00001080),
|
||||
U32(0x00401001), U32(0x00001081), U32(0x00401080), U32(0x00000001),
|
||||
U32(0x00001081), U32(0x00401001), U32(0x00000080), U32(0x00400000),
|
||||
U32(0x00001081), U32(0x00401000), U32(0x00401001), U32(0x00000081),
|
||||
U32(0x00001000), U32(0x00000080), U32(0x00400000), U32(0x00401001),
|
||||
U32(0x00400081), U32(0x00001081), U32(0x00001080), U32(0x00000000),
|
||||
U32(0x00000080), U32(0x00400001), U32(0x00000001), U32(0x00400080),
|
||||
U32(0x00000000), U32(0x00400081), U32(0x00400080), U32(0x00001080),
|
||||
U32(0x00000081), U32(0x00001000), U32(0x00401081), U32(0x00400000),
|
||||
U32(0x00401080), U32(0x00000001), U32(0x00001001), U32(0x00401081),
|
||||
U32(0x00400001), U32(0x00401080), U32(0x00401000), U32(0x00001001),
|
||||
],
|
||||
|
||||
# nibble 7
|
||||
[
|
||||
U32(0x08200020), U32(0x08208000), U32(0x00008020), U32(0x00000000),
|
||||
U32(0x08008000), U32(0x00200020), U32(0x08200000), U32(0x08208020),
|
||||
U32(0x00000020), U32(0x08000000), U32(0x00208000), U32(0x00008020),
|
||||
U32(0x00208020), U32(0x08008020), U32(0x08000020), U32(0x08200000),
|
||||
U32(0x00008000), U32(0x00208020), U32(0x00200020), U32(0x08008000),
|
||||
U32(0x08208020), U32(0x08000020), U32(0x00000000), U32(0x00208000),
|
||||
U32(0x08000000), U32(0x00200000), U32(0x08008020), U32(0x08200020),
|
||||
U32(0x00200000), U32(0x00008000), U32(0x08208000), U32(0x00000020),
|
||||
U32(0x00200000), U32(0x00008000), U32(0x08000020), U32(0x08208020),
|
||||
U32(0x00008020), U32(0x08000000), U32(0x00000000), U32(0x00208000),
|
||||
U32(0x08200020), U32(0x08008020), U32(0x08008000), U32(0x00200020),
|
||||
U32(0x08208000), U32(0x00000020), U32(0x00200020), U32(0x08008000),
|
||||
U32(0x08208020), U32(0x00200000), U32(0x08200000), U32(0x08000020),
|
||||
U32(0x00208000), U32(0x00008020), U32(0x08008020), U32(0x08200000),
|
||||
U32(0x00000020), U32(0x08208000), U32(0x00208020), U32(0x00000000),
|
||||
U32(0x08000000), U32(0x08200020), U32(0x00008000), U32(0x00208020),
|
||||
],
|
||||
]
|
||||
|
||||
# static unsigned long des_skb[8][64]={
|
||||
|
||||
des_skb = \
|
||||
[
|
||||
# for C bits (numbered as per FIPS 46) 1 2 3 4 5 6
|
||||
[
|
||||
U32(0x00000000), U32(0x00000010), U32(0x20000000), U32(0x20000010),
|
||||
U32(0x00010000), U32(0x00010010), U32(0x20010000), U32(0x20010010),
|
||||
U32(0x00000800), U32(0x00000810), U32(0x20000800), U32(0x20000810),
|
||||
U32(0x00010800), U32(0x00010810), U32(0x20010800), U32(0x20010810),
|
||||
U32(0x00000020), U32(0x00000030), U32(0x20000020), U32(0x20000030),
|
||||
U32(0x00010020), U32(0x00010030), U32(0x20010020), U32(0x20010030),
|
||||
U32(0x00000820), U32(0x00000830), U32(0x20000820), U32(0x20000830),
|
||||
U32(0x00010820), U32(0x00010830), U32(0x20010820), U32(0x20010830),
|
||||
U32(0x00080000), U32(0x00080010), U32(0x20080000), U32(0x20080010),
|
||||
U32(0x00090000), U32(0x00090010), U32(0x20090000), U32(0x20090010),
|
||||
U32(0x00080800), U32(0x00080810), U32(0x20080800), U32(0x20080810),
|
||||
U32(0x00090800), U32(0x00090810), U32(0x20090800), U32(0x20090810),
|
||||
U32(0x00080020), U32(0x00080030), U32(0x20080020), U32(0x20080030),
|
||||
U32(0x00090020), U32(0x00090030), U32(0x20090020), U32(0x20090030),
|
||||
U32(0x00080820), U32(0x00080830), U32(0x20080820), U32(0x20080830),
|
||||
U32(0x00090820), U32(0x00090830), U32(0x20090820), U32(0x20090830),
|
||||
],
|
||||
|
||||
# for C bits (numbered as per FIPS 46) 7 8 10 11 12 13
|
||||
[
|
||||
U32(0x00000000), U32(0x02000000), U32(0x00002000), U32(0x02002000),
|
||||
U32(0x00200000), U32(0x02200000), U32(0x00202000), U32(0x02202000),
|
||||
U32(0x00000004), U32(0x02000004), U32(0x00002004), U32(0x02002004),
|
||||
U32(0x00200004), U32(0x02200004), U32(0x00202004), U32(0x02202004),
|
||||
U32(0x00000400), U32(0x02000400), U32(0x00002400), U32(0x02002400),
|
||||
U32(0x00200400), U32(0x02200400), U32(0x00202400), U32(0x02202400),
|
||||
U32(0x00000404), U32(0x02000404), U32(0x00002404), U32(0x02002404),
|
||||
U32(0x00200404), U32(0x02200404), U32(0x00202404), U32(0x02202404),
|
||||
U32(0x10000000), U32(0x12000000), U32(0x10002000), U32(0x12002000),
|
||||
U32(0x10200000), U32(0x12200000), U32(0x10202000), U32(0x12202000),
|
||||
U32(0x10000004), U32(0x12000004), U32(0x10002004), U32(0x12002004),
|
||||
U32(0x10200004), U32(0x12200004), U32(0x10202004), U32(0x12202004),
|
||||
U32(0x10000400), U32(0x12000400), U32(0x10002400), U32(0x12002400),
|
||||
U32(0x10200400), U32(0x12200400), U32(0x10202400), U32(0x12202400),
|
||||
U32(0x10000404), U32(0x12000404), U32(0x10002404), U32(0x12002404),
|
||||
U32(0x10200404), U32(0x12200404), U32(0x10202404), U32(0x12202404),
|
||||
],
|
||||
|
||||
# for C bits (numbered as per FIPS 46) 14 15 16 17 19 20
|
||||
[
|
||||
U32(0x00000000), U32(0x00000001), U32(0x00040000), U32(0x00040001),
|
||||
U32(0x01000000), U32(0x01000001), U32(0x01040000), U32(0x01040001),
|
||||
U32(0x00000002), U32(0x00000003), U32(0x00040002), U32(0x00040003),
|
||||
U32(0x01000002), U32(0x01000003), U32(0x01040002), U32(0x01040003),
|
||||
U32(0x00000200), U32(0x00000201), U32(0x00040200), U32(0x00040201),
|
||||
U32(0x01000200), U32(0x01000201), U32(0x01040200), U32(0x01040201),
|
||||
U32(0x00000202), U32(0x00000203), U32(0x00040202), U32(0x00040203),
|
||||
U32(0x01000202), U32(0x01000203), U32(0x01040202), U32(0x01040203),
|
||||
U32(0x08000000), U32(0x08000001), U32(0x08040000), U32(0x08040001),
|
||||
U32(0x09000000), U32(0x09000001), U32(0x09040000), U32(0x09040001),
|
||||
U32(0x08000002), U32(0x08000003), U32(0x08040002), U32(0x08040003),
|
||||
U32(0x09000002), U32(0x09000003), U32(0x09040002), U32(0x09040003),
|
||||
U32(0x08000200), U32(0x08000201), U32(0x08040200), U32(0x08040201),
|
||||
U32(0x09000200), U32(0x09000201), U32(0x09040200), U32(0x09040201),
|
||||
U32(0x08000202), U32(0x08000203), U32(0x08040202), U32(0x08040203),
|
||||
U32(0x09000202), U32(0x09000203), U32(0x09040202), U32(0x09040203),
|
||||
],
|
||||
|
||||
# for C bits (numbered as per FIPS 46) 21 23 24 26 27 28
|
||||
[
|
||||
U32(0x00000000), U32(0x00100000), U32(0x00000100), U32(0x00100100),
|
||||
U32(0x00000008), U32(0x00100008), U32(0x00000108), U32(0x00100108),
|
||||
U32(0x00001000), U32(0x00101000), U32(0x00001100), U32(0x00101100),
|
||||
U32(0x00001008), U32(0x00101008), U32(0x00001108), U32(0x00101108),
|
||||
U32(0x04000000), U32(0x04100000), U32(0x04000100), U32(0x04100100),
|
||||
U32(0x04000008), U32(0x04100008), U32(0x04000108), U32(0x04100108),
|
||||
U32(0x04001000), U32(0x04101000), U32(0x04001100), U32(0x04101100),
|
||||
U32(0x04001008), U32(0x04101008), U32(0x04001108), U32(0x04101108),
|
||||
U32(0x00020000), U32(0x00120000), U32(0x00020100), U32(0x00120100),
|
||||
U32(0x00020008), U32(0x00120008), U32(0x00020108), U32(0x00120108),
|
||||
U32(0x00021000), U32(0x00121000), U32(0x00021100), U32(0x00121100),
|
||||
U32(0x00021008), U32(0x00121008), U32(0x00021108), U32(0x00121108),
|
||||
U32(0x04020000), U32(0x04120000), U32(0x04020100), U32(0x04120100),
|
||||
U32(0x04020008), U32(0x04120008), U32(0x04020108), U32(0x04120108),
|
||||
U32(0x04021000), U32(0x04121000), U32(0x04021100), U32(0x04121100),
|
||||
U32(0x04021008), U32(0x04121008), U32(0x04021108), U32(0x04121108),
|
||||
],
|
||||
|
||||
# for D bits (numbered as per FIPS 46) 1 2 3 4 5 6
|
||||
[
|
||||
U32(0x00000000), U32(0x10000000), U32(0x00010000), U32(0x10010000),
|
||||
U32(0x00000004), U32(0x10000004), U32(0x00010004), U32(0x10010004),
|
||||
U32(0x20000000), U32(0x30000000), U32(0x20010000), U32(0x30010000),
|
||||
U32(0x20000004), U32(0x30000004), U32(0x20010004), U32(0x30010004),
|
||||
U32(0x00100000), U32(0x10100000), U32(0x00110000), U32(0x10110000),
|
||||
U32(0x00100004), U32(0x10100004), U32(0x00110004), U32(0x10110004),
|
||||
U32(0x20100000), U32(0x30100000), U32(0x20110000), U32(0x30110000),
|
||||
U32(0x20100004), U32(0x30100004), U32(0x20110004), U32(0x30110004),
|
||||
U32(0x00001000), U32(0x10001000), U32(0x00011000), U32(0x10011000),
|
||||
U32(0x00001004), U32(0x10001004), U32(0x00011004), U32(0x10011004),
|
||||
U32(0x20001000), U32(0x30001000), U32(0x20011000), U32(0x30011000),
|
||||
U32(0x20001004), U32(0x30001004), U32(0x20011004), U32(0x30011004),
|
||||
U32(0x00101000), U32(0x10101000), U32(0x00111000), U32(0x10111000),
|
||||
U32(0x00101004), U32(0x10101004), U32(0x00111004), U32(0x10111004),
|
||||
U32(0x20101000), U32(0x30101000), U32(0x20111000), U32(0x30111000),
|
||||
U32(0x20101004), U32(0x30101004), U32(0x20111004), U32(0x30111004),
|
||||
],
|
||||
|
||||
# for D bits (numbered as per FIPS 46) 8 9 11 12 13 14
|
||||
[
|
||||
U32(0x00000000), U32(0x08000000), U32(0x00000008), U32(0x08000008),
|
||||
U32(0x00000400), U32(0x08000400), U32(0x00000408), U32(0x08000408),
|
||||
U32(0x00020000), U32(0x08020000), U32(0x00020008), U32(0x08020008),
|
||||
U32(0x00020400), U32(0x08020400), U32(0x00020408), U32(0x08020408),
|
||||
U32(0x00000001), U32(0x08000001), U32(0x00000009), U32(0x08000009),
|
||||
U32(0x00000401), U32(0x08000401), U32(0x00000409), U32(0x08000409),
|
||||
U32(0x00020001), U32(0x08020001), U32(0x00020009), U32(0x08020009),
|
||||
U32(0x00020401), U32(0x08020401), U32(0x00020409), U32(0x08020409),
|
||||
U32(0x02000000), U32(0x0A000000), U32(0x02000008), U32(0x0A000008),
|
||||
U32(0x02000400), U32(0x0A000400), U32(0x02000408), U32(0x0A000408),
|
||||
U32(0x02020000), U32(0x0A020000), U32(0x02020008), U32(0x0A020008),
|
||||
U32(0x02020400), U32(0x0A020400), U32(0x02020408), U32(0x0A020408),
|
||||
U32(0x02000001), U32(0x0A000001), U32(0x02000009), U32(0x0A000009),
|
||||
U32(0x02000401), U32(0x0A000401), U32(0x02000409), U32(0x0A000409),
|
||||
U32(0x02020001), U32(0x0A020001), U32(0x02020009), U32(0x0A020009),
|
||||
U32(0x02020401), U32(0x0A020401), U32(0x02020409), U32(0x0A020409),
|
||||
],
|
||||
|
||||
# for D bits (numbered as per FIPS 46) 16 17 18 19 20 21
|
||||
[
|
||||
U32(0x00000000), U32(0x00000100), U32(0x00080000), U32(0x00080100),
|
||||
U32(0x01000000), U32(0x01000100), U32(0x01080000), U32(0x01080100),
|
||||
U32(0x00000010), U32(0x00000110), U32(0x00080010), U32(0x00080110),
|
||||
U32(0x01000010), U32(0x01000110), U32(0x01080010), U32(0x01080110),
|
||||
U32(0x00200000), U32(0x00200100), U32(0x00280000), U32(0x00280100),
|
||||
U32(0x01200000), U32(0x01200100), U32(0x01280000), U32(0x01280100),
|
||||
U32(0x00200010), U32(0x00200110), U32(0x00280010), U32(0x00280110),
|
||||
U32(0x01200010), U32(0x01200110), U32(0x01280010), U32(0x01280110),
|
||||
U32(0x00000200), U32(0x00000300), U32(0x00080200), U32(0x00080300),
|
||||
U32(0x01000200), U32(0x01000300), U32(0x01080200), U32(0x01080300),
|
||||
U32(0x00000210), U32(0x00000310), U32(0x00080210), U32(0x00080310),
|
||||
U32(0x01000210), U32(0x01000310), U32(0x01080210), U32(0x01080310),
|
||||
U32(0x00200200), U32(0x00200300), U32(0x00280200), U32(0x00280300),
|
||||
U32(0x01200200), U32(0x01200300), U32(0x01280200), U32(0x01280300),
|
||||
U32(0x00200210), U32(0x00200310), U32(0x00280210), U32(0x00280310),
|
||||
U32(0x01200210), U32(0x01200310), U32(0x01280210), U32(0x01280310),
|
||||
],
|
||||
|
||||
# for D bits (numbered as per FIPS 46) 22 23 24 25 27 28
|
||||
[
|
||||
U32(0x00000000), U32(0x04000000), U32(0x00040000), U32(0x04040000),
|
||||
U32(0x00000002), U32(0x04000002), U32(0x00040002), U32(0x04040002),
|
||||
U32(0x00002000), U32(0x04002000), U32(0x00042000), U32(0x04042000),
|
||||
U32(0x00002002), U32(0x04002002), U32(0x00042002), U32(0x04042002),
|
||||
U32(0x00000020), U32(0x04000020), U32(0x00040020), U32(0x04040020),
|
||||
U32(0x00000022), U32(0x04000022), U32(0x00040022), U32(0x04040022),
|
||||
U32(0x00002020), U32(0x04002020), U32(0x00042020), U32(0x04042020),
|
||||
U32(0x00002022), U32(0x04002022), U32(0x00042022), U32(0x04042022),
|
||||
U32(0x00000800), U32(0x04000800), U32(0x00040800), U32(0x04040800),
|
||||
U32(0x00000802), U32(0x04000802), U32(0x00040802), U32(0x04040802),
|
||||
U32(0x00002800), U32(0x04002800), U32(0x00042800), U32(0x04042800),
|
||||
U32(0x00002802), U32(0x04002802), U32(0x00042802), U32(0x04042802),
|
||||
U32(0x00000820), U32(0x04000820), U32(0x00040820), U32(0x04040820),
|
||||
U32(0x00000822), U32(0x04000822), U32(0x00040822), U32(0x04040822),
|
||||
U32(0x00002820), U32(0x04002820), U32(0x00042820), U32(0x04042820),
|
||||
U32(0x00002822), U32(0x04002822), U32(0x00042822), U32(0x04042822),
|
||||
]
|
||||
|
||||
]
|
67
wakatime/packages/ntlm_auth/gss_channel_bindings.py
Normal file
67
wakatime/packages/ntlm_auth/gss_channel_bindings.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
# 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 struct
|
||||
|
||||
"""
|
||||
This is not the easiest structure to understand, ultimately this is a set structure
|
||||
as defined by Microsoft. Channel Binding Tokens set the SHA256 hash of the server
|
||||
certificate to the application_data field and then ultimately creates the MD5 hash
|
||||
to include in the NTLM auth from there. This class is just designed to create the
|
||||
bindings structure which is then used by compute_response.py to do the rest of the
|
||||
work.
|
||||
|
||||
For more infor on how this works and how it is derived, this is a great link;
|
||||
https://blogs.msdn.microsoft.com/openspecification/2013/03/26/ntlm-and-channel-binding-hash-aka-extended-protection-for-authentication/
|
||||
"""
|
||||
class GssChannelBindingsStruct(object):
|
||||
INITIATOR_ADDTYPE = 'initiator_addtype'
|
||||
INITIATOR_ADDRESS_LENGTH = 'initiator_address_length'
|
||||
ACCEPTOR_ADDRTYPE = 'acceptor_addrtype'
|
||||
ACCEPTOR_ADDRESS_LENGTH = 'acceptor_address_length'
|
||||
APPLICATION_DATA_LENGTH = 'application_data_length'
|
||||
INITIATOR_ADDRESS = 'initiator_address'
|
||||
ACCEPTOR_ADDRESS = 'acceptor_address'
|
||||
APPLICATION_DATA = 'application_data'
|
||||
|
||||
def __init__(self):
|
||||
self.fields = {}
|
||||
self.fields[self.INITIATOR_ADDTYPE] = 0
|
||||
self.fields[self.INITIATOR_ADDRESS_LENGTH] = 0
|
||||
self.fields[self.ACCEPTOR_ADDRTYPE] = 0
|
||||
self.fields[self.ACCEPTOR_ADDRESS_LENGTH] = 0
|
||||
self.fields[self.APPLICATION_DATA_LENGTH] = 0
|
||||
self.fields[self.INITIATOR_ADDRESS] = b''
|
||||
self.fields[self.ACCEPTOR_ADDRESS] = b''
|
||||
self.fields[self.APPLICATION_DATA] = b''
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.fields[key] = value
|
||||
|
||||
def get_data(self):
|
||||
# Set the lengths of each len field in case they have changed
|
||||
self.fields[self.INITIATOR_ADDRESS_LENGTH] = len(self.fields[self.INITIATOR_ADDRESS])
|
||||
self.fields[self.ACCEPTOR_ADDRESS_LENGTH] = len(self.fields[self.ACCEPTOR_ADDRESS])
|
||||
self.fields[self.APPLICATION_DATA_LENGTH] = len(self.fields[self.APPLICATION_DATA])
|
||||
|
||||
# Add all the values together to create the gss_channel_bindings_struct
|
||||
data = struct.pack('<L', self.fields[self.INITIATOR_ADDTYPE]) + \
|
||||
struct.pack('<L', self.fields[self.INITIATOR_ADDRESS_LENGTH]) + \
|
||||
self.fields[self.INITIATOR_ADDRESS] + \
|
||||
struct.pack('<L', self.fields[self.ACCEPTOR_ADDRTYPE]) + \
|
||||
struct.pack('<L', self.fields[self.ACCEPTOR_ADDRESS_LENGTH]) + \
|
||||
self.fields[self.ACCEPTOR_ADDRESS] + \
|
||||
struct.pack('<L', self.fields[self.APPLICATION_DATA_LENGTH]) + \
|
||||
self.fields[self.APPLICATION_DATA]
|
||||
|
||||
return data
|
359
wakatime/packages/ntlm_auth/messages.py
Normal file
359
wakatime/packages/ntlm_auth/messages.py
Normal file
|
@ -0,0 +1,359 @@
|
|||
# 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 hmac
|
||||
import os
|
||||
import struct
|
||||
from ntlm_auth.compute_response import ComputeResponse
|
||||
from ntlm_auth.constants import NegotiateFlags, MessageTypes, NTLM_SIGNATURE, AvFlags
|
||||
from ntlm_auth.rc4 import ARC4
|
||||
from ntlm_auth.target_info import TargetInfo
|
||||
|
||||
class NegotiateMessage(object):
|
||||
EXPECTED_BODY_LENGTH = 40
|
||||
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2.1.1 NEGOTIATE_MESSAGE
|
||||
The NEGOTIATE_MESSAGE defines an NTLM Negotiate message that is sent from the client to
|
||||
the server. This message allows the client to specify its supported NTLM options to
|
||||
the server.
|
||||
|
||||
:param negotiate_flags: A NEGOTIATE structure that contains a set of bit flags. These flags are the options the client supports
|
||||
:param domain_name: The domain name of the user to authenticate with, default is None
|
||||
:param workstation: The worksation of the client machine, default is None
|
||||
|
||||
Attributes:
|
||||
signature: An 8-byte character array that MUST contain the ASCII string 'NTLMSSP\0'
|
||||
message_type: A 32-bit unsigned integer that indicates the message type. This field must be set to 0x00000001
|
||||
negotiate_flags: A NEGOTIATE structure that contains a set of bit flags. These flags are the options the client supports
|
||||
version: Contains the windows version info of the client. It is used only debugging purposes and are only set when NTLMSSP_NEGOTIATE_VERSION flag is set
|
||||
domain_name: A byte-array that contains the name of the client authentication domain that MUST Be encoded in the negotiated character set
|
||||
workstation: A byte-array that contains the name of the client machine that MUST Be encoded in the negotiated character set
|
||||
"""
|
||||
def __init__(self, negotiate_flags, domain_name, workstation):
|
||||
self.signature = NTLM_SIGNATURE
|
||||
self.message_type = struct.pack('<L', MessageTypes.NTLM_NEGOTIATE)
|
||||
|
||||
# Check if the domain_name value is set, if it is, make sure the negotiate_flag is also set
|
||||
if domain_name is None:
|
||||
self.domain_name = ''
|
||||
else:
|
||||
self.domain_name = domain_name
|
||||
negotiate_flags |= NegotiateFlags.NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED
|
||||
|
||||
# Check if the workstation value is set, if it is, make sure the negotiate_flag is also set
|
||||
if workstation is None:
|
||||
self.workstation = ''
|
||||
else:
|
||||
self.workstation = workstation
|
||||
negotiate_flags |= NegotiateFlags.NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED
|
||||
|
||||
# Set the encoding flag to use OEM, remove UNICODE if set as it isn't support in this message
|
||||
negotiate_flags -= NegotiateFlags.NTLMSSP_NEGOTIATE_UNICODE
|
||||
negotiate_flags |= NegotiateFlags.NTLMSSP_NEGOTIATE_OEM
|
||||
self.domain_name = self.domain_name.encode('ascii')
|
||||
self.workstation = self.workstation.encode('ascii')
|
||||
|
||||
self.version = get_version(negotiate_flags)
|
||||
|
||||
self.negotiate_flags = struct.pack('<I', negotiate_flags)
|
||||
|
||||
def get_data(self):
|
||||
payload_offset = self.EXPECTED_BODY_LENGTH
|
||||
|
||||
# DomainNameFields - 8 bytes
|
||||
domain_name_len = struct.pack('<H', len(self.domain_name))
|
||||
domain_name_max_len = struct.pack('<H', len(self.domain_name))
|
||||
domain_name_buffer_offset = struct.pack('<I', payload_offset)
|
||||
payload_offset += len(self.domain_name)
|
||||
|
||||
# WorkstationFields - 8 bytes
|
||||
workstation_len = struct.pack('<H', len(self.workstation))
|
||||
workstation_max_len = struct.pack('<H', len(self.workstation))
|
||||
workstation_buffer_offset = struct.pack('<I', payload_offset)
|
||||
payload_offset += len(self.workstation)
|
||||
|
||||
# Payload - variable length
|
||||
payload = self.domain_name
|
||||
payload += self.workstation
|
||||
|
||||
# Bring the header values together into 1 message
|
||||
msg1 = self.signature
|
||||
msg1 += self.message_type
|
||||
msg1 += self.negotiate_flags
|
||||
msg1 += domain_name_len
|
||||
msg1 += domain_name_max_len
|
||||
msg1 += domain_name_buffer_offset
|
||||
msg1 += workstation_len
|
||||
msg1 += workstation_max_len
|
||||
msg1 += workstation_buffer_offset
|
||||
msg1 += self.version
|
||||
|
||||
assert self.EXPECTED_BODY_LENGTH == len(msg1), "BODY_LENGTH: %d != msg1: %d" % (self.EXPECTED_BODY_LENGTH, len(msg1))
|
||||
|
||||
# Adding the payload data to the message
|
||||
msg1 += payload
|
||||
return msg1
|
||||
|
||||
class ChallengeMessage(object):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2.1.2 CHALLENGE_MESSAGE
|
||||
The CHALLENGE_MESSAGE defines an NTLM challenge message that is sent from the server to
|
||||
the client. The CHALLENGE_MESSAGE is used by the server to challenge the client to prove
|
||||
its identity, For connection-oriented requests, the CHALLENGE_MESSAGE generated by the
|
||||
server is in response to the NEGOTIATE_MESSAGE from the client.
|
||||
|
||||
:param msg2: The CHALLENGE_MESSAGE received from the server after sending our NEGOTIATE_MESSAGE. This has
|
||||
been decoded from a base64 string
|
||||
|
||||
Attributes
|
||||
signature: An 8-byte character array that MUST contain the ASCII string 'NTLMSSP\0'
|
||||
message_type: A 32-bit unsigned integer that indicates the message type. This field must be set to 0x00000002
|
||||
negotiate_flags: A NEGOTIATE strucutre that contains a set of bit flags. The server sets flags to indicate options it supports
|
||||
server_challenge: A 64-bit value that contains the NTLM challenge. The challenge is a 64-bit nonce. Used in the AuthenticateMessage message
|
||||
reserved: An 8-byte array whose elements MUST be zero when sent and MUST be ignored on receipt
|
||||
version: When NTLMSSP_NEGOTIATE_VERSION flag is set in negotiate_flags field which contains the windows version info. Used only for debugging purposes
|
||||
target_name: When NTLMSSP_REQUEST_TARGET is set is a byte array that contains the name of the server authentication realm. In a domain environment this is the domain name not server name
|
||||
target_info: When NTLMSSP_NEGOTIATE_TARGET_INFO is set is a byte array that contains a sequence of AV_PAIR structures (target_info.py)
|
||||
"""
|
||||
def __init__(self, msg2):
|
||||
self.data = msg2
|
||||
# Setting the object values from the raw_challenge_message
|
||||
self.signature = msg2[0:8]
|
||||
self.message_type = struct.unpack("<I", msg2[8:12])[0]
|
||||
self.negotiate_flags = struct.unpack("<I", msg2[20:24])[0]
|
||||
self.server_challenge = msg2[24:32]
|
||||
self.reserved = msg2[32:40]
|
||||
|
||||
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_VERSION and self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||
size = len(msg2)
|
||||
self.version = struct.unpack("<q", msg2[48:56])[0]
|
||||
else:
|
||||
self.version = None
|
||||
|
||||
if self.negotiate_flags & NegotiateFlags.NTLMSSP_REQUEST_TARGET:
|
||||
target_name_len = struct.unpack("<H", msg2[12:14])[0]
|
||||
target_name_max_len = struct.unpack("<H", msg2[14:16])[0]
|
||||
target_name_buffer_offset = struct.unpack("<I", msg2[16:20])[0]
|
||||
self.target_name = msg2[target_name_buffer_offset:target_name_buffer_offset + target_name_len]
|
||||
else:
|
||||
self.target_name = None
|
||||
|
||||
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_TARGET_INFO:
|
||||
target_info_len = struct.unpack("<H", msg2[40:42])[0]
|
||||
target_info_max_len = struct.unpack("<H", msg2[42:44])[0]
|
||||
target_info_buffer_offset = struct.unpack("<I", msg2[44:48])[0]
|
||||
|
||||
target_info_raw = msg2[target_info_buffer_offset:target_info_buffer_offset + target_info_len]
|
||||
self.target_info = TargetInfo(target_info_raw)
|
||||
else:
|
||||
self.target_info = None
|
||||
|
||||
# Verify initial integrity of the message, it matches what should be there
|
||||
assert self.signature == NTLM_SIGNATURE
|
||||
assert self.message_type == MessageTypes.NTLM_CHALLENGE
|
||||
|
||||
def get_data(self):
|
||||
return self.data
|
||||
|
||||
class AuthenticateMessage(object):
|
||||
EXPECTED_BODY_LENGTH = 72
|
||||
EXPECTED_BODY_LENGTH_WITH_MIC = 88
|
||||
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2.1.3 AUTHENTICATE_MESSAGE
|
||||
The AUTHENTICATE_MESSAGE defines an NTLM authenticate message that is sent from the
|
||||
client to the server after the CHALLENGE_MESSAGE is processed by the client.
|
||||
|
||||
: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 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
|
||||
: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.
|
||||
|
||||
Message Attributes (Attributes not used to compute the message structure):
|
||||
signature: An 8-byte character array that MUST contain the ASCII string 'NTLMSSP\0'
|
||||
message_type: A 32-bit unsigned integer that indicates the message type. This field must be set to 0x00000003
|
||||
negotiate_flags: A NEGOTIATE strucutre that contains a set of bit flags. These flags are the choices the client has made from the CHALLENGE_MESSAGE options
|
||||
version: Contains the windows version info of the client. It is used only debugging purposes and are only set when NTLMSSP_NEGOTIATE_VERSION flag is set
|
||||
mic: The message integrity for the NEGOTIATE_MESSAGE, CHALLENGE_MESSAGE and AUTHENTICATE_MESSAGE
|
||||
lm_challenge_response: An LM_RESPONSE of LMv2_RESPONSE structure that contains the computed LM response to the challenge
|
||||
nt_challenge_response: An NTLM_RESPONSE or NTLMv2_RESPONSE structure that contains the computed NT response to the challenge
|
||||
domain_name: The domain or computer name hosting the user account, MUST be encoded in the negotiated character set
|
||||
user_name: The name of the user to be authenticated, MUST be encoded in the negotiated character set
|
||||
workstation: The name of the computer to which the user is logged on, MUST Be encoded in the negotiated character set
|
||||
encrypted_random_session_key: The client's encrypted random session key
|
||||
|
||||
Non-Message Attributes (Attributes not used to compute the message structure):
|
||||
exported_session_key: A randomly generated session key based on other keys, used to derive the SIGNKEY and SEALKEY
|
||||
target_info: The AV_PAIR structure used in the nt response calculation
|
||||
"""
|
||||
def __init__(self, user_name, password, domain_name, workstation, challenge_message, ntlm_compatibility, server_certificate_hash):
|
||||
self.signature = NTLM_SIGNATURE
|
||||
self.message_type = struct.pack('<L', MessageTypes.NTLM_AUTHENTICATE)
|
||||
self.negotiate_flags = challenge_message.negotiate_flags
|
||||
self.version = get_version(self.negotiate_flags)
|
||||
self.mic = None
|
||||
|
||||
if domain_name is None:
|
||||
self.domain_name = ''
|
||||
else:
|
||||
self.domain_name = domain_name
|
||||
|
||||
if workstation is None:
|
||||
self.workstation = ''
|
||||
else:
|
||||
self.workstation = workstation
|
||||
|
||||
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_UNICODE:
|
||||
self.negotiate_flags -= NegotiateFlags.NTLMSSP_NEGOTIATE_OEM
|
||||
encoding_value = 'utf-16-le'
|
||||
else:
|
||||
encoding_value = 'ascii'
|
||||
|
||||
self.domain_name = self.domain_name.encode(encoding_value)
|
||||
self.user_name = user_name.encode(encoding_value)
|
||||
self.workstation = self.workstation.encode(encoding_value)
|
||||
|
||||
compute_response = ComputeResponse(user_name, password, domain_name, challenge_message,
|
||||
ntlm_compatibility)
|
||||
|
||||
self.lm_challenge_response = 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.target_info = target_info
|
||||
|
||||
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_KEY_EXCH:
|
||||
self.exported_session_key = get_random_export_session_key()
|
||||
|
||||
rc4_handle = ARC4(key_exchange_key)
|
||||
self.encrypted_random_session_key = rc4_handle.update(self.exported_session_key)
|
||||
else:
|
||||
self.exported_session_key = key_exchange_key
|
||||
self.encrypted_random_session_key = b''
|
||||
|
||||
self.negotiate_flags = struct.pack('<I', self.negotiate_flags)
|
||||
|
||||
def get_data(self):
|
||||
if self.mic is None:
|
||||
mic = b''
|
||||
expected_body_length = self.EXPECTED_BODY_LENGTH
|
||||
else:
|
||||
mic = self.mic
|
||||
expected_body_length = self.EXPECTED_BODY_LENGTH_WITH_MIC
|
||||
|
||||
payload_offset = expected_body_length
|
||||
|
||||
# DomainNameFields - 8 bytes
|
||||
domain_name_len = struct.pack('<H', len(self.domain_name))
|
||||
domain_name_max_len = struct.pack('<H', len(self.domain_name))
|
||||
domain_name_buffer_offset = struct.pack('<I', payload_offset)
|
||||
payload_offset += len(self.domain_name)
|
||||
|
||||
# UserNameFields - 8 bytes
|
||||
user_name_len = struct.pack('<H', len(self.user_name))
|
||||
user_name_max_len = struct.pack('<H', len(self.user_name))
|
||||
user_name_buffer_offset = struct.pack('<I', payload_offset)
|
||||
payload_offset += len(self.user_name)
|
||||
|
||||
# WorkstatonFields - 8 bytes
|
||||
workstation_len = struct.pack('<H', len(self.workstation))
|
||||
workstation_max_len = struct.pack('<H', len(self.workstation))
|
||||
workstation_buffer_offset = struct.pack('<I', payload_offset)
|
||||
payload_offset += len(self.workstation)
|
||||
|
||||
# LmChallengeResponseFields - 8 bytes
|
||||
lm_challenge_response_len = struct.pack('<H', len(self.lm_challenge_response))
|
||||
lm_challenge_response_max_len = struct.pack('<H', len(self.lm_challenge_response))
|
||||
lm_challenge_response_buffer_offset = struct.pack('<I', payload_offset)
|
||||
payload_offset += len(self.lm_challenge_response)
|
||||
|
||||
# NtChallengeResponseFields - 8 bytes
|
||||
nt_challenge_response_len = struct.pack('<H', len(self.nt_challenge_response))
|
||||
nt_challenge_response_max_len = struct.pack('<H', len(self.nt_challenge_response))
|
||||
nt_challenge_response_buffer_offset = struct.pack('<I', payload_offset)
|
||||
payload_offset += len(self.nt_challenge_response)
|
||||
|
||||
# EncryptedRandomSessionKeyFields - 8 bytes
|
||||
encrypted_random_session_key_len = struct.pack('<H', len(self.encrypted_random_session_key))
|
||||
encrypted_random_session_key_max_len = struct.pack('<H', len(self.encrypted_random_session_key))
|
||||
encrypted_random_session_key_buffer_offset = struct.pack('<I', payload_offset)
|
||||
payload_offset += len(self.encrypted_random_session_key)
|
||||
|
||||
# Payload - variable length
|
||||
payload = self.domain_name
|
||||
payload += self.user_name
|
||||
payload += self.workstation
|
||||
payload += self.lm_challenge_response
|
||||
payload += self.nt_challenge_response
|
||||
payload += self.encrypted_random_session_key
|
||||
|
||||
msg3 = self.signature
|
||||
msg3 += self.message_type
|
||||
msg3 += lm_challenge_response_len + lm_challenge_response_max_len + lm_challenge_response_buffer_offset
|
||||
msg3 += nt_challenge_response_len + nt_challenge_response_max_len + nt_challenge_response_buffer_offset
|
||||
msg3 += domain_name_len + domain_name_max_len + domain_name_buffer_offset
|
||||
msg3 += user_name_len + user_name_max_len + user_name_buffer_offset
|
||||
msg3 += workstation_len + workstation_max_len + workstation_buffer_offset
|
||||
msg3 += encrypted_random_session_key_len + encrypted_random_session_key_max_len + encrypted_random_session_key_buffer_offset
|
||||
msg3 += self.negotiate_flags
|
||||
msg3 += self.version
|
||||
msg3 += mic
|
||||
|
||||
# Adding the payload data to the message
|
||||
msg3 += payload
|
||||
|
||||
return msg3
|
||||
|
||||
def add_mic(self, negotiate_message, challenge_message):
|
||||
if self.target_info is not None:
|
||||
av_flags = self.target_info[TargetInfo.MSV_AV_FLAGS]
|
||||
|
||||
if av_flags is not None and av_flags[1] == struct.pack("<L", AvFlags.MIC_PROVIDED):
|
||||
self.mic = struct.pack("<IIII", 0, 0, 0, 0)
|
||||
negotiate_data = negotiate_message.get_data()
|
||||
challenge_data = challenge_message.get_data()
|
||||
authenticate_data = self.get_data()
|
||||
|
||||
mic = hmac.new(self.exported_session_key,
|
||||
(negotiate_data + challenge_data + authenticate_data)).digest()
|
||||
self.mic = mic
|
||||
|
||||
def get_version(negotiate_flags):
|
||||
# Check the negotiate_flag version is set, if it is make sure the version info is added to the data
|
||||
if negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_VERSION:
|
||||
# TODO: Get the major and minor version of Windows instead of using default values
|
||||
product_major_version = struct.pack('<B', 6)
|
||||
product_minor_version = struct.pack('<B', 1)
|
||||
product_build = struct.pack('<H', 7601)
|
||||
version_reserved = b'\0' * 3
|
||||
ntlm_revision_current = struct.pack('<B', 15)
|
||||
version = product_major_version + product_minor_version + product_build + version_reserved + ntlm_revision_current
|
||||
else:
|
||||
version = b'\0' * 8
|
||||
|
||||
return version
|
||||
|
||||
def get_random_export_session_key():
|
||||
return os.urandom(16)
|
146
wakatime/packages/ntlm_auth/ntlm.py
Normal file
146
wakatime/packages/ntlm_auth/ntlm.py
Normal file
|
@ -0,0 +1,146 @@
|
|||
# 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 base64
|
||||
import socket
|
||||
import struct
|
||||
from ntlm_auth.constants import NegotiateFlags
|
||||
from ntlm_auth.messages import NegotiateMessage, ChallengeMessage, AuthenticateMessage
|
||||
from ntlm_auth.session_security import SessionSecurity
|
||||
|
||||
|
||||
"""
|
||||
utility functions for Microsoft NTLM authentication
|
||||
|
||||
References:
|
||||
[MS-NLMP]: NT LAN Manager (NTLM) Authentication Protocol Specification
|
||||
http://download.microsoft.com/download/a/e/6/ae6e4142-aa58-45c6-8dcf-a657e5900cd3/%5BMS-NLMP%5D.pdf
|
||||
|
||||
[MS-NTHT]: NTLM Over HTTP Protocol Specification
|
||||
http://download.microsoft.com/download/a/e/6/ae6e4142-aa58-45c6-8dcf-a657e5900cd3/%5BMS-NTHT%5D.pdf
|
||||
|
||||
Cntlm Authentication Proxy
|
||||
http://cntlm.awk.cz/
|
||||
|
||||
NTLM Authorization Proxy Server
|
||||
http://sourceforge.net/projects/ntlmaps/
|
||||
|
||||
Optimized Attack for NTLM2 Session Response
|
||||
http://www.blackhat.com/presentations/bh-asia-04/bh-jp-04-pdfs/bh-jp-04-seki.pdf
|
||||
"""
|
||||
|
||||
class Ntlm(object):
|
||||
"""
|
||||
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 ntlm_compatibility: The Lan Manager Compatibility Level to use withe the auth message - Default 3
|
||||
This is set by an Administrator in the registry key
|
||||
'HKLM\SYSTEM\CurrentControlSet\Control\Lsa\LmCompatibilityLevel'
|
||||
The values correspond to the following;
|
||||
0 : LM and NTLMv1
|
||||
1 : LM, NTLMv1 and NTLMv1 with Extended Session Security
|
||||
2 : NTLMv1 and NTLMv1 with Extended Session Security
|
||||
3-5 : NTLMv2 Only
|
||||
Note: Values 3 to 5 are no different as the client supports the same types
|
||||
|
||||
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
|
||||
"""
|
||||
def __init__(self, ntlm_compatibility=3):
|
||||
self.ntlm_compatibility = ntlm_compatibility
|
||||
|
||||
# Setting up our flags so the challenge message returns the target info block if supported
|
||||
self.negotiate_flags = NegotiateFlags.NTLMSSP_NEGOTIATE_TARGET_INFO | \
|
||||
NegotiateFlags.NTLMSSP_NEGOTIATE_128 | \
|
||||
NegotiateFlags.NTLMSSP_NEGOTIATE_56 | \
|
||||
NegotiateFlags.NTLMSSP_NEGOTIATE_UNICODE | \
|
||||
NegotiateFlags.NTLMSSP_NEGOTIATE_VERSION | \
|
||||
NegotiateFlags.NTLMSSP_NEGOTIATE_KEY_EXCH | \
|
||||
NegotiateFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN | \
|
||||
NegotiateFlags.NTLMSSP_NEGOTIATE_SIGN | \
|
||||
NegotiateFlags.NTLMSSP_NEGOTIATE_SEAL
|
||||
|
||||
# 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:
|
||||
self.session_security = SessionSecurity(struct.unpack("<I", self.authenticate_message.negotiate_flags)[0],
|
||||
self.authenticate_message.exported_session_key)
|
||||
|
||||
return base64.b64encode(self.authenticate_message.get_data())
|
||||
|
||||
def _set_ntlm_compatibility_flags(self, ntlm_compatibility):
|
||||
if (ntlm_compatibility >= 0) and (ntlm_compatibility <= 5):
|
||||
if ntlm_compatibility == 0:
|
||||
self.negotiate_flags |= NegotiateFlags.NTLMSSP_NEGOTIATE_NTLM | \
|
||||
NegotiateFlags.NTLMSSP_NEGOTIATE_LM_KEY
|
||||
elif ntlm_compatibility == 1:
|
||||
self.negotiate_flags |= NegotiateFlags.NTLMSSP_NEGOTIATE_NTLM | \
|
||||
NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
|
||||
else:
|
||||
self.negotiate_flags |= NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
|
||||
else:
|
||||
raise Exception("Unknown ntlm_compatibility level - expecting value between 0 and 5")
|
51
wakatime/packages/ntlm_auth/rc4.py
Normal file
51
wakatime/packages/ntlm_auth/rc4.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
# 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>.
|
||||
|
||||
class ARC4(object):
|
||||
state = None
|
||||
i = 0
|
||||
j = 0
|
||||
|
||||
def __init__(self, key):
|
||||
# Split up the key into a list
|
||||
if isinstance(key, str):
|
||||
key = [ord(c) for c in key]
|
||||
else:
|
||||
key = [c for c in key]
|
||||
|
||||
#Key-scheduling algorithm (KSA)
|
||||
self.state = [n for n in range(256)]
|
||||
j = 0
|
||||
for i in range(256):
|
||||
j = (j + self.state[i] + key[i % len(key)]) % 256
|
||||
self.state[i], self.state[j] = self.state[j], self.state[i]
|
||||
|
||||
def update(self, value):
|
||||
chars = []
|
||||
random_gen = self._random_generator()
|
||||
for char in value:
|
||||
if isinstance(value, str):
|
||||
byte = ord(char)
|
||||
else:
|
||||
byte = char
|
||||
updated_byte = byte ^ next(random_gen)
|
||||
chars.append(updated_byte)
|
||||
return bytes(bytearray(chars))
|
||||
|
||||
def _random_generator(self):
|
||||
#Pseudo-Random Generation Algorithm (PRGA)
|
||||
while True:
|
||||
self.i = (self.i + 1) % 256
|
||||
self.j = (self.j + self.state[self.i]) % 256
|
||||
self.state[self.i], self.state[self.j] = self.state[self.j], self.state[self.i]
|
||||
yield self.state[(self.state[self.i] + self.state[self.j]) % 256]
|
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
|
68
wakatime/packages/ntlm_auth/target_info.py
Normal file
68
wakatime/packages/ntlm_auth/target_info.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
"""
|
||||
Original Author: Ian Clegg
|
||||
Project: ntlmlib
|
||||
URL: https://github.com/ianclegg/ntlmlib
|
||||
License: Apache 2.0 License
|
||||
Notes: Most of this code has been copied from the messages.py in the ntlmlib repo.
|
||||
Some minor changes such as the name of the AV Pairs and extra comments have been added.
|
||||
"""
|
||||
|
||||
import struct
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from ordereddict import OrderedDict
|
||||
|
||||
class TargetInfo(object):
|
||||
MSV_AV_EOL = 0x00
|
||||
MSV_AV_NB_COMPUTER_NAME = 0x01
|
||||
MSV_AV_NB_DOMAIN_NAME = 0x02
|
||||
MSV_AV_DNS_COMPUTER_NAME = 0x03
|
||||
MSV_AV_DNS_DOMAIN_NAME = 0x04
|
||||
MSV_AV_DNS_TREE_NAME = 0x05
|
||||
MSV_AV_FLAGS = 0x06
|
||||
MSV_AV_TIMESTAMP = 0x07
|
||||
MSV_AV_SINGLE_HOST = 0x08
|
||||
MSV_AV_TARGET_NAME = 0x09
|
||||
MSV_AV_CHANNEL_BINDINGS = 0x0a
|
||||
|
||||
def __init__(self, data=None):
|
||||
self.fields = OrderedDict()
|
||||
if data is not None:
|
||||
self.from_string(data)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.fields[key] = (len(value), value)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key in self.fields:
|
||||
return self.fields[key]
|
||||
return None
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self.fields[key]
|
||||
|
||||
def from_string(self, data):
|
||||
attribute_type = 0xff
|
||||
while attribute_type is not TargetInfo.MSV_AV_EOL:
|
||||
# Parse the Attribute Value pair from the structure
|
||||
attribute_type = struct.unpack('<H', data[:struct.calcsize('<H')])[0]
|
||||
data = data[struct.calcsize('<H'):]
|
||||
length = struct.unpack('<H', data[:struct.calcsize('<H')])[0]
|
||||
data = data[struct.calcsize('<H'):]
|
||||
# Add a new field to the object for the parse attribute value
|
||||
self.fields[attribute_type] = (length, data[:length])
|
||||
data = data[length:]
|
||||
|
||||
def get_data(self):
|
||||
if TargetInfo.MSV_AV_EOL in self.fields:
|
||||
del self.fields[TargetInfo.MSV_AV_EOL]
|
||||
|
||||
data = b''
|
||||
for i in self.fields.keys():
|
||||
data += struct.pack('<HH', i, self[i][0])
|
||||
data += self[i][1]
|
||||
|
||||
# end with a NTLMSSP_AV_EOL
|
||||
data += struct.pack('<HH', TargetInfo.MSV_AV_EOL, 0)
|
||||
return data
|
3
wakatime/packages/requests_ntlm/__init__.py
Normal file
3
wakatime/packages/requests_ntlm/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from .requests_ntlm import HttpNtlmAuth
|
||||
|
||||
__all__ = ('HttpNtlmAuth',)
|
218
wakatime/packages/requests_ntlm/requests_ntlm.py
Normal file
218
wakatime/packages/requests_ntlm/requests_ntlm.py
Normal file
|
@ -0,0 +1,218 @@
|
|||
import hashlib
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from ntlm_auth import ntlm
|
||||
from requests.auth import AuthBase
|
||||
from requests.packages.urllib3.response import HTTPResponse
|
||||
|
||||
class HttpNtlmAuth(AuthBase):
|
||||
"""
|
||||
HTTP NTLM Authentication Handler for Requests.
|
||||
|
||||
Supports pass-the-hash.
|
||||
"""
|
||||
|
||||
def __init__(self, username, password, session=None):
|
||||
"""Create an authentication handler for NTLM over HTTP.
|
||||
|
||||
:param str username: Username in 'domain\\username' format
|
||||
:param str password: Password
|
||||
:param str session: Unused. Kept for backwards-compatibility.
|
||||
"""
|
||||
if ntlm is None:
|
||||
raise Exception("NTLM libraries unavailable")
|
||||
|
||||
# parse the username
|
||||
try:
|
||||
self.domain, self.username = username.split('\\', 1)
|
||||
except ValueError:
|
||||
self.username = username
|
||||
self.domain = ''
|
||||
|
||||
if self.domain:
|
||||
self.domain = self.domain.upper()
|
||||
self.password = password
|
||||
|
||||
# This exposes the encrypt/decrypt methods used to encrypt and decrypt messages
|
||||
# sent after ntlm authentication. These methods are utilised by libraries that
|
||||
# call requests_ntlm to encrypt and decrypt the messages sent after authentication
|
||||
self.session_security = None
|
||||
|
||||
def retry_using_http_NTLM_auth(self, auth_header_field, auth_header,
|
||||
response, auth_type, args):
|
||||
# Get the certificate of the server if using HTTPS for CBT
|
||||
server_certificate_hash = _get_server_cert(response)
|
||||
|
||||
"""Attempt to authenticate using HTTP NTLM challenge/response."""
|
||||
if auth_header in response.request.headers:
|
||||
return response
|
||||
|
||||
content_length = int(
|
||||
response.request.headers.get('Content-Length', '0'), base=10)
|
||||
if hasattr(response.request.body, 'seek'):
|
||||
if content_length > 0:
|
||||
response.request.body.seek(-content_length, 1)
|
||||
else:
|
||||
response.request.body.seek(0, 0)
|
||||
|
||||
# Consume content and release the original connection
|
||||
# to allow our new request to reuse the same one.
|
||||
response.content
|
||||
response.raw.release_conn()
|
||||
request = response.request.copy()
|
||||
|
||||
# ntlm returns the headers as a base64 encoded bytestring. Convert to
|
||||
# a string.
|
||||
context = ntlm.Ntlm()
|
||||
negotiate_message = context.create_negotiate_message(self.domain).decode('ascii')
|
||||
auth = u'%s %s' % (auth_type, negotiate_message)
|
||||
request.headers[auth_header] = auth
|
||||
|
||||
# A streaming response breaks authentication.
|
||||
# This can be fixed by not streaming this request, which is safe
|
||||
# because the returned response3 will still have stream=True set if
|
||||
# specified in args. In addition, we expect this request to give us a
|
||||
# challenge and not the real content, so the content will be short
|
||||
# anyway.
|
||||
args_nostream = dict(args, stream=False)
|
||||
response2 = response.connection.send(request, **args_nostream)
|
||||
|
||||
# needed to make NTLM auth compatible with requests-2.3.0
|
||||
|
||||
# Consume content and release the original connection
|
||||
# to allow our new request to reuse the same one.
|
||||
response2.content
|
||||
response2.raw.release_conn()
|
||||
request = response2.request.copy()
|
||||
|
||||
# this is important for some web applications that store
|
||||
# authentication-related info in cookies (it took a long time to
|
||||
# figure out)
|
||||
if response2.headers.get('set-cookie'):
|
||||
request.headers['Cookie'] = response2.headers.get('set-cookie')
|
||||
|
||||
# get the challenge
|
||||
auth_header_value = response2.headers[auth_header_field]
|
||||
|
||||
auth_strip = auth_type + ' '
|
||||
|
||||
ntlm_header_value = next(
|
||||
s for s in (val.lstrip() for val in auth_header_value.split(','))
|
||||
if s.startswith(auth_strip)
|
||||
).strip()
|
||||
|
||||
# Parse the challenge in the ntlm context
|
||||
context.parse_challenge_message(ntlm_header_value[len(auth_strip):])
|
||||
|
||||
# build response
|
||||
# Get the response based on the challenge message
|
||||
authenticate_message = context.create_authenticate_message(
|
||||
self.username,
|
||||
self.password,
|
||||
self.domain,
|
||||
server_certificate_hash=server_certificate_hash
|
||||
)
|
||||
authenticate_message = authenticate_message.decode('ascii')
|
||||
auth = u'%s %s' % (auth_type, authenticate_message)
|
||||
request.headers[auth_header] = auth
|
||||
|
||||
response3 = response2.connection.send(request, **args)
|
||||
|
||||
# Update the history.
|
||||
response3.history.append(response)
|
||||
response3.history.append(response2)
|
||||
|
||||
# Get the session_security object created by ntlm-auth for signing and sealing of messages
|
||||
self.session_security = context.session_security
|
||||
|
||||
return response3
|
||||
|
||||
def response_hook(self, r, **kwargs):
|
||||
"""The actual hook handler."""
|
||||
if r.status_code == 401:
|
||||
# Handle server auth.
|
||||
www_authenticate = r.headers.get('www-authenticate', '').lower()
|
||||
auth_type = _auth_type_from_header(www_authenticate)
|
||||
|
||||
if auth_type is not None:
|
||||
return self.retry_using_http_NTLM_auth(
|
||||
'www-authenticate',
|
||||
'Authorization',
|
||||
r,
|
||||
auth_type,
|
||||
kwargs
|
||||
)
|
||||
elif r.status_code == 407:
|
||||
# If we didn't have server auth, do proxy auth.
|
||||
proxy_authenticate = r.headers.get(
|
||||
'proxy-authenticate', ''
|
||||
).lower()
|
||||
auth_type = _auth_type_from_header(proxy_authenticate)
|
||||
if auth_type is not None:
|
||||
return self.retry_using_http_NTLM_auth(
|
||||
'proxy-authenticate',
|
||||
'Proxy-authorization',
|
||||
r,
|
||||
auth_type,
|
||||
kwargs
|
||||
)
|
||||
|
||||
return r
|
||||
|
||||
def __call__(self, r):
|
||||
# we must keep the connection because NTLM authenticates the
|
||||
# connection, not single requests
|
||||
r.headers["Connection"] = "Keep-Alive"
|
||||
|
||||
r.register_hook('response', self.response_hook)
|
||||
return r
|
||||
|
||||
|
||||
def _auth_type_from_header(header):
|
||||
"""
|
||||
Given a WWW-Authenticate or Proxy-Authenticate header, returns the
|
||||
authentication type to use. We prefer NTLM over Negotiate if the server
|
||||
suppports it.
|
||||
"""
|
||||
if 'ntlm' in header:
|
||||
return 'NTLM'
|
||||
elif 'negotiate' in header:
|
||||
return 'Negotiate'
|
||||
|
||||
return None
|
||||
|
||||
def _get_server_cert(response):
|
||||
"""
|
||||
Get the certificate at the request_url and return it as a SHA256 hash. Will get the raw socket from the
|
||||
original response from the server. This socket is then checked if it is an SSL socket and then used to
|
||||
get the hash of the certificate. The certificate hash is then used with NTLMv2 authentication for
|
||||
Channel Binding Tokens support. If the raw object is not a urllib3 HTTPReponse (default with requests)
|
||||
then no certificate will be returned.
|
||||
|
||||
:param response: The original 401 response from the server
|
||||
:return: SHA256 hash of the DER encoded certificate at the request_url or None if not a HTTPS endpoint
|
||||
"""
|
||||
certificate_hash = None
|
||||
raw_response = response.raw
|
||||
|
||||
if isinstance(raw_response, HTTPResponse):
|
||||
if sys.version_info > (3, 0):
|
||||
socket = raw_response._fp.fp.raw._sock
|
||||
else:
|
||||
socket = raw_response._fp.fp._sock
|
||||
|
||||
try:
|
||||
server_certificate = socket.getpeercert(True)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
hash_object = hashlib.sha256(server_certificate)
|
||||
certificate_hash = hash_object.hexdigest().upper()
|
||||
else:
|
||||
warnings.warn("Requests is running with a non urllib3 backend, cannot retrieve server certificate for CBT", NoCertificateRetrievedWarning)
|
||||
|
||||
return certificate_hash
|
||||
|
||||
class NoCertificateRetrievedWarning(Warning):
|
||||
pass
|
Loading…
Reference in a new issue