upgrade wakatime-cli to v7.0.2

This commit is contained in:
Alan Hamlett 2017-02-20 16:18:38 -08:00
parent 20a1965f13
commit 8cb1c557d9
171 changed files with 15256 additions and 1300 deletions

View file

@ -1,7 +1,7 @@
__title__ = 'wakatime'
__description__ = 'Common interface to the WakaTime api.'
__url__ = 'https://github.com/wakatime/wakatime'
__version_info__ = ('6', '2', '2')
__version_info__ = ('7', '0', '2')
__version__ = '.'.join(__version_info__)
__author__ = 'Alan Hamlett'
__author_email__ = 'alan@wakatime.com'

View file

@ -0,0 +1,236 @@
# -*- coding: utf-8 -*-
"""
wakatime.arguments
~~~~~~~~~~~~~~~~~~
Command-line arguments.
:copyright: (c) 2016 Alan Hamlett.
:license: BSD, see LICENSE for more details.
"""
from __future__ import print_function
import os
import re
import time
import traceback
from .__about__ import __version__
from .configs import parseConfigFile
from .constants import AUTH_ERROR
from .packages import argparse
class FileAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
try:
if os.path.isfile(values):
values = os.path.realpath(values)
except: # pragma: nocover
pass
setattr(namespace, self.dest, values)
def parseArguments():
"""Parse command line arguments and configs from ~/.wakatime.cfg.
Command line arguments take precedence over config file settings.
Returns instances of ArgumentParser and SafeConfigParser.
"""
# define supported command line arguments
parser = argparse.ArgumentParser(
description='Common interface for the WakaTime api.')
parser.add_argument('--entity', dest='entity', metavar='FILE',
action=FileAction,
help='absolute path to file for the heartbeat; can also be a '+
'url, domain, or app when --entity-type is not file')
parser.add_argument('--file', dest='file', action=FileAction,
help=argparse.SUPPRESS)
parser.add_argument('--key', dest='key',
help='your wakatime api key; uses api_key from '+
'~/.wakatime.cfg by default')
parser.add_argument('--write', dest='is_write',
action='store_true',
help='when set, tells api this heartbeat was triggered from '+
'writing to a file')
parser.add_argument('--plugin', dest='plugin',
help='optional text editor plugin name and version '+
'for User-Agent header')
parser.add_argument('--time', dest='timestamp', metavar='time',
type=float,
help='optional floating-point unix epoch timestamp; '+
'uses current time by default')
parser.add_argument('--lineno', dest='lineno',
help='optional line number; current line being edited')
parser.add_argument('--cursorpos', dest='cursorpos',
help='optional cursor position in the current file')
parser.add_argument('--entity-type', dest='entity_type',
help='entity type for this heartbeat. can be one of "file", '+
'"domain", or "app"; defaults to file.')
parser.add_argument('--proxy', dest='proxy',
help='optional proxy configuration. Supports HTTPS '+
'and SOCKS proxies. For example: '+
'https://user:pass@host:port or '+
'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',
help='optional alternate project name; auto-discovered project '+
'takes priority')
parser.add_argument('--alternate-language', dest='alternate_language',
help=argparse.SUPPRESS)
parser.add_argument('--language', dest='language',
help='optional language name; if valid, takes priority over '+
'auto-detected language')
parser.add_argument('--hostname', dest='hostname', help='hostname of '+
'current machine.')
parser.add_argument('--disableoffline', dest='offline',
action='store_false',
help='disables offline time logging instead of queuing logged time')
parser.add_argument('--hidefilenames', dest='hidefilenames',
action='store_true',
help='obfuscate file names; will not send file names to api')
parser.add_argument('--exclude', dest='exclude', action='append',
help='filename patterns to exclude from logging; POSIX regex '+
'syntax; can be used more than once')
parser.add_argument('--include', dest='include', action='append',
help='filename patterns to log; when used in combination with '+
'--exclude, files matching include will still be logged; '+
'POSIX regex syntax; can be used more than once')
parser.add_argument('--ignore', dest='ignore', action='append',
help=argparse.SUPPRESS)
parser.add_argument('--extra-heartbeats', dest='extra_heartbeats',
action='store_true',
help='reads extra heartbeats from STDIN as a JSON array until EOF')
parser.add_argument('--logfile', dest='logfile',
help='defaults to ~/.wakatime.log')
parser.add_argument('--apiurl', dest='api_url',
help='heartbeats api url; for debugging with a local server')
parser.add_argument('--timeout', dest='timeout', type=int,
help='number of seconds to wait when sending heartbeats to api; '+
'defaults to 60 seconds')
parser.add_argument('--config', dest='config',
help='defaults to ~/.wakatime.cfg')
parser.add_argument('--verbose', dest='verbose', action='store_true',
help='turns on debug messages in log file')
parser.add_argument('--version', action='version', version=__version__)
# parse command line arguments
args = parser.parse_args()
# use current unix epoch timestamp by default
if not args.timestamp:
args.timestamp = time.time()
# parse ~/.wakatime.cfg file
configs = parseConfigFile(args.config)
if configs is None:
return args, configs
# update args from configs
if not args.hostname:
if configs.has_option('settings', 'hostname'):
args.hostname = configs.get('settings', 'hostname')
if not args.key:
default_key = None
if configs.has_option('settings', 'api_key'):
default_key = configs.get('settings', 'api_key')
elif configs.has_option('settings', 'apikey'):
default_key = configs.get('settings', 'apikey')
if default_key:
args.key = default_key
else:
try:
parser.error('Missing api key. Find your api key from wakatime.com/settings.')
except SystemExit:
raise SystemExit(AUTH_ERROR)
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)
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
else:
parser.error('argument --entity is required')
if not args.language and args.alternate_language:
args.language = args.alternate_language
if not args.exclude:
args.exclude = []
if configs.has_option('settings', 'ignore'):
try:
for pattern in configs.get('settings', 'ignore').split("\n"):
if pattern.strip() != '':
args.exclude.append(pattern)
except TypeError: # pragma: nocover
pass
if configs.has_option('settings', 'exclude'):
try:
for pattern in configs.get('settings', 'exclude').split("\n"):
if pattern.strip() != '':
args.exclude.append(pattern)
except TypeError: # pragma: nocover
pass
if not args.include:
args.include = []
if configs.has_option('settings', 'include'):
try:
for pattern in configs.get('settings', 'include').split("\n"):
if pattern.strip() != '':
args.include.append(pattern)
except TypeError: # pragma: nocover
pass
if args.hidefilenames:
args.hidefilenames = ['.*']
else:
args.hidefilenames = []
if configs.has_option('settings', 'hidefilenames'):
option = configs.get('settings', 'hidefilenames')
if option.strip().lower() == 'true':
args.hidefilenames = ['.*']
elif option.strip().lower() != 'false':
for pattern in option.split("\n"):
if pattern.strip() != '':
args.hidefilenames.append(pattern)
if args.offline and configs.has_option('settings', 'offline'):
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:
pattern = r'^((https?|socks5)://)?([^:@]+(:([^:@])+)?@)?[^:]+(:\d+)?$'
if '\\' in args.proxy:
pattern = r'^.*\\.+$'
is_valid = not not re.match(pattern, args.proxy, re.I)
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'):
args.verbose = configs.getboolean('settings', 'debug')
if not args.logfile and configs.has_option('settings', 'logfile'):
args.logfile = configs.get('settings', 'logfile')
if not args.logfile and os.environ.get('WAKATIME_HOME'):
home = os.environ.get('WAKATIME_HOME')
args.logfile = os.path.join(os.path.expanduser(home), '.wakatime.log')
if not args.api_url and configs.has_option('settings', 'api_url'):
args.api_url = configs.get('settings', 'api_url')
if not args.timeout and configs.has_option('settings', 'timeout'):
try:
args.timeout = int(configs.get('settings', 'timeout'))
except ValueError:
print(traceback.format_exc())
return args, configs

View file

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
"""
wakatime.configs
~~~~~~~~~~~~~~~~
Config file parser.
:copyright: (c) 2016 Alan Hamlett.
:license: BSD, see LICENSE for more details.
"""
from __future__ import print_function
import os
import sys
import traceback
from .compat import u, open
from .constants import CONFIG_FILE_PARSE_ERROR
try:
import ConfigParser as configparser
except ImportError: # pragma: nocover
import configparser
def parseConfigFile(configFile=None):
"""Returns a configparser.SafeConfigParser instance with configs
read from the config file. Default location of the config file is
at ~/.wakatime.cfg.
"""
# get config file location from ENV
home = os.environ.get('WAKATIME_HOME')
if not configFile and home:
configFile = os.path.join(os.path.expanduser(home), '.wakatime.cfg')
# use default config file location
if not configFile:
configFile = os.path.join(os.path.expanduser('~'), '.wakatime.cfg')
configs = configparser.SafeConfigParser()
try:
with open(configFile, 'r', encoding='utf-8') as fh:
try:
configs.readfp(fh)
except configparser.Error:
print(traceback.format_exc())
return None
except IOError:
sys.stderr.write(u("Error: Could not read from config file {0}\n").format(u(configFile)))
raise SystemExit(CONFIG_FILE_PARSE_ERROR)
return configs

View file

@ -1,80 +1,81 @@
{
"ActionScript": "ActionScript",
"ApacheConf": "ApacheConf",
"AppleScript": "AppleScript",
"ASP": "ASP",
"Assembly": "Assembly",
"Awk": "Awk",
"Bash": "Bash",
"Basic": "Basic",
"BrightScript": "BrightScript",
"C": "C",
"C#": "C#",
"C++": "C++",
"Clojure": "Clojure",
"Cocoa": "Cocoa",
"CoffeeScript": "CoffeeScript",
"ColdFusion": "ColdFusion",
"Common Lisp": "Common Lisp",
"CSHTML": "CSHTML",
"CSS": "CSS",
"Dart": "Dart",
"Delphi": "Delphi",
"Elixir": "Elixir",
"Elm": "Elm",
"Emacs Lisp": "Emacs Lisp",
"Erlang": "Erlang",
"F#": "F#",
"Fortran": "Fortran",
"Go": "Go",
"Gous": "Gosu",
"Groovy": "Groovy",
"Haml": "Haml",
"HaXe": "HaXe",
"Haskell": "Haskell",
"HTML": "HTML",
"INI": "INI",
"Jade": "Jade",
"Java": "Java",
"JavaScript": "JavaScript",
"JSON": "JSON",
"JSX": "JSX",
"Kotlin": "Kotlin",
"LESS": "LESS",
"Lua": "Lua",
"Markdown": "Markdown",
"Matlab": "Matlab",
"Mustache": "Mustache",
"OCaml": "OCaml",
"Objective-C": "Objective-C",
"Objective-C++": "Objective-C++",
"Objective-J": "Objective-J",
"Perl": "Perl",
"PHP": "PHP",
"PowerShell": "PowerShell",
"Prolog": "Prolog",
"Puppet": "Puppet",
"Python": "Python",
"R": "R",
"reStructuredText": "reStructuredText",
"Ruby": "Ruby",
"Rust": "Rust",
"Sass": "Sass",
"Scala": "Scala",
"Scheme": "Scheme",
"SCSS": "SCSS",
"Shell": "Shell",
"Slim": "Slim",
"Smalltalk": "Smalltalk",
"SQL": "SQL",
"Swift": "Swift",
"Text": "Text",
"Turing": "Turing",
"Twig": "Twig",
"TypeScript": "TypeScript",
"VB.net": "VB.net",
"VimL": "VimL",
"XAML": "XAML",
"XML": "XML",
"YAML": "YAML"
"actionscript": "ActionScript",
"apacheconf": "ApacheConf",
"applescript": "AppleScript",
"asp": "ASP",
"assembly": "Assembly",
"awk": "Awk",
"bash": "Bash",
"basic": "Basic",
"brightscript": "BrightScript",
"c": "C",
"c#": "C#",
"c++": "C++",
"clojure": "Clojure",
"cocoa": "Cocoa",
"coffeescript": "CoffeeScript",
"coldfusion": "ColdFusion",
"common lisp": "Common Lisp",
"cshtml": "CSHTML",
"css": "CSS",
"dart": "Dart",
"delphi": "Delphi",
"elixir": "Elixir",
"elm": "Elm",
"emacs lisp": "Emacs Lisp",
"erlang": "Erlang",
"f#": "F#",
"fortran": "Fortran",
"go": "Go",
"gous": "Gosu",
"groovy": "Groovy",
"haml": "Haml",
"haskell": "Haskell",
"haxe": "HaXe",
"html": "HTML",
"ini": "INI",
"jade": "Jade",
"java": "Java",
"javascript": "JavaScript",
"json": "JSON",
"jsx": "JSX",
"kotlin": "Kotlin",
"less": "LESS",
"lua": "Lua",
"markdown": "Markdown",
"matlab": "Matlab",
"mustache": "Mustache",
"objective-c": "Objective-C",
"objective-c++": "Objective-C++",
"objective-j": "Objective-J",
"ocaml": "OCaml",
"perl": "Perl",
"php": "PHP",
"powershell": "PowerShell",
"prolog": "Prolog",
"puppet": "Puppet",
"python": "Python",
"r": "R",
"restructuredtext": "reStructuredText",
"ruby": "Ruby",
"rust": "Rust",
"sass": "Sass",
"scala": "Scala",
"scheme": "Scheme",
"scss": "SCSS",
"shell": "Shell",
"slim": "Slim",
"smalltalk": "Smalltalk",
"sql": "SQL",
"swift": "Swift",
"text": "Text",
"turing": "Turing",
"twig": "Twig",
"typescript": "TypeScript",
"typoscript": "TypoScript",
"vb.net": "VB.net",
"viml": "VimL",
"xaml": "XAML",
"xml": "XML",
"yaml": "YAML"
}

View file

@ -3,7 +3,7 @@
wakatime.main
~~~~~~~~~~~~~
wakatime module entry point.
Module entry point.
:copyright: (c) 2013 Alan Hamlett.
:license: BSD, see LICENSE for more details.
@ -14,23 +14,18 @@ from __future__ import print_function
import base64
import logging
import os
import platform
import re
import sys
import time
import traceback
import socket
try:
import ConfigParser as configparser
except ImportError: # pragma: nocover
import configparser
pwd = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.dirname(pwd))
sys.path.insert(0, os.path.join(pwd, 'packages'))
from .__about__ import __version__
from .compat import u, open, is_py3
from .arguments import parseArguments
from .compat import u, is_py3
from .constants import (
API_ERROR,
AUTH_ERROR,
@ -41,12 +36,12 @@ from .constants import (
)
from .logger import setup_logging
from .offlinequeue import Queue
from .packages import argparse
from .packages import requests
from .packages.requests.exceptions import RequestException
from .project import get_project_info
from .session_cache import SessionCache
from .stats import get_file_stats
from .utils import get_user_agent, should_exclude, format_file_path
try:
from .packages import simplejson as json # pragma: nocover
except (ImportError, SyntaxError): # pragma: nocover
@ -57,271 +52,6 @@ from .packages import tzlocal
log = logging.getLogger('WakaTime')
class FileAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
try:
if os.path.isfile(values):
values = os.path.realpath(values)
except: # pragma: nocover
pass
setattr(namespace, self.dest, values)
def parseConfigFile(configFile=None):
"""Returns a configparser.SafeConfigParser instance with configs
read from the config file. Default location of the config file is
at ~/.wakatime.cfg.
"""
# get config file location from ENV
home = os.environ.get('WAKATIME_HOME')
if not configFile and home:
configFile = os.path.join(os.path.expanduser(home), '.wakatime.cfg')
# use default config file location
if not configFile:
configFile = os.path.join(os.path.expanduser('~'), '.wakatime.cfg')
configs = configparser.SafeConfigParser()
try:
with open(configFile, 'r', encoding='utf-8') as fh:
try:
configs.readfp(fh)
except configparser.Error:
print(traceback.format_exc())
return None
except IOError:
sys.stderr.write(u("Error: Could not read from config file {0}\n").format(u(configFile)))
raise SystemExit(CONFIG_FILE_PARSE_ERROR)
return configs
def parseArguments():
"""Parse command line arguments and configs from ~/.wakatime.cfg.
Command line arguments take precedence over config file settings.
Returns instances of ArgumentParser and SafeConfigParser.
"""
# define supported command line arguments
parser = argparse.ArgumentParser(
description='Common interface for the WakaTime api.')
parser.add_argument('--entity', dest='entity', metavar='FILE',
action=FileAction,
help='absolute path to file for the heartbeat; can also be a '+
'url, domain, or app when --entity-type is not file')
parser.add_argument('--file', dest='file', action=FileAction,
help=argparse.SUPPRESS)
parser.add_argument('--key', dest='key',
help='your wakatime api key; uses api_key from '+
'~/.wakatime.cfg by default')
parser.add_argument('--write', dest='is_write',
action='store_true',
help='when set, tells api this heartbeat was triggered from '+
'writing to a file')
parser.add_argument('--plugin', dest='plugin',
help='optional text editor plugin name and version '+
'for User-Agent header')
parser.add_argument('--time', dest='timestamp', metavar='time',
type=float,
help='optional floating-point unix epoch timestamp; '+
'uses current time by default')
parser.add_argument('--lineno', dest='lineno',
help='optional line number; current line being edited')
parser.add_argument('--cursorpos', dest='cursorpos',
help='optional cursor position in the current file')
parser.add_argument('--entity-type', dest='entity_type',
help='entity type for this heartbeat. can be one of "file", '+
'"domain", or "app"; defaults to file.')
parser.add_argument('--proxy', dest='proxy',
help='optional proxy configuration. Supports HTTPS '+
'and SOCKS proxies. For example: '+
'https://user:pass@host:port or '+
'socks5://user:pass@host:port')
parser.add_argument('--project', dest='project',
help='optional project name')
parser.add_argument('--alternate-project', dest='alternate_project',
help='optional alternate project name; auto-discovered project '+
'takes priority')
parser.add_argument('--alternate-language', dest='alternate_language',
help='optional alternate language name; auto-detected language'+
'takes priority')
parser.add_argument('--hostname', dest='hostname', help='hostname of '+
'current machine.')
parser.add_argument('--disableoffline', dest='offline',
action='store_false',
help='disables offline time logging instead of queuing logged time')
parser.add_argument('--hidefilenames', dest='hidefilenames',
action='store_true',
help='obfuscate file names; will not send file names to api')
parser.add_argument('--exclude', dest='exclude', action='append',
help='filename patterns to exclude from logging; POSIX regex '+
'syntax; can be used more than once')
parser.add_argument('--include', dest='include', action='append',
help='filename patterns to log; when used in combination with '+
'--exclude, files matching include will still be logged; '+
'POSIX regex syntax; can be used more than once')
parser.add_argument('--ignore', dest='ignore', action='append',
help=argparse.SUPPRESS)
parser.add_argument('--extra-heartbeats', dest='extra_heartbeats',
action='store_true',
help='reads extra heartbeats from STDIN as a JSON array until EOF')
parser.add_argument('--logfile', dest='logfile',
help='defaults to ~/.wakatime.log')
parser.add_argument('--apiurl', dest='api_url',
help='heartbeats api url; for debugging with a local server')
parser.add_argument('--timeout', dest='timeout', type=int,
help='number of seconds to wait when sending heartbeats to api; '+
'defaults to 60 seconds')
parser.add_argument('--config', dest='config',
help='defaults to ~/.wakatime.cfg')
parser.add_argument('--verbose', dest='verbose', action='store_true',
help='turns on debug messages in log file')
parser.add_argument('--version', action='version', version=__version__)
# parse command line arguments
args = parser.parse_args()
# use current unix epoch timestamp by default
if not args.timestamp:
args.timestamp = time.time()
# parse ~/.wakatime.cfg file
configs = parseConfigFile(args.config)
if configs is None:
return args, configs
# update args from configs
if not args.hostname:
if configs.has_option('settings', 'hostname'):
args.hostname = configs.get('settings', 'hostname')
if not args.key:
default_key = None
if configs.has_option('settings', 'api_key'):
default_key = configs.get('settings', 'api_key')
elif configs.has_option('settings', 'apikey'):
default_key = configs.get('settings', 'apikey')
if default_key:
args.key = default_key
else:
try:
parser.error('Missing api key')
except SystemExit:
raise SystemExit(AUTH_ERROR)
if not args.entity:
if args.file:
args.entity = args.file
else:
parser.error('argument --entity is required')
if not args.exclude:
args.exclude = []
if configs.has_option('settings', 'ignore'):
try:
for pattern in configs.get('settings', 'ignore').split("\n"):
if pattern.strip() != '':
args.exclude.append(pattern)
except TypeError: # pragma: nocover
pass
if configs.has_option('settings', 'exclude'):
try:
for pattern in configs.get('settings', 'exclude').split("\n"):
if pattern.strip() != '':
args.exclude.append(pattern)
except TypeError: # pragma: nocover
pass
if not args.include:
args.include = []
if configs.has_option('settings', 'include'):
try:
for pattern in configs.get('settings', 'include').split("\n"):
if pattern.strip() != '':
args.include.append(pattern)
except TypeError: # pragma: nocover
pass
if args.hidefilenames:
args.hidefilenames = ['.*']
else:
args.hidefilenames = []
if configs.has_option('settings', 'hidefilenames'):
option = configs.get('settings', 'hidefilenames')
if option.strip().lower() == 'true':
args.hidefilenames = ['.*']
elif option.strip().lower() != 'false':
try:
for pattern in option.split("\n"):
if pattern.strip() != '':
args.hidefilenames.append(pattern)
except TypeError:
pass
if args.offline and configs.has_option('settings', 'offline'):
args.offline = configs.getboolean('settings', 'offline')
if not args.proxy and configs.has_option('settings', 'proxy'):
args.proxy = configs.get('settings', 'proxy')
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'):
args.verbose = configs.getboolean('settings', 'debug')
if not args.logfile and configs.has_option('settings', 'logfile'):
args.logfile = configs.get('settings', 'logfile')
if not args.logfile and os.environ.get('WAKATIME_HOME'):
home = os.environ.get('WAKATIME_HOME')
args.logfile = os.path.join(os.path.expanduser(home), '.wakatime.log')
if not args.api_url and configs.has_option('settings', 'api_url'):
args.api_url = configs.get('settings', 'api_url')
if not args.timeout and configs.has_option('settings', 'timeout'):
try:
args.timeout = int(configs.get('settings', 'timeout'))
except ValueError:
print(traceback.format_exc())
return args, configs
def should_exclude(entity, include, exclude):
if entity is not None and entity.strip() != '':
for pattern in include:
try:
compiled = re.compile(pattern, re.IGNORECASE)
if compiled.search(entity):
return False
except re.error as ex:
log.warning(u('Regex error ({msg}) for include pattern: {pattern}').format(
msg=u(ex),
pattern=u(pattern),
))
for pattern in exclude:
try:
compiled = re.compile(pattern, re.IGNORECASE)
if compiled.search(entity):
return pattern
except re.error as ex:
log.warning(u('Regex error ({msg}) for exclude pattern: {pattern}').format(
msg=u(ex),
pattern=u(pattern),
))
return False
def get_user_agent(plugin):
ver = sys.version_info
python_version = '%d.%d.%d.%s.%d' % (ver[0], ver[1], ver[2], ver[3], ver[4])
user_agent = u('wakatime/{ver} ({platform}) Python{py_ver}').format(
ver=u(__version__),
platform=u(platform.platform()),
py_ver=python_version,
)
if plugin:
user_agent = u('{user_agent} {plugin}').format(
user_agent=user_agent,
plugin=u(plugin),
)
else:
user_agent = u('{user_agent} Unknown/0').format(
user_agent=user_agent,
)
return user_agent
def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None,
entity=None, timestamp=None, is_write=None, plugin=None,
offline=None, entity_type='file', hidefilenames=None,
@ -385,9 +115,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 +127,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:
@ -501,17 +241,6 @@ def sync_offline_heartbeats(args, hostname):
return SUCCESS
def format_file_path(filepath):
"""Formats a path as absolute and with the correct platform separator."""
try:
filepath = os.path.realpath(os.path.abspath(filepath))
filepath = re.sub(r'[/\\]', os.path.sep, filepath)
except:
pass # pragma: nocover
return filepath
def process_heartbeat(args, configs, hostname, heartbeat):
exclude = should_exclude(heartbeat['entity'], args.include, args.exclude)
if exclude is not False:
@ -533,7 +262,7 @@ def process_heartbeat(args, configs, hostname, heartbeat):
lineno=heartbeat.get('lineno'),
cursorpos=heartbeat.get('cursorpos'),
plugin=args.plugin,
alternate_language=heartbeat.get('alternate_language'))
language=heartbeat.get('language'))
project = heartbeat.get('project') or heartbeat.get('alternate_project')
branch = None

View file

@ -34,7 +34,7 @@ class Queue(object):
return self.db_file
def connect(self):
conn = sqlite3.connect(self.get_db_file())
conn = sqlite3.connect(self.get_db_file(), isolation_level=None)
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS {0} (
entity text,

View file

@ -1,4 +0,0 @@
import tzlocal
from pygments.lexers import get_lexer_by_name, guess_lexer_for_filename
from pygments.modeline import get_filetype_from_buffer
from pygments.util import ClassNotFound

View 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)

View file

@ -0,0 +1,3 @@
from . import ntlm, session_security
__all__ = ('ntlm', 'session_security')

View 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

View 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

View 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

View 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"

View 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

View 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

View 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),
]
]

View 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

View 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)

View 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")

View 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]

View 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

View 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

View file

@ -450,6 +450,7 @@ class TypeScriptLexer(RegexLexer):
aliases = ['ts', 'typescript']
filenames = ['*.ts', '*.tsx']
mimetypes = ['text/x-typescript']
priority = 0.11
flags = re.DOTALL | re.MULTILINE

View file

@ -9,8 +9,8 @@ on how to use these modules.
'''
# The IANA (nee Olson) database is updated several times a year.
OLSON_VERSION = '2016f'
VERSION = '2016.6.1' # Switching to pip compatible version numbering.
OLSON_VERSION = '2016j'
VERSION = '2016.10' # Switching to pip compatible version numbering.
__version__ = VERSION
OLSEN_VERSION = OLSON_VERSION # Old releases had this misspelling
@ -731,6 +731,7 @@ all_timezones = \
'Asia/Aqtobe',
'Asia/Ashgabat',
'Asia/Ashkhabad',
'Asia/Atyrau',
'Asia/Baghdad',
'Asia/Bahrain',
'Asia/Baku',
@ -751,6 +752,7 @@ all_timezones = \
'Asia/Dili',
'Asia/Dubai',
'Asia/Dushanbe',
'Asia/Famagusta',
'Asia/Gaza',
'Asia/Harbin',
'Asia/Hebron',
@ -816,6 +818,7 @@ all_timezones = \
'Asia/Vientiane',
'Asia/Vladivostok',
'Asia/Yakutsk',
'Asia/Yangon',
'Asia/Yekaterinburg',
'Asia/Yerevan',
'Atlantic/Azores',
@ -955,6 +958,7 @@ all_timezones = \
'Europe/Samara',
'Europe/San_Marino',
'Europe/Sarajevo',
'Europe/Saratov',
'Europe/Simferopol',
'Europe/Skopje',
'Europe/Sofia',
@ -1301,6 +1305,7 @@ common_timezones = \
'Asia/Aqtau',
'Asia/Aqtobe',
'Asia/Ashgabat',
'Asia/Atyrau',
'Asia/Baghdad',
'Asia/Bahrain',
'Asia/Baku',
@ -1317,6 +1322,7 @@ common_timezones = \
'Asia/Dili',
'Asia/Dubai',
'Asia/Dushanbe',
'Asia/Famagusta',
'Asia/Gaza',
'Asia/Hebron',
'Asia/Ho_Chi_Minh',
@ -1351,7 +1357,6 @@ common_timezones = \
'Asia/Pyongyang',
'Asia/Qatar',
'Asia/Qyzylorda',
'Asia/Rangoon',
'Asia/Riyadh',
'Asia/Sakhalin',
'Asia/Samarkand',
@ -1372,6 +1377,7 @@ common_timezones = \
'Asia/Vientiane',
'Asia/Vladivostok',
'Asia/Yakutsk',
'Asia/Yangon',
'Asia/Yekaterinburg',
'Asia/Yerevan',
'Atlantic/Azores',
@ -1444,6 +1450,7 @@ common_timezones = \
'Europe/Samara',
'Europe/San_Marino',
'Europe/Sarajevo',
'Europe/Saratov',
'Europe/Simferopol',
'Europe/Skopje',
'Europe/Sofia',

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more