upgrade wakatime-cli to v7.0.2
This commit is contained in:
parent
28a55238a6
commit
2e427cbb3c
171 changed files with 15256 additions and 1300 deletions
|
@ -1,7 +1,7 @@
|
||||||
__title__ = 'wakatime'
|
__title__ = 'wakatime'
|
||||||
__description__ = 'Common interface to the WakaTime api.'
|
__description__ = 'Common interface to the WakaTime api.'
|
||||||
__url__ = 'https://github.com/wakatime/wakatime'
|
__url__ = 'https://github.com/wakatime/wakatime'
|
||||||
__version_info__ = ('6', '2', '2')
|
__version_info__ = ('7', '0', '2')
|
||||||
__version__ = '.'.join(__version_info__)
|
__version__ = '.'.join(__version_info__)
|
||||||
__author__ = 'Alan Hamlett'
|
__author__ = 'Alan Hamlett'
|
||||||
__author_email__ = 'alan@wakatime.com'
|
__author_email__ = 'alan@wakatime.com'
|
||||||
|
|
236
plugin/packages/wakatime/arguments.py
Normal file
236
plugin/packages/wakatime/arguments.py
Normal 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
|
55
plugin/packages/wakatime/configs.py
Normal file
55
plugin/packages/wakatime/configs.py
Normal 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
|
|
@ -1,80 +1,81 @@
|
||||||
{
|
{
|
||||||
"ActionScript": "ActionScript",
|
"actionscript": "ActionScript",
|
||||||
"ApacheConf": "ApacheConf",
|
"apacheconf": "ApacheConf",
|
||||||
"AppleScript": "AppleScript",
|
"applescript": "AppleScript",
|
||||||
"ASP": "ASP",
|
"asp": "ASP",
|
||||||
"Assembly": "Assembly",
|
"assembly": "Assembly",
|
||||||
"Awk": "Awk",
|
"awk": "Awk",
|
||||||
"Bash": "Bash",
|
"bash": "Bash",
|
||||||
"Basic": "Basic",
|
"basic": "Basic",
|
||||||
"BrightScript": "BrightScript",
|
"brightscript": "BrightScript",
|
||||||
"C": "C",
|
"c": "C",
|
||||||
"C#": "C#",
|
"c#": "C#",
|
||||||
"C++": "C++",
|
"c++": "C++",
|
||||||
"Clojure": "Clojure",
|
"clojure": "Clojure",
|
||||||
"Cocoa": "Cocoa",
|
"cocoa": "Cocoa",
|
||||||
"CoffeeScript": "CoffeeScript",
|
"coffeescript": "CoffeeScript",
|
||||||
"ColdFusion": "ColdFusion",
|
"coldfusion": "ColdFusion",
|
||||||
"Common Lisp": "Common Lisp",
|
"common lisp": "Common Lisp",
|
||||||
"CSHTML": "CSHTML",
|
"cshtml": "CSHTML",
|
||||||
"CSS": "CSS",
|
"css": "CSS",
|
||||||
"Dart": "Dart",
|
"dart": "Dart",
|
||||||
"Delphi": "Delphi",
|
"delphi": "Delphi",
|
||||||
"Elixir": "Elixir",
|
"elixir": "Elixir",
|
||||||
"Elm": "Elm",
|
"elm": "Elm",
|
||||||
"Emacs Lisp": "Emacs Lisp",
|
"emacs lisp": "Emacs Lisp",
|
||||||
"Erlang": "Erlang",
|
"erlang": "Erlang",
|
||||||
"F#": "F#",
|
"f#": "F#",
|
||||||
"Fortran": "Fortran",
|
"fortran": "Fortran",
|
||||||
"Go": "Go",
|
"go": "Go",
|
||||||
"Gous": "Gosu",
|
"gous": "Gosu",
|
||||||
"Groovy": "Groovy",
|
"groovy": "Groovy",
|
||||||
"Haml": "Haml",
|
"haml": "Haml",
|
||||||
"HaXe": "HaXe",
|
"haskell": "Haskell",
|
||||||
"Haskell": "Haskell",
|
"haxe": "HaXe",
|
||||||
"HTML": "HTML",
|
"html": "HTML",
|
||||||
"INI": "INI",
|
"ini": "INI",
|
||||||
"Jade": "Jade",
|
"jade": "Jade",
|
||||||
"Java": "Java",
|
"java": "Java",
|
||||||
"JavaScript": "JavaScript",
|
"javascript": "JavaScript",
|
||||||
"JSON": "JSON",
|
"json": "JSON",
|
||||||
"JSX": "JSX",
|
"jsx": "JSX",
|
||||||
"Kotlin": "Kotlin",
|
"kotlin": "Kotlin",
|
||||||
"LESS": "LESS",
|
"less": "LESS",
|
||||||
"Lua": "Lua",
|
"lua": "Lua",
|
||||||
"Markdown": "Markdown",
|
"markdown": "Markdown",
|
||||||
"Matlab": "Matlab",
|
"matlab": "Matlab",
|
||||||
"Mustache": "Mustache",
|
"mustache": "Mustache",
|
||||||
"OCaml": "OCaml",
|
"objective-c": "Objective-C",
|
||||||
"Objective-C": "Objective-C",
|
"objective-c++": "Objective-C++",
|
||||||
"Objective-C++": "Objective-C++",
|
"objective-j": "Objective-J",
|
||||||
"Objective-J": "Objective-J",
|
"ocaml": "OCaml",
|
||||||
"Perl": "Perl",
|
"perl": "Perl",
|
||||||
"PHP": "PHP",
|
"php": "PHP",
|
||||||
"PowerShell": "PowerShell",
|
"powershell": "PowerShell",
|
||||||
"Prolog": "Prolog",
|
"prolog": "Prolog",
|
||||||
"Puppet": "Puppet",
|
"puppet": "Puppet",
|
||||||
"Python": "Python",
|
"python": "Python",
|
||||||
"R": "R",
|
"r": "R",
|
||||||
"reStructuredText": "reStructuredText",
|
"restructuredtext": "reStructuredText",
|
||||||
"Ruby": "Ruby",
|
"ruby": "Ruby",
|
||||||
"Rust": "Rust",
|
"rust": "Rust",
|
||||||
"Sass": "Sass",
|
"sass": "Sass",
|
||||||
"Scala": "Scala",
|
"scala": "Scala",
|
||||||
"Scheme": "Scheme",
|
"scheme": "Scheme",
|
||||||
"SCSS": "SCSS",
|
"scss": "SCSS",
|
||||||
"Shell": "Shell",
|
"shell": "Shell",
|
||||||
"Slim": "Slim",
|
"slim": "Slim",
|
||||||
"Smalltalk": "Smalltalk",
|
"smalltalk": "Smalltalk",
|
||||||
"SQL": "SQL",
|
"sql": "SQL",
|
||||||
"Swift": "Swift",
|
"swift": "Swift",
|
||||||
"Text": "Text",
|
"text": "Text",
|
||||||
"Turing": "Turing",
|
"turing": "Turing",
|
||||||
"Twig": "Twig",
|
"twig": "Twig",
|
||||||
"TypeScript": "TypeScript",
|
"typescript": "TypeScript",
|
||||||
"VB.net": "VB.net",
|
"typoscript": "TypoScript",
|
||||||
"VimL": "VimL",
|
"vb.net": "VB.net",
|
||||||
"XAML": "XAML",
|
"viml": "VimL",
|
||||||
"XML": "XML",
|
"xaml": "XAML",
|
||||||
"YAML": "YAML"
|
"xml": "XML",
|
||||||
|
"yaml": "YAML"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
wakatime.main
|
wakatime.main
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
wakatime module entry point.
|
Module entry point.
|
||||||
|
|
||||||
:copyright: (c) 2013 Alan Hamlett.
|
:copyright: (c) 2013 Alan Hamlett.
|
||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
|
@ -14,23 +14,18 @@ from __future__ import print_function
|
||||||
import base64
|
import base64
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import platform
|
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import time
|
|
||||||
import traceback
|
import traceback
|
||||||
import socket
|
import socket
|
||||||
try:
|
|
||||||
import ConfigParser as configparser
|
|
||||||
except ImportError: # pragma: nocover
|
|
||||||
import configparser
|
|
||||||
|
|
||||||
pwd = os.path.dirname(os.path.abspath(__file__))
|
pwd = os.path.dirname(os.path.abspath(__file__))
|
||||||
sys.path.insert(0, os.path.dirname(pwd))
|
sys.path.insert(0, os.path.dirname(pwd))
|
||||||
sys.path.insert(0, os.path.join(pwd, 'packages'))
|
sys.path.insert(0, os.path.join(pwd, 'packages'))
|
||||||
|
|
||||||
from .__about__ import __version__
|
from .__about__ import __version__
|
||||||
from .compat import u, open, is_py3
|
from .arguments import parseArguments
|
||||||
|
from .compat import u, is_py3
|
||||||
from .constants import (
|
from .constants import (
|
||||||
API_ERROR,
|
API_ERROR,
|
||||||
AUTH_ERROR,
|
AUTH_ERROR,
|
||||||
|
@ -41,12 +36,12 @@ from .constants import (
|
||||||
)
|
)
|
||||||
from .logger import setup_logging
|
from .logger import setup_logging
|
||||||
from .offlinequeue import Queue
|
from .offlinequeue import Queue
|
||||||
from .packages import argparse
|
|
||||||
from .packages import requests
|
from .packages import requests
|
||||||
from .packages.requests.exceptions import RequestException
|
from .packages.requests.exceptions import RequestException
|
||||||
from .project import get_project_info
|
from .project import get_project_info
|
||||||
from .session_cache import SessionCache
|
from .session_cache import SessionCache
|
||||||
from .stats import get_file_stats
|
from .stats import get_file_stats
|
||||||
|
from .utils import get_user_agent, should_exclude, format_file_path
|
||||||
try:
|
try:
|
||||||
from .packages import simplejson as json # pragma: nocover
|
from .packages import simplejson as json # pragma: nocover
|
||||||
except (ImportError, SyntaxError): # pragma: nocover
|
except (ImportError, SyntaxError): # pragma: nocover
|
||||||
|
@ -57,271 +52,6 @@ from .packages import tzlocal
|
||||||
log = logging.getLogger('WakaTime')
|
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,
|
def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None,
|
||||||
entity=None, timestamp=None, is_write=None, plugin=None,
|
entity=None, timestamp=None, is_write=None, plugin=None,
|
||||||
offline=None, entity_type='file', hidefilenames=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:
|
if hostname:
|
||||||
headers['X-Machine-Name'] = u(hostname).encode('utf-8')
|
headers['X-Machine-Name'] = u(hostname).encode('utf-8')
|
||||||
proxies = {}
|
|
||||||
if proxy:
|
|
||||||
proxies['https'] = proxy
|
|
||||||
|
|
||||||
# add Olson timezone to request
|
# add Olson timezone to request
|
||||||
try:
|
try:
|
||||||
|
@ -400,6 +127,19 @@ def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None,
|
||||||
session_cache = SessionCache()
|
session_cache = SessionCache()
|
||||||
session = session_cache.get()
|
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
|
# log time to api
|
||||||
response = None
|
response = None
|
||||||
try:
|
try:
|
||||||
|
@ -501,17 +241,6 @@ def sync_offline_heartbeats(args, hostname):
|
||||||
return SUCCESS
|
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):
|
def process_heartbeat(args, configs, hostname, heartbeat):
|
||||||
exclude = should_exclude(heartbeat['entity'], args.include, args.exclude)
|
exclude = should_exclude(heartbeat['entity'], args.include, args.exclude)
|
||||||
if exclude is not False:
|
if exclude is not False:
|
||||||
|
@ -533,7 +262,7 @@ def process_heartbeat(args, configs, hostname, heartbeat):
|
||||||
lineno=heartbeat.get('lineno'),
|
lineno=heartbeat.get('lineno'),
|
||||||
cursorpos=heartbeat.get('cursorpos'),
|
cursorpos=heartbeat.get('cursorpos'),
|
||||||
plugin=args.plugin,
|
plugin=args.plugin,
|
||||||
alternate_language=heartbeat.get('alternate_language'))
|
language=heartbeat.get('language'))
|
||||||
|
|
||||||
project = heartbeat.get('project') or heartbeat.get('alternate_project')
|
project = heartbeat.get('project') or heartbeat.get('alternate_project')
|
||||||
branch = None
|
branch = None
|
||||||
|
|
|
@ -34,7 +34,7 @@ class Queue(object):
|
||||||
return self.db_file
|
return self.db_file
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
conn = sqlite3.connect(self.get_db_file())
|
conn = sqlite3.connect(self.get_db_file(), isolation_level=None)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
c.execute('''CREATE TABLE IF NOT EXISTS {0} (
|
c.execute('''CREATE TABLE IF NOT EXISTS {0} (
|
||||||
entity text,
|
entity text,
|
||||||
|
|
|
@ -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
|
|
156
plugin/packages/wakatime/packages/ntlm_auth/U32.py
Normal file
156
plugin/packages/wakatime/packages/ntlm_auth/U32.py
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
# This file is part of 'NTLM Authorization Proxy Server' http://sourceforge.net/projects/ntlmaps/
|
||||||
|
# Copyright 2001 Dmitry A. Rozmanov <dima@xenon.spb.ru>
|
||||||
|
#
|
||||||
|
# This library is free software: you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation, either
|
||||||
|
# version 3 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
# This library is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||||
|
|
||||||
|
from __future__ import division
|
||||||
|
import six
|
||||||
|
|
||||||
|
C = 0x1000000000
|
||||||
|
|
||||||
|
|
||||||
|
def norm(n):
|
||||||
|
return n & 0xFFFFFFFF
|
||||||
|
|
||||||
|
|
||||||
|
class U32:
|
||||||
|
v = 0
|
||||||
|
|
||||||
|
def __init__(self, value=0):
|
||||||
|
if not isinstance(value, six.integer_types):
|
||||||
|
value = six.byte2int(value)
|
||||||
|
|
||||||
|
self.v = C + norm(abs(int(value)))
|
||||||
|
|
||||||
|
def set(self, value=0):
|
||||||
|
self.v = C + norm(abs(int(value)))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return hex(norm(self.v))
|
||||||
|
|
||||||
|
def __long__(self):
|
||||||
|
return int(norm(self.v))
|
||||||
|
|
||||||
|
def __int__(self):
|
||||||
|
return int(norm(self.v))
|
||||||
|
|
||||||
|
def __chr__(self):
|
||||||
|
return chr(norm(self.v))
|
||||||
|
|
||||||
|
def __add__(self, b):
|
||||||
|
r = U32()
|
||||||
|
r.v = C + norm(self.v + b.v)
|
||||||
|
return r
|
||||||
|
|
||||||
|
def __sub__(self, b):
|
||||||
|
r = U32()
|
||||||
|
if self.v < b.v:
|
||||||
|
r.v = C + norm(0x100000000 - (b.v - self.v))
|
||||||
|
else:
|
||||||
|
r.v = C + norm(self.v - b.v)
|
||||||
|
return r
|
||||||
|
|
||||||
|
def __mul__(self, b):
|
||||||
|
r = U32()
|
||||||
|
r.v = C + norm(self.v * b.v)
|
||||||
|
return r
|
||||||
|
|
||||||
|
def __div__(self, b):
|
||||||
|
r = U32()
|
||||||
|
r.v = C + (norm(self.v) // norm(b.v))
|
||||||
|
return r
|
||||||
|
|
||||||
|
def __truediv__(self, b):
|
||||||
|
r = U32()
|
||||||
|
r.v = C + (norm(self.v) / norm(b.v))
|
||||||
|
return r
|
||||||
|
|
||||||
|
def __mod__(self, b):
|
||||||
|
r = U32()
|
||||||
|
r.v = C + (norm(self.v) % norm(b.v))
|
||||||
|
return r
|
||||||
|
|
||||||
|
def __neg__(self):
|
||||||
|
return U32(self.v)
|
||||||
|
|
||||||
|
def __pos__(self):
|
||||||
|
return U32(self.v)
|
||||||
|
|
||||||
|
def __abs__(self):
|
||||||
|
return U32(self.v)
|
||||||
|
|
||||||
|
def __invert__(self):
|
||||||
|
r = U32()
|
||||||
|
r.v = C + norm(~self.v)
|
||||||
|
return r
|
||||||
|
|
||||||
|
def __lshift__(self, b):
|
||||||
|
r = U32()
|
||||||
|
r.v = C + norm(self.v << b)
|
||||||
|
return r
|
||||||
|
|
||||||
|
def __rshift__(self, b):
|
||||||
|
r = U32()
|
||||||
|
r.v = C + (norm(self.v) >> b)
|
||||||
|
return r
|
||||||
|
|
||||||
|
def __and__(self, b):
|
||||||
|
r = U32()
|
||||||
|
r.v = C + norm(self.v & b.v)
|
||||||
|
return r
|
||||||
|
|
||||||
|
def __or__(self, b):
|
||||||
|
r = U32()
|
||||||
|
r.v = C + norm(self.v | b.v)
|
||||||
|
return r
|
||||||
|
|
||||||
|
def __xor__(self, b):
|
||||||
|
r = U32()
|
||||||
|
r.v = C + norm(self.v ^ b.v)
|
||||||
|
return r
|
||||||
|
|
||||||
|
def __not__(self):
|
||||||
|
return U32(not norm(self.v))
|
||||||
|
|
||||||
|
def truth(self):
|
||||||
|
return norm(self.v)
|
||||||
|
|
||||||
|
def __cmp__(self, b):
|
||||||
|
if norm(self.v) > norm(b.v):
|
||||||
|
return 1
|
||||||
|
elif norm(self.v) < norm(b.v):
|
||||||
|
return -1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.v < other.v
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return self.v > other.v
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.v == other.v
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
return self.v <= other.v
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
return self.v >= other.v
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return self.v != other.v
|
||||||
|
|
||||||
|
def __nonzero__(self):
|
||||||
|
return norm(self.v)
|
3
plugin/packages/wakatime/packages/ntlm_auth/__init__.py
Normal file
3
plugin/packages/wakatime/packages/ntlm_auth/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from . import ntlm, session_security
|
||||||
|
|
||||||
|
__all__ = ('ntlm', 'session_security')
|
80
plugin/packages/wakatime/packages/ntlm_auth/compute_hash.py
Normal file
80
plugin/packages/wakatime/packages/ntlm_auth/compute_hash.py
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
# This library is free software: you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation, either
|
||||||
|
# version 3 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
# This library is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||||
|
|
||||||
|
import binascii
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
import re
|
||||||
|
from ntlm_auth import des
|
||||||
|
|
||||||
|
|
||||||
|
def _lmowfv1(password):
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
3.3.1 NTLM v1 Authentication
|
||||||
|
Same function as LMOWFv1 in document to create a one way hash of the password. Only
|
||||||
|
used in NTLMv1 auth without session security
|
||||||
|
|
||||||
|
:param password: The password of the user we are trying to authenticate with
|
||||||
|
:return res: A Lan Manager hash of the password supplied
|
||||||
|
"""
|
||||||
|
|
||||||
|
# fix the password length to 14 bytes
|
||||||
|
password = password.upper()
|
||||||
|
lm_pw = password[0:14]
|
||||||
|
|
||||||
|
# do hash
|
||||||
|
magic_str = b"KGS!@#$%" # page 56 in [MS-NLMP v28.0]
|
||||||
|
|
||||||
|
res = b''
|
||||||
|
dobj = des.DES(lm_pw[0:7])
|
||||||
|
res = res + dobj.encrypt(magic_str)
|
||||||
|
|
||||||
|
dobj = des.DES(lm_pw[7:14])
|
||||||
|
res = res + dobj.encrypt(magic_str)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _ntowfv1(password):
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
3.3.1 NTLM v1 Authentication
|
||||||
|
Same function as NTOWFv1 in document to create a one way hash of the password. Only
|
||||||
|
used in NTLMv1 auth without session security
|
||||||
|
|
||||||
|
:param password: The password of the user we are trying to authenticate with
|
||||||
|
:return digest: An NT hash of the password supplied
|
||||||
|
"""
|
||||||
|
|
||||||
|
digest = hashlib.new('md4', password.encode('utf-16le')).digest()
|
||||||
|
return digest
|
||||||
|
|
||||||
|
def _ntowfv2(user_name, password, domain_name):
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
3.3.2 NTLM v2 Authentication
|
||||||
|
Same function as NTOWFv2 (and LMOWFv2) in document to create a one way hash of the password.
|
||||||
|
This combines some extra security features over the v1 calculations used in NTLMv2 auth.
|
||||||
|
|
||||||
|
:param user_name: The user name of the user we are trying to authenticate with
|
||||||
|
:param password: The password of the user we are trying to authenticate with
|
||||||
|
:param domain_name: The domain name of the user account we are authenticated with
|
||||||
|
:return digest: An NT hash of the parameters supplied
|
||||||
|
"""
|
||||||
|
digest = _ntowfv1(password)
|
||||||
|
digest = hmac.new(digest, (user_name.upper() + domain_name).encode('utf-16le')).digest()
|
||||||
|
|
||||||
|
return digest
|
138
plugin/packages/wakatime/packages/ntlm_auth/compute_keys.py
Normal file
138
plugin/packages/wakatime/packages/ntlm_auth/compute_keys.py
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
# This library is free software: you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation, either
|
||||||
|
# version 3 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
# This library is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||||
|
|
||||||
|
import binascii
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
from ntlm_auth import des
|
||||||
|
from ntlm_auth.constants import NegotiateFlags
|
||||||
|
|
||||||
|
def _get_exchange_key_ntlm_v1(negotiate_flags, session_base_key, server_challenge, lm_challenge_response, lm_hash):
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
4.3.5.1 KXKEY
|
||||||
|
Calculates the Key Exchange Key for NTLMv1 authentication. Used for signing and sealing messages
|
||||||
|
|
||||||
|
@param negotiate_flags:
|
||||||
|
@param session_base_key: A session key calculated from the user password challenge
|
||||||
|
@param server_challenge: A random 8-byte response generated by the server in the CHALLENGE_MESSAGE
|
||||||
|
@param lm_challenge_response: The LmChallengeResponse value computed in ComputeResponse
|
||||||
|
@param lm_hash: The LMOWF computed in Compute Response
|
||||||
|
@return key_exchange_key: The Key Exchange Key (KXKEY) used to sign and seal messages and compute the ExportedSessionKey
|
||||||
|
"""
|
||||||
|
if negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||||
|
key_exchange_key = hmac.new(session_base_key, server_challenge + lm_challenge_response[:8]).digest()
|
||||||
|
elif negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_LM_KEY:
|
||||||
|
des_handler = des.DES(lm_hash[:7])
|
||||||
|
first_des = des_handler.encrypt(lm_challenge_response[:8])
|
||||||
|
des_handler = des.DES(lm_hash[7:8] + binascii.unhexlify('bdbdbdbdbdbdbd'))
|
||||||
|
second_des = des_handler.encrypt(lm_challenge_response[:8])
|
||||||
|
|
||||||
|
key_exchange_key = first_des + second_des
|
||||||
|
elif negotiate_flags & NegotiateFlags.NTLMSSP_REQUEST_NON_NT_SESSION_KEY:
|
||||||
|
key_exchange_key = lm_hash[:8] + b'\0' * 8
|
||||||
|
else:
|
||||||
|
key_exchange_key = session_base_key
|
||||||
|
|
||||||
|
return key_exchange_key
|
||||||
|
|
||||||
|
def _get_exchange_key_ntlm_v2(session_base_key):
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
4.3.5.1 KXKEY
|
||||||
|
Calculates the Key Exchange Key for NTLMv2 authentication. Used for signing and sealing messages.
|
||||||
|
According to docs, 'If NTLM v2 is used, KeyExchangeKey MUST be set to the given 128-bit SessionBaseKey
|
||||||
|
|
||||||
|
@param session_base_key: A session key calculated from the user password challenge
|
||||||
|
@return key_exchange_key: The Key Exchange Key (KXKEY) used to sign and seal messages
|
||||||
|
"""
|
||||||
|
return session_base_key
|
||||||
|
|
||||||
|
def get_sign_key(exported_session_key, magic_constant):
|
||||||
|
"""
|
||||||
|
3.4.5.2 SIGNKEY
|
||||||
|
|
||||||
|
@param exported_session_key: A 128-bit session key used to derive signing and sealing keys
|
||||||
|
@param magic_constant: A constant value set in the MS-NLMP documentation (constants.SignSealConstants)
|
||||||
|
@return sign_key: Key used to sign messages
|
||||||
|
"""
|
||||||
|
|
||||||
|
sign_key = hashlib.md5(exported_session_key + magic_constant).digest()
|
||||||
|
|
||||||
|
return sign_key
|
||||||
|
|
||||||
|
def get_seal_key(negotiate_flags, exported_session_key, magic_constant):
|
||||||
|
"""
|
||||||
|
3.4.5.3. SEALKEY
|
||||||
|
Main method to use to calculate the seal_key used to seal (encrypt) messages. This will determine
|
||||||
|
the correct method below to use based on the compatibility flags set and should be called instead
|
||||||
|
of the others
|
||||||
|
|
||||||
|
@param exported_session_key: A 128-bit session key used to derive signing and sealing keys
|
||||||
|
@param negotiate_flags: The negotiate_flags structure sent by the server
|
||||||
|
@param magic_constant: A constant value set in the MS-NLMP documentation (constants.SignSealConstants)
|
||||||
|
@return seal_key: Key used to seal messages
|
||||||
|
"""
|
||||||
|
|
||||||
|
if negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||||
|
seal_key = _get_seal_key_ntlm2(negotiate_flags, exported_session_key, magic_constant)
|
||||||
|
elif negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_LM_KEY:
|
||||||
|
seal_key = _get_seal_key_ntlm1(negotiate_flags, exported_session_key)
|
||||||
|
else:
|
||||||
|
seal_key = exported_session_key
|
||||||
|
|
||||||
|
return seal_key
|
||||||
|
|
||||||
|
def _get_seal_key_ntlm1(negotiate_flags, exported_session_key):
|
||||||
|
"""
|
||||||
|
3.4.5.3 SEALKEY
|
||||||
|
Calculates the seal_key used to seal (encrypt) messages. This for authentication where
|
||||||
|
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY has not been negotiated. Will weaken the keys
|
||||||
|
if NTLMSSP_NEGOTIATE_56 is not negotiated it will default to the 40-bit key
|
||||||
|
|
||||||
|
@param negotiate_flags: The negotiate_flags structure sent by the server
|
||||||
|
@param exported_session_key: A 128-bit session key used to derive signing and sealing keys
|
||||||
|
@return seal_key: Key used to seal messages
|
||||||
|
"""
|
||||||
|
if negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_56:
|
||||||
|
seal_key = exported_session_key[:7] + binascii.unhexlify('a0')
|
||||||
|
else:
|
||||||
|
seal_key = exported_session_key[:5] + binascii.unhexlify('e538b0')
|
||||||
|
|
||||||
|
return seal_key
|
||||||
|
|
||||||
|
def _get_seal_key_ntlm2(negotiate_flags, exported_session_key, magic_constant):
|
||||||
|
"""
|
||||||
|
3.4.5.3 SEALKEY
|
||||||
|
Calculates the seal_key used to seal (encrypt) messages. This for authentication where
|
||||||
|
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY has been negotiated. Will weaken the keys
|
||||||
|
if NTLMSSP_NEGOTIATE_128 is not negotiated, will try NEGOTIATE_56 and then will default
|
||||||
|
to the 40-bit key
|
||||||
|
|
||||||
|
@param negotiate_flags: The negotiate_flags structure sent by the server
|
||||||
|
@param exported_session_key: A 128-bit session key used to derive signing and sealing keys
|
||||||
|
@param magic_constant: A constant value set in the MS-NLMP documentation (constants.SignSealConstants)
|
||||||
|
@return seal_key: Key used to seal messages
|
||||||
|
"""
|
||||||
|
if negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_128:
|
||||||
|
seal_key = exported_session_key
|
||||||
|
elif negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_56:
|
||||||
|
seal_key = exported_session_key[:7]
|
||||||
|
else:
|
||||||
|
seal_key = exported_session_key[:5]
|
||||||
|
|
||||||
|
seal_key = hashlib.md5(seal_key + magic_constant).digest()
|
||||||
|
|
||||||
|
return seal_key
|
397
plugin/packages/wakatime/packages/ntlm_auth/compute_response.py
Normal file
397
plugin/packages/wakatime/packages/ntlm_auth/compute_response.py
Normal file
|
@ -0,0 +1,397 @@
|
||||||
|
# This library is free software: you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation, either
|
||||||
|
# version 3 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
# This library is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import calendar
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
import time
|
||||||
|
import ntlm_auth.compute_hash as comphash
|
||||||
|
import ntlm_auth.compute_keys as compkeys
|
||||||
|
from ntlm_auth import des
|
||||||
|
from ntlm_auth.constants import NegotiateFlags, AvFlags
|
||||||
|
from ntlm_auth.gss_channel_bindings import GssChannelBindingsStruct
|
||||||
|
from ntlm_auth.target_info import TargetInfo
|
||||||
|
|
||||||
|
class ComputeResponse():
|
||||||
|
"""
|
||||||
|
Constructor for the response computations. This class will compute the various
|
||||||
|
nt and lm challenge responses.
|
||||||
|
|
||||||
|
:param user_name: The user name of the user we are trying to authenticate with
|
||||||
|
:param password: The password of the user we are trying to authenticate with
|
||||||
|
:param domain_name: The domain name of the user account we are authenticated with, default is None
|
||||||
|
:param challenge_message: A ChallengeMessage object that was received from the server after the negotiate_message
|
||||||
|
:param ntlm_compatibility: The Lan Manager Compatibility Level, used to determine what NTLM auth version to use, see Ntlm in ntlm.py for more details
|
||||||
|
"""
|
||||||
|
def __init__(self, user_name, password, domain_name, challenge_message, ntlm_compatibility):
|
||||||
|
self._user_name = user_name
|
||||||
|
self._password = password
|
||||||
|
self._domain_name = domain_name
|
||||||
|
self._challenge_message = challenge_message
|
||||||
|
self._negotiate_flags = challenge_message.negotiate_flags
|
||||||
|
self._server_challenge = challenge_message.server_challenge
|
||||||
|
self._server_target_info = challenge_message.target_info
|
||||||
|
self._ntlm_compatibility = ntlm_compatibility
|
||||||
|
self._client_challenge = os.urandom(8)
|
||||||
|
|
||||||
|
def get_lm_challenge_response(self):
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
3.3.1 - NTLM v1 Authentication
|
||||||
|
3.3.2 - NTLM v2 Authentication
|
||||||
|
|
||||||
|
This method returns the LmChallengeResponse key based on the ntlm_compatibility chosen
|
||||||
|
and the target_info supplied by the CHALLENGE_MESSAGE. It is quite different from what
|
||||||
|
is set in the document as it combines the NTLMv1, NTLM2 and NTLMv2 methods into one
|
||||||
|
and calls separate methods based on the ntlm_compatibility flag chosen.
|
||||||
|
|
||||||
|
:return: response (LmChallengeResponse) - The LM response to the server challenge. Computed by the client
|
||||||
|
"""
|
||||||
|
if self._negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY and self._ntlm_compatibility < 3:
|
||||||
|
response = ComputeResponse._get_LMv1_with_session_security_response(self._client_challenge)
|
||||||
|
|
||||||
|
elif 0 <= self._ntlm_compatibility <= 1:
|
||||||
|
response = ComputeResponse._get_LMv1_response(self._password, self._server_challenge)
|
||||||
|
elif self._ntlm_compatibility == 2:
|
||||||
|
# Based on the compatibility level we don't want to use LM responses, ignore the session_base_key as it is returned in nt
|
||||||
|
response, ignore_key = ComputeResponse._get_NTLMv1_response(self._password, self._server_challenge)
|
||||||
|
else:
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 page 45 - 2016-07-14
|
||||||
|
|
||||||
|
3.1.5.12 Client Received a CHALLENGE_MESSAGE from the Server
|
||||||
|
If NTLMv2 authentication is used and the CHALLENGE_MESSAGE TargetInfo field has an MsvAvTimestamp present,
|
||||||
|
the client SHOULD NOT send the LmChallengeResponse and SHOULD send Z(24) instead.
|
||||||
|
"""
|
||||||
|
|
||||||
|
response = ComputeResponse._get_LMv2_response(self._user_name, self._password, self._domain_name,
|
||||||
|
self._server_challenge,
|
||||||
|
self._client_challenge)
|
||||||
|
if self._server_target_info is not None:
|
||||||
|
timestamp = self._server_target_info[TargetInfo.MSV_AV_TIMESTAMP]
|
||||||
|
if timestamp is not None:
|
||||||
|
response = b'\0' * 24
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def get_nt_challenge_response(self, lm_challenge_response, server_certificate_hash):
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
3.3.1 - NTLM v1 Authentication
|
||||||
|
3.3.2 - NTLM v2 Authentication
|
||||||
|
|
||||||
|
This method returns the NtChallengeResponse key based on the ntlm_compatibility chosen
|
||||||
|
and the target_info supplied by the CHALLENGE_MESSAGE. It is quite different from what
|
||||||
|
is set in the document as it combines the NTLMv1, NTLM2 and NTLMv2 methods into one
|
||||||
|
and calls separate methods based on the ntlm_compatibility value chosen.
|
||||||
|
|
||||||
|
:param lm_challenge_response: The LmChallengeResponse calculated beforeand, used to get the key_exchange_key value
|
||||||
|
:param server_certificate_hash: The SHA256 hash of the server certificate (DER encoded) NTLM is authenticated to.
|
||||||
|
Used in Channel Binding Tokens if present, default value is None. See
|
||||||
|
AuthenticateMessage in messages.py for more details
|
||||||
|
:return response: (NtChallengeResponse) - The NT response to the server challenge. Computed by the client
|
||||||
|
:return session_base_key: (SessionBaseKey) - A session key calculated from the user password challenge
|
||||||
|
:return target_info: (AV_PAIR) - The AV_PAIR structure used in the nt_challenge calculations
|
||||||
|
"""
|
||||||
|
if self._negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY and self._ntlm_compatibility < 3:
|
||||||
|
# The compatibility level is less than 3 which means it doesn't support NTLMv2 but we want extended security so use NTLM2 which is different from NTLMv2
|
||||||
|
# [MS-NLMP] - 3.3.1 NTLMv1 Authentication
|
||||||
|
response, session_base_key = ComputeResponse._get_NTLM2_response(self._password, self._server_challenge, self._client_challenge)
|
||||||
|
key_exchange_key = compkeys._get_exchange_key_ntlm_v1(self._negotiate_flags, session_base_key,
|
||||||
|
self._server_challenge, lm_challenge_response,
|
||||||
|
comphash._lmowfv1(self._password))
|
||||||
|
target_info = None
|
||||||
|
|
||||||
|
elif 0 <= self._ntlm_compatibility < 3:
|
||||||
|
response, session_base_key = ComputeResponse._get_NTLMv1_response(self._password, self._server_challenge)
|
||||||
|
key_exchange_key = compkeys._get_exchange_key_ntlm_v1(self._negotiate_flags, session_base_key,
|
||||||
|
self._server_challenge, lm_challenge_response,
|
||||||
|
comphash._lmowfv1(self._password))
|
||||||
|
target_info = None
|
||||||
|
else:
|
||||||
|
if self._server_target_info is None:
|
||||||
|
target_info = TargetInfo()
|
||||||
|
else:
|
||||||
|
target_info = self._server_target_info
|
||||||
|
|
||||||
|
if target_info[TargetInfo.MSV_AV_TIMESTAMP] is None:
|
||||||
|
timestamp = get_windows_timestamp()
|
||||||
|
else:
|
||||||
|
timestamp = target_info[TargetInfo.MSV_AV_TIMESTAMP][1]
|
||||||
|
|
||||||
|
# [MS-NLMP] If the CHALLENGE_MESSAGE TargetInfo field has an MsvAvTimestamp present, the client SHOULD provide a MIC
|
||||||
|
target_info[TargetInfo.MSV_AV_FLAGS] = struct.pack("<L", AvFlags.MIC_PROVIDED)
|
||||||
|
|
||||||
|
if server_certificate_hash != None:
|
||||||
|
channel_bindings_hash = ComputeResponse._get_channel_bindings_value(server_certificate_hash)
|
||||||
|
target_info[TargetInfo.MSV_AV_CHANNEL_BINDINGS] = channel_bindings_hash
|
||||||
|
|
||||||
|
response, session_base_key = ComputeResponse._get_NTLMv2_response(self._user_name, self._password, self._domain_name,
|
||||||
|
self._server_challenge, self._client_challenge, timestamp, target_info)
|
||||||
|
|
||||||
|
key_exchange_key = compkeys._get_exchange_key_ntlm_v2(session_base_key)
|
||||||
|
|
||||||
|
return response, key_exchange_key, target_info
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_LMv1_response(password, server_challenge):
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
2.2.2.3 LM_RESPONSE
|
||||||
|
The LM_RESPONSE structure defines the NTLM v1 authentication LmChallengeResponse
|
||||||
|
in the AUTHENTICATE_MESSAGE. This response is used only when NTLM v1
|
||||||
|
authentication is configured.
|
||||||
|
|
||||||
|
:param password: The password of the user we are trying to authenticate with
|
||||||
|
:param server_challenge: A random 8-byte response generated by the server in the CHALLENGE_MESSAGE
|
||||||
|
:return response: LmChallengeResponse to the server challenge
|
||||||
|
"""
|
||||||
|
lm_hash = comphash._lmowfv1(password)
|
||||||
|
response = ComputeResponse._calc_resp(lm_hash, server_challenge)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_LMv1_with_session_security_response(client_challenge):
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
2.2.2.3 LM_RESPONSE
|
||||||
|
The LM_RESPONSE structure defines the NTLM v1 authentication LmChallengeResponse
|
||||||
|
in the AUTHENTICATE_MESSAGE. This response is used only when NTLM v1
|
||||||
|
authentication is configured and NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY is flages.
|
||||||
|
|
||||||
|
:param client_challenge: A random 8-byte response generated by the client for the AUTHENTICATE_MESSAGE
|
||||||
|
:return response: LmChallengeResponse to the server challenge
|
||||||
|
"""
|
||||||
|
|
||||||
|
response = client_challenge + b'\0' * 16
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_LMv2_response(user_name, password, domain_name, server_challenge, client_challenge):
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
2.2.2.4 LMv2_RESPONSE
|
||||||
|
The LMv2_RESPONSE structure defines the NTLM v2 authentication LmChallengeResponse
|
||||||
|
in the AUTHENTICATE_MESSAGE. This response is used only when NTLM v2
|
||||||
|
authentication is configured.
|
||||||
|
|
||||||
|
:param user_name: The user name of the user we are trying to authenticate with
|
||||||
|
:param password: The password of the user we are trying to authenticate with
|
||||||
|
:param domain_name: The domain name of the user account we are authenticated with
|
||||||
|
:param server_challenge: A random 8-byte response generated by the server in the CHALLENGE_MESSAGE
|
||||||
|
:param client_challenge: A random 8-byte response generated by the client for the AUTHENTICATE_MESSAGE
|
||||||
|
:return response: LmChallengeResponse to the server challenge
|
||||||
|
"""
|
||||||
|
nt_hash = comphash._ntowfv2(user_name, password, domain_name)
|
||||||
|
lm_hash = hmac.new(nt_hash, (server_challenge + client_challenge)).digest()
|
||||||
|
response = lm_hash + client_challenge
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_NTLMv1_response(password, server_challenge):
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
2.2.2.6 NTLM v1 Response: NTLM_RESPONSE
|
||||||
|
The NTLM_RESPONSE strucutre defines the NTLM v1 authentication NtChallengeResponse
|
||||||
|
in the AUTHENTICATE_MESSAGE. This response is only used when NTLM v1 authentication
|
||||||
|
is configured.
|
||||||
|
|
||||||
|
:param password: The password of the user we are trying to authenticate with
|
||||||
|
:param server_challenge: A random 8-byte response generated by the server in the CHALLENGE_MESSAGE
|
||||||
|
:return response: NtChallengeResponse to the server_challenge
|
||||||
|
:return session_base_key: A session key calculated from the user password challenge
|
||||||
|
"""
|
||||||
|
ntlm_hash = comphash._ntowfv1(password)
|
||||||
|
response = ComputeResponse._calc_resp(ntlm_hash, server_challenge)
|
||||||
|
|
||||||
|
session_base_key = hashlib.new('md4', ntlm_hash).digest()
|
||||||
|
|
||||||
|
return response, session_base_key
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_NTLM2_response(password, server_challenge, client_challenge):
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
This name is really misleading as it isn't NTLM v2 authentication rather
|
||||||
|
This authentication is only used when the ntlm_compatibility level is set
|
||||||
|
to a value < 3 (No NTLMv2 auth) but the NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
|
||||||
|
flag is set in the negotiate flags section. The documentation for computing this
|
||||||
|
value is on page 56 under section 3.3.1 NTLM v1 Authentication
|
||||||
|
|
||||||
|
:param password: The password of the user we are trying to authenticate with
|
||||||
|
:param server_challenge: A random 8-byte response generated by the server in the CHALLENGE_MESSAGE
|
||||||
|
:param client_challenge: A random 8-byte response generated by the client for the AUTHENTICATE_MESSAGE
|
||||||
|
:return response: NtChallengeResponse to the server_challenge
|
||||||
|
:return session_base_key: A session key calculated from the user password challenge
|
||||||
|
"""
|
||||||
|
ntlm_hash = comphash._ntowfv1(password)
|
||||||
|
nt_session_hash = hashlib.md5(server_challenge + client_challenge).digest()[:8]
|
||||||
|
response = ComputeResponse._calc_resp(ntlm_hash, nt_session_hash[0:8])
|
||||||
|
|
||||||
|
session_base_key = hashlib.new('md4', ntlm_hash).digest()
|
||||||
|
|
||||||
|
return response, session_base_key
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_NTLMv2_response(user_name, password, domain_name, server_challenge, client_challenge, timestamp, target_info):
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
2.2.2.8 NTLM V2 Response: NTLMv2_RESPONSE
|
||||||
|
The NTLMv2_RESPONSE strucutre defines the NTLMv2 authentication NtChallengeResponse
|
||||||
|
in the AUTHENTICATE_MESSAGE. This response is used only when NTLMv2 authentication
|
||||||
|
is configured.
|
||||||
|
|
||||||
|
The guide on how this is computed is in 3.3.2 NTLM v2 Authentication.
|
||||||
|
|
||||||
|
:param user_name: The user name of the user we are trying to authenticate with
|
||||||
|
:param password: The password of the user we are trying to authenticate with
|
||||||
|
:param domain_name: The domain name of the user account we are authenticated with
|
||||||
|
:param server_challenge: A random 8-byte response generated by the server in the CHALLENGE_MESSAGE
|
||||||
|
:param client_challenge: A random 8-byte response generated by the client for the AUTHENTICATE_MESSAGE
|
||||||
|
:param timestamp: An 8-byte timestamp in windows format, 100 nanoseconds since 1601-01-01
|
||||||
|
:param target_info: The target_info structure from the CHALLENGE_MESSAGE with the CBT attached if required
|
||||||
|
:return response: NtChallengeResponse to the server_challenge
|
||||||
|
:return session_base_key: A session key calculated from the user password challenge
|
||||||
|
"""
|
||||||
|
|
||||||
|
nt_hash = comphash._ntowfv2(user_name, password, domain_name)
|
||||||
|
temp = ComputeResponse._get_NTLMv2_temp(timestamp, client_challenge, target_info)
|
||||||
|
nt_proof_str = hmac.new(nt_hash, (server_challenge + temp)).digest()
|
||||||
|
response = nt_proof_str + temp
|
||||||
|
|
||||||
|
session_base_key = hmac.new(nt_hash, nt_proof_str).digest()
|
||||||
|
|
||||||
|
return response, session_base_key
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_NTLMv2_temp(timestamp, client_challenge, target_info):
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
2.2.2.7 NTLMv2_CLIENT_CHALLENGE - variable length
|
||||||
|
The NTLMv2_CLIENT_CHALLENGE structure defines the client challenge in
|
||||||
|
the AUTHENTICATE_MESSAGE. This structure is used only when NTLM v2
|
||||||
|
authentication is configured and is transported in the NTLMv2_RESPONSE
|
||||||
|
structure.
|
||||||
|
|
||||||
|
The method to create this structure is defined in 3.3.2 NTLMv2 Authentication.
|
||||||
|
In this method this variable is known as the temp value. The target_info variable
|
||||||
|
corresponds to the ServerName variable used in that documentation. This is in
|
||||||
|
reality a lot more than just the ServerName and contains the AV_PAIRS structure
|
||||||
|
we need to transport with the message like Channel Binding tokens and others.
|
||||||
|
By default this will be the target_info returned from the CHALLENGE_MESSAGE plus
|
||||||
|
MSV_AV_CHANNEL_BINDINGS if specified otherwise it is a new target_info set with
|
||||||
|
MSV_AV_TIMESTAMP to the current time.
|
||||||
|
|
||||||
|
:param timestamp: An 8-byte timestamp in windows format, 100 nanoseconds since 1601-01-01
|
||||||
|
:param client_challenge: A random 8-byte response generated by the client for the AUTHENTICATE_MESSAGE
|
||||||
|
:param target_info: The target_info structure from the CHALLENGE_MESSAGE with the CBT attached if required
|
||||||
|
:return temp: The CLIENT_CHALLENGE structure that will be added to the NtChallengeResponse structure
|
||||||
|
"""
|
||||||
|
resp_type = b'\1'
|
||||||
|
hi_resp_type = b'\1'
|
||||||
|
reserved1 = b'\0' * 2
|
||||||
|
reserved2 = b'\0' * 4
|
||||||
|
reserved3 = b'\0' * 4
|
||||||
|
reserved4 = b'\0' * 4 # This byte is not in the structure defined in 2.2.2.7 but is in the computation guide, works with it present
|
||||||
|
|
||||||
|
temp = resp_type + hi_resp_type + reserved1 + \
|
||||||
|
reserved2 + \
|
||||||
|
timestamp + \
|
||||||
|
client_challenge + \
|
||||||
|
reserved3 + \
|
||||||
|
target_info.get_data() + reserved4
|
||||||
|
|
||||||
|
return temp
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _calc_resp(password_hash, server_challenge):
|
||||||
|
"""
|
||||||
|
Generate the LM response given a 16-byte password hash and the challenge
|
||||||
|
from the CHALLENGE_MESSAGE
|
||||||
|
|
||||||
|
:param password_hash: A 16-byte password hash
|
||||||
|
:param server_challenge: A random 8-byte response generated by the server in the CHALLENGE_MESSAGE
|
||||||
|
:return res: A 24-byte buffer to contain the LM response upon return
|
||||||
|
"""
|
||||||
|
|
||||||
|
# padding with zeros to make the hash 21 bytes long
|
||||||
|
password_hash += b'\0' * (21 - len(password_hash))
|
||||||
|
|
||||||
|
res = b''
|
||||||
|
dobj = des.DES(password_hash[0:7])
|
||||||
|
res = res + dobj.encrypt(server_challenge[0:8])
|
||||||
|
|
||||||
|
dobj = des.DES(password_hash[7:14])
|
||||||
|
res = res + dobj.encrypt(server_challenge[0:8])
|
||||||
|
|
||||||
|
dobj = des.DES(password_hash[14:21])
|
||||||
|
res = res + dobj.encrypt(server_challenge[0:8])
|
||||||
|
return res
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_channel_bindings_value(server_certificate_hash):
|
||||||
|
"""
|
||||||
|
https://msdn.microsoft.com/en-us/library/windows/desktop/dd919963%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
|
||||||
|
https://blogs.msdn.microsoft.com/openspecification/2013/03/26/ntlm-and-channel-binding-hash-aka-extended-protection-for-authentication/
|
||||||
|
|
||||||
|
Get's the MD5 hash of the gss_channel_bindings_struct to add to the AV_PAIR MSV_AV_CHANNEL_BINDINGS.
|
||||||
|
This method takes in the SHA256 hash (Hash of the DER encoded certificate of the server we are connecting to)
|
||||||
|
and add's it to the gss_channel_bindings_struct. It then gets the MD5 hash and converts this to a
|
||||||
|
byte array in preparation of adding it to the AV_PAIR structure.
|
||||||
|
|
||||||
|
:param server_certificate_hash: The SHA256 hash of the server certificate (DER encoded) NTLM is authenticated to
|
||||||
|
:return channel_bindings: An MD5 hash of the gss_channel_bindings_struct to add to the AV_PAIR MsvChannelBindings
|
||||||
|
"""
|
||||||
|
# Channel Binding Tokens support, used for NTLMv2
|
||||||
|
# Decode the SHA256 certificate hash
|
||||||
|
certificate_digest = base64.b16decode(server_certificate_hash)
|
||||||
|
|
||||||
|
# Initialise the GssChannelBindingsStruct and add the certificate_digest to the application_data field
|
||||||
|
gss_channel_bindings = GssChannelBindingsStruct()
|
||||||
|
gss_channel_bindings[gss_channel_bindings.APPLICATION_DATA] = 'tls-server-end-point:'.encode() + certificate_digest
|
||||||
|
|
||||||
|
# Get the gss_channel_bindings_struct and create an MD5 hash
|
||||||
|
channel_bindings_struct_data = gss_channel_bindings.get_data()
|
||||||
|
channel_bindings_hash = hashlib.md5(channel_bindings_struct_data).hexdigest()
|
||||||
|
|
||||||
|
try:
|
||||||
|
cbt_value = bytearray.fromhex(channel_bindings_hash)
|
||||||
|
except TypeError:
|
||||||
|
# Work-around for Python 2.6 bug
|
||||||
|
cbt_value = bytearray.fromhex(unicode(channel_bindings_hash))
|
||||||
|
|
||||||
|
channel_bindings = bytes(cbt_value)
|
||||||
|
return channel_bindings
|
||||||
|
|
||||||
|
|
||||||
|
def get_windows_timestamp():
|
||||||
|
# Get Windows Date time, 100 nanoseconds since 1601-01-01 in a 64 bit structure
|
||||||
|
timestamp = struct.pack('<q', (116444736000000000 + calendar.timegm(time.gmtime()) * 10000000))
|
||||||
|
|
||||||
|
return timestamp
|
92
plugin/packages/wakatime/packages/ntlm_auth/constants.py
Normal file
92
plugin/packages/wakatime/packages/ntlm_auth/constants.py
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
# This library is free software: you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation, either
|
||||||
|
# version 3 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
# This library is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||||
|
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
2.2 Message Syntax
|
||||||
|
The signature field used in NTLM messages
|
||||||
|
"""
|
||||||
|
NTLM_SIGNATURE = b'NTLMSSP\0'
|
||||||
|
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
2.2 Message Syntax
|
||||||
|
The 3 message type options you can have in a message.
|
||||||
|
"""
|
||||||
|
class MessageTypes(object):
|
||||||
|
NTLM_NEGOTIATE = 0x1
|
||||||
|
NTLM_CHALLENGE = 0x2
|
||||||
|
NTLM_AUTHENTICATE = 0x3
|
||||||
|
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
2.2.2.1 AV_PAIR (MsvAvFlags)
|
||||||
|
A 32-bit value indicated server or client configuration
|
||||||
|
"""
|
||||||
|
class AvFlags(object):
|
||||||
|
AUTHENTICATION_CONSTRAINED = 0x1
|
||||||
|
MIC_PROVIDED = 0x2
|
||||||
|
UNTRUSTED_SPN_SOURCE = 0x4
|
||||||
|
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
2.2.2.5 NEGOTIATE
|
||||||
|
During NTLM authentication, each of the following flags is a possible value of the
|
||||||
|
NegotiateFlags field of the NEGOTIATE_MESSAGE, CHALLENGE_MESSAGE and AUTHENTICATE_MESSAGE,
|
||||||
|
unless otherwise noted. These flags define client or server NTLM capabilities
|
||||||
|
supported by the sender.
|
||||||
|
"""
|
||||||
|
class NegotiateFlags(object):
|
||||||
|
NTLMSSP_NEGOTIATE_56 = 0x80000000
|
||||||
|
NTLMSSP_NEGOTIATE_KEY_EXCH = 0x40000000
|
||||||
|
NTLMSSP_NEGOTIATE_128 = 0x20000000
|
||||||
|
NTLMSSP_RESERVED_R1 = 0x10000000
|
||||||
|
NTLMSSP_RESERVED_R2 = 0x08000000
|
||||||
|
NTLMSSP_RESERVED_R3 = 0x04000000
|
||||||
|
NTLMSSP_NEGOTIATE_VERSION = 0x02000000
|
||||||
|
NTLMSSP_RESERVED_R4 = 0x01000000
|
||||||
|
NTLMSSP_NEGOTIATE_TARGET_INFO = 0x00800000
|
||||||
|
NTLMSSP_REQUEST_NON_NT_SESSION_KEY = 0x00400000
|
||||||
|
NTLMSSP_RESERVED_R5 = 0x00200000
|
||||||
|
NTLMSSP_NEGOTIATE_IDENTITY = 0x00100000
|
||||||
|
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY = 0x00080000
|
||||||
|
NTLMSSP_RESERVED_R6 = 0x00040000
|
||||||
|
NTLMSSP_TARGET_TYPE_SERVER = 0x00020000
|
||||||
|
NTLMSSP_TARGET_TYPE_DOMAIN = 0x00010000
|
||||||
|
NTLMSSP_NEGOTIATE_ALWAYS_SIGN = 0x00008000
|
||||||
|
NTLMSSP_RESERVED_R7 = 0x00004000
|
||||||
|
NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED = 0x00002000
|
||||||
|
NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED = 0x00001000
|
||||||
|
NTLMSSP_ANOYNMOUS = 0x00000800
|
||||||
|
NTLMSSP_RESERVED_R8 = 0x00000400
|
||||||
|
NTLMSSP_NEGOTIATE_NTLM = 0x00000200
|
||||||
|
NTLMSSP_RESERVED_R9 = 0x00000100
|
||||||
|
NTLMSSP_NEGOTIATE_LM_KEY = 0x00000080
|
||||||
|
NTLMSSP_NEGOTIATE_DATAGRAM = 0x00000040
|
||||||
|
NTLMSSP_NEGOTIATE_SEAL = 0x00000020
|
||||||
|
NTLMSSP_NEGOTIATE_SIGN = 0x00000010
|
||||||
|
NTLMSSP_RESERVED_R10 = 0x00000008
|
||||||
|
NTLMSSP_REQUEST_TARGET = 0x00000004
|
||||||
|
NTLMSSP_NEGOTIATE_OEM = 0x00000002
|
||||||
|
NTLMSSP_NEGOTIATE_UNICODE = 0x00000001
|
||||||
|
|
||||||
|
class SignSealConstants(object):
|
||||||
|
# Magic Contants used to get the signing and sealing key for Extended Session Security
|
||||||
|
CLIENT_SIGNING = b"session key to client-to-server signing key magic constant\0"
|
||||||
|
SERVER_SIGNING = b"session key to server-to-client signing key magic constant\0"
|
||||||
|
CLIENT_SEALING = b"session key to client-to-server sealing key magic constant\0"
|
||||||
|
SERVER_SEALING = b"session key to server-to-client sealing key magic constant\0"
|
88
plugin/packages/wakatime/packages/ntlm_auth/des.py
Normal file
88
plugin/packages/wakatime/packages/ntlm_auth/des.py
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
# This file is part of 'NTLM Authorization Proxy Server' http://sourceforge.net/projects/ntlmaps/
|
||||||
|
# Copyright 2001 Dmitry A. Rozmanov <dima@xenon.spb.ru>
|
||||||
|
#
|
||||||
|
# This library is free software: you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation, either
|
||||||
|
# version 3 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
# This library is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||||
|
import logging
|
||||||
|
import six
|
||||||
|
from ntlm_auth import des_c
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DES:
|
||||||
|
des_c_obj = None
|
||||||
|
|
||||||
|
def __init__(self, key_str):
|
||||||
|
k = str_to_key56(key_str)
|
||||||
|
k = key56_to_key64(k)
|
||||||
|
|
||||||
|
key_str = b''
|
||||||
|
for i in k:
|
||||||
|
key_str += six.int2byte(i & 0xFF)
|
||||||
|
|
||||||
|
self.des_c_obj = des_c.DES(key_str)
|
||||||
|
|
||||||
|
def encrypt(self, plain_text):
|
||||||
|
return self.des_c_obj.encrypt(plain_text)
|
||||||
|
|
||||||
|
def decrypt(self, crypted_text):
|
||||||
|
return self.des_c_obj.decrypt(crypted_text)
|
||||||
|
|
||||||
|
|
||||||
|
DESException = 'DESException'
|
||||||
|
|
||||||
|
|
||||||
|
def str_to_key56(key_str):
|
||||||
|
|
||||||
|
if not type(key_str) == six.binary_type:
|
||||||
|
# TODO rsanders high - figure out how to make this not necessary
|
||||||
|
key_str = key_str.encode('ascii')
|
||||||
|
|
||||||
|
if len(key_str) < 7:
|
||||||
|
key_str = key_str + b'\000\000\000\000\000\000\000'[:(7 - len(key_str))]
|
||||||
|
key_56 = []
|
||||||
|
for i in six.iterbytes(key_str[:7]):
|
||||||
|
key_56.append(i)
|
||||||
|
|
||||||
|
return key_56
|
||||||
|
|
||||||
|
|
||||||
|
def key56_to_key64(key_56):
|
||||||
|
key = []
|
||||||
|
for i in range(8):
|
||||||
|
key.append(0)
|
||||||
|
|
||||||
|
key[0] = key_56[0]
|
||||||
|
key[1] = ((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1)
|
||||||
|
key[2] = ((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2)
|
||||||
|
key[3] = ((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3)
|
||||||
|
key[4] = ((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4)
|
||||||
|
key[5] = ((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5)
|
||||||
|
key[6] = ((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6)
|
||||||
|
key[7] = (key_56[6] << 1) & 0xFF
|
||||||
|
|
||||||
|
key = set_key_odd_parity(key)
|
||||||
|
|
||||||
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
def set_key_odd_parity(key):
|
||||||
|
for i in range(len(key)):
|
||||||
|
for k in range(7):
|
||||||
|
bit = 0
|
||||||
|
t = key[i] >> k
|
||||||
|
bit = (t ^ bit) & 0x1
|
||||||
|
key[i] = (key[i] & 0xFE) | bit
|
||||||
|
|
||||||
|
return key
|
254
plugin/packages/wakatime/packages/ntlm_auth/des_c.py
Normal file
254
plugin/packages/wakatime/packages/ntlm_auth/des_c.py
Normal file
|
@ -0,0 +1,254 @@
|
||||||
|
# This file is part of 'NTLM Authorization Proxy Server' http://sourceforge.net/projects/ntlmaps/
|
||||||
|
# Copyright 2001 Dmitry A. Rozmanov <dima@xenon.spb.ru>
|
||||||
|
#
|
||||||
|
# This library is free software: you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation, either
|
||||||
|
# version 3 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
# This library is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||||
|
import six
|
||||||
|
|
||||||
|
from ntlm_auth.U32 import U32
|
||||||
|
from ntlm_auth.des_data import des_SPtrans, des_skb
|
||||||
|
|
||||||
|
def c2l(c):
|
||||||
|
"char[4] to unsigned long"
|
||||||
|
l = U32(c[0])
|
||||||
|
l = l | (U32(c[1]) << 8)
|
||||||
|
l = l | (U32(c[2]) << 16)
|
||||||
|
l = l | (U32(c[3]) << 24)
|
||||||
|
return l
|
||||||
|
|
||||||
|
|
||||||
|
def l2c(l):
|
||||||
|
"unsigned long to char[4]"
|
||||||
|
c = []
|
||||||
|
c.append(int(l & U32(0xFF)))
|
||||||
|
c.append(int((l >> 8) & U32(0xFF)))
|
||||||
|
c.append(int((l >> 16) & U32(0xFF)))
|
||||||
|
c.append(int((l >> 24) & U32(0xFF)))
|
||||||
|
return c
|
||||||
|
|
||||||
|
|
||||||
|
def D_ENCRYPT(tup, u, t, s):
|
||||||
|
L, R, S = tup
|
||||||
|
# print 'LRS1', L, R, S, u, t, '-->',
|
||||||
|
u = (R ^ s[S])
|
||||||
|
t = R ^ s[S + 1]
|
||||||
|
t = ((t >> 4) + (t << 28))
|
||||||
|
L = L ^ (des_SPtrans[1][int((t) & U32(0x3f))] |
|
||||||
|
des_SPtrans[3][int((t >> 8) & U32(0x3f))] |
|
||||||
|
des_SPtrans[5][int((t >> 16) & U32(0x3f))] |
|
||||||
|
des_SPtrans[7][int((t >> 24) & U32(0x3f))] |
|
||||||
|
des_SPtrans[0][int((u) & U32(0x3f))] |
|
||||||
|
des_SPtrans[2][int((u >> 8) & U32(0x3f))] |
|
||||||
|
des_SPtrans[4][int((u >> 16) & U32(0x3f))] |
|
||||||
|
des_SPtrans[6][int((u >> 24) & U32(0x3f))])
|
||||||
|
# print 'LRS:', L, R, S, u, t
|
||||||
|
return (L, R, S), u, t, s
|
||||||
|
|
||||||
|
|
||||||
|
def PERM_OP(tup, n, m):
|
||||||
|
"tup - (a, b, t)"
|
||||||
|
a, b, t = tup
|
||||||
|
t = ((a >> n) ^ b) & m
|
||||||
|
b = b ^ t
|
||||||
|
a = a ^ (t << n)
|
||||||
|
return (a, b, t)
|
||||||
|
|
||||||
|
|
||||||
|
def HPERM_OP(tup, n, m):
|
||||||
|
"tup - (a, t)"
|
||||||
|
a, t = tup
|
||||||
|
t = ((a << (16 - n)) ^ a) & m
|
||||||
|
a = a ^ t ^ (t >> (16 - n))
|
||||||
|
return a, t
|
||||||
|
|
||||||
|
|
||||||
|
shifts2 = [0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0]
|
||||||
|
|
||||||
|
|
||||||
|
class DES:
|
||||||
|
KeySched = None # des_key_schedule
|
||||||
|
|
||||||
|
def __init__(self, key_str):
|
||||||
|
self.KeySched = des_set_key(key_str)
|
||||||
|
|
||||||
|
def decrypt(self, str):
|
||||||
|
# block - UChar[]
|
||||||
|
block = []
|
||||||
|
|
||||||
|
for i in six.iterbytes(str):
|
||||||
|
block.append(i)
|
||||||
|
|
||||||
|
# print block
|
||||||
|
block = des_ecb_encrypt(block, self.KeySched, 0)
|
||||||
|
|
||||||
|
res = b''
|
||||||
|
for i in block:
|
||||||
|
res = res + six.int2byte(i)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
def encrypt(self, plaintext):
|
||||||
|
# block - UChar[]
|
||||||
|
|
||||||
|
block = []
|
||||||
|
for i in plaintext:
|
||||||
|
block.append(i)
|
||||||
|
|
||||||
|
block = des_ecb_encrypt(block, self.KeySched, 1)
|
||||||
|
|
||||||
|
res = b''
|
||||||
|
|
||||||
|
for i in block:
|
||||||
|
res += six.int2byte(i)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
def des_encript(input, ks, encrypt):
|
||||||
|
# input - U32[]
|
||||||
|
# output - U32[]
|
||||||
|
# ks - des_key_shedule - U32[2][16]
|
||||||
|
# encrypt - int
|
||||||
|
# l, r, t, u - U32
|
||||||
|
# i - int
|
||||||
|
# s - U32[]
|
||||||
|
|
||||||
|
l = input[0]
|
||||||
|
r = input[1]
|
||||||
|
t = U32(0)
|
||||||
|
u = U32(0)
|
||||||
|
|
||||||
|
r, l, t = PERM_OP((r, l, t), 4, U32(0x0f0f0f0f))
|
||||||
|
l, r, t = PERM_OP((l, r, t), 16, U32(0x0000ffff))
|
||||||
|
r, l, t = PERM_OP((r, l, t), 2, U32(0x33333333))
|
||||||
|
l, r, t = PERM_OP((l, r, t), 8, U32(0x00ff00ff))
|
||||||
|
r, l, t = PERM_OP((r, l, t), 1, U32(0x55555555))
|
||||||
|
|
||||||
|
t = (r << 1) | (r >> 31)
|
||||||
|
r = (l << 1) | (l >> 31)
|
||||||
|
l = t
|
||||||
|
|
||||||
|
s = ks # ???????????????
|
||||||
|
# print l, r
|
||||||
|
if encrypt:
|
||||||
|
for i in range(0, 32, 4):
|
||||||
|
rtup, u, t, s = D_ENCRYPT((l, r, i + 0), u, t, s)
|
||||||
|
l = rtup[0]
|
||||||
|
r = rtup[1]
|
||||||
|
rtup, u, t, s = D_ENCRYPT((r, l, i + 2), u, t, s)
|
||||||
|
r = rtup[0]
|
||||||
|
l = rtup[1]
|
||||||
|
else:
|
||||||
|
for i in range(30, 0, -4):
|
||||||
|
rtup, u, t, s = D_ENCRYPT((l, r, i - 0), u, t, s)
|
||||||
|
l = rtup[0]
|
||||||
|
r = rtup[1]
|
||||||
|
rtup, u, t, s = D_ENCRYPT((r, l, i - 2), u, t, s)
|
||||||
|
r = rtup[0]
|
||||||
|
l = rtup[1]
|
||||||
|
# print l, r
|
||||||
|
l = (l >> 1) | (l << 31)
|
||||||
|
r = (r >> 1) | (r << 31)
|
||||||
|
|
||||||
|
r, l, t = PERM_OP((r, l, t), 1, U32(0x55555555))
|
||||||
|
l, r, t = PERM_OP((l, r, t), 8, U32(0x00ff00ff))
|
||||||
|
r, l, t = PERM_OP((r, l, t), 2, U32(0x33333333))
|
||||||
|
l, r, t = PERM_OP((l, r, t), 16, U32(0x0000ffff))
|
||||||
|
r, l, t = PERM_OP((r, l, t), 4, U32(0x0f0f0f0f))
|
||||||
|
|
||||||
|
output = [l]
|
||||||
|
output.append(r)
|
||||||
|
l, r, t, u = U32(0), U32(0), U32(0), U32(0)
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def des_ecb_encrypt(input, ks, encrypt):
|
||||||
|
# input - des_cblock - UChar[8]
|
||||||
|
# output - des_cblock - UChar[8]
|
||||||
|
# ks - des_key_shedule - U32[2][16]
|
||||||
|
# encrypt - int
|
||||||
|
|
||||||
|
# print input
|
||||||
|
l0 = c2l(input[0:4])
|
||||||
|
l1 = c2l(input[4:8])
|
||||||
|
ll = [l0]
|
||||||
|
ll.append(l1)
|
||||||
|
# print ll
|
||||||
|
ll = des_encript(ll, ks, encrypt)
|
||||||
|
# print ll
|
||||||
|
l0 = ll[0]
|
||||||
|
l1 = ll[1]
|
||||||
|
output = l2c(l0)
|
||||||
|
output = output + l2c(l1)
|
||||||
|
# print output
|
||||||
|
l0, l1, ll[0], ll[1] = U32(0), U32(0), U32(0), U32(0)
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def des_set_key(key):
|
||||||
|
# key - des_cblock - UChar[8]
|
||||||
|
# schedule - des_key_schedule
|
||||||
|
|
||||||
|
# register unsigned long c,d,t,s;
|
||||||
|
# register unsigned char *in;
|
||||||
|
# register unsigned long *k;
|
||||||
|
# register int i;
|
||||||
|
|
||||||
|
# k = schedule
|
||||||
|
# in = key
|
||||||
|
|
||||||
|
k = []
|
||||||
|
c = c2l(key[0:4])
|
||||||
|
d = c2l(key[4:8])
|
||||||
|
t = U32(0)
|
||||||
|
|
||||||
|
d, c, t = PERM_OP((d, c, t), 4, U32(0x0f0f0f0f))
|
||||||
|
c, t = HPERM_OP((c, t), -2, U32(0xcccc0000))
|
||||||
|
d, t = HPERM_OP((d, t), -2, U32(0xcccc0000))
|
||||||
|
d, c, t = PERM_OP((d, c, t), 1, U32(0x55555555))
|
||||||
|
c, d, t = PERM_OP((c, d, t), 8, U32(0x00ff00ff))
|
||||||
|
d, c, t = PERM_OP((d, c, t), 1, U32(0x55555555))
|
||||||
|
|
||||||
|
d = (((d & U32(0x000000ff)) << 16) | (d & U32(0x0000ff00)) | ((d & U32(0x00ff0000)) >> 16) | (
|
||||||
|
(c & U32(0xf0000000)) >> 4))
|
||||||
|
c = c & U32(0x0fffffff)
|
||||||
|
|
||||||
|
for i in range(16):
|
||||||
|
if (shifts2[i]):
|
||||||
|
c = ((c >> 2) | (c << 26))
|
||||||
|
d = ((d >> 2) | (d << 26))
|
||||||
|
else:
|
||||||
|
c = ((c >> 1) | (c << 27))
|
||||||
|
d = ((d >> 1) | (d << 27))
|
||||||
|
c = c & U32(0x0fffffff)
|
||||||
|
d = d & U32(0x0fffffff)
|
||||||
|
|
||||||
|
s = des_skb[0][int((c) & U32(0x3f))] | \
|
||||||
|
des_skb[1][int(((c >> 6) & U32(0x03)) | ((c >> 7) & U32(0x3c)))] | \
|
||||||
|
des_skb[2][int(((c >> 13) & U32(0x0f)) | ((c >> 14) & U32(0x30)))] | \
|
||||||
|
des_skb[3][int(((c >> 20) & U32(0x01)) | ((c >> 21) & U32(0x06)) | ((c >> 22) & U32(0x38)))]
|
||||||
|
|
||||||
|
t = des_skb[4][int((d) & U32(0x3f))] | \
|
||||||
|
des_skb[5][int(((d >> 7) & U32(0x03)) | ((d >> 8) & U32(0x3c)))] | \
|
||||||
|
des_skb[6][int((d >> 15) & U32(0x3f))] | \
|
||||||
|
des_skb[7][int(((d >> 21) & U32(0x0f)) | ((d >> 22) & U32(0x30)))]
|
||||||
|
# print s, t
|
||||||
|
|
||||||
|
k.append(((t << 16) | (s & U32(0x0000ffff))) & U32(0xffffffff))
|
||||||
|
s = ((s >> 16) | (t & U32(0xffff0000)))
|
||||||
|
s = (s << 4) | (s >> 28)
|
||||||
|
k.append(s & U32(0xffffffff))
|
||||||
|
|
||||||
|
schedule = k
|
||||||
|
|
||||||
|
return schedule
|
348
plugin/packages/wakatime/packages/ntlm_auth/des_data.py
Normal file
348
plugin/packages/wakatime/packages/ntlm_auth/des_data.py
Normal file
|
@ -0,0 +1,348 @@
|
||||||
|
# This file is part of 'NTLM Authorization Proxy Server' http://sourceforge.net/projects/ntlmaps/
|
||||||
|
# Copyright 2001 Dmitry A. Rozmanov <dima@xenon.spb.ru>
|
||||||
|
#
|
||||||
|
# This library is free software: you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation, either
|
||||||
|
# version 3 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
# This library is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||||
|
|
||||||
|
from ntlm_auth.U32 import U32
|
||||||
|
|
||||||
|
# static unsigned long des_SPtrans[8][64]={
|
||||||
|
|
||||||
|
des_SPtrans = \
|
||||||
|
[
|
||||||
|
# nibble 0
|
||||||
|
[
|
||||||
|
U32(0x00820200), U32(0x00020000), U32(0x80800000), U32(0x80820200),
|
||||||
|
U32(0x00800000), U32(0x80020200), U32(0x80020000), U32(0x80800000),
|
||||||
|
U32(0x80020200), U32(0x00820200), U32(0x00820000), U32(0x80000200),
|
||||||
|
U32(0x80800200), U32(0x00800000), U32(0x00000000), U32(0x80020000),
|
||||||
|
U32(0x00020000), U32(0x80000000), U32(0x00800200), U32(0x00020200),
|
||||||
|
U32(0x80820200), U32(0x00820000), U32(0x80000200), U32(0x00800200),
|
||||||
|
U32(0x80000000), U32(0x00000200), U32(0x00020200), U32(0x80820000),
|
||||||
|
U32(0x00000200), U32(0x80800200), U32(0x80820000), U32(0x00000000),
|
||||||
|
U32(0x00000000), U32(0x80820200), U32(0x00800200), U32(0x80020000),
|
||||||
|
U32(0x00820200), U32(0x00020000), U32(0x80000200), U32(0x00800200),
|
||||||
|
U32(0x80820000), U32(0x00000200), U32(0x00020200), U32(0x80800000),
|
||||||
|
U32(0x80020200), U32(0x80000000), U32(0x80800000), U32(0x00820000),
|
||||||
|
U32(0x80820200), U32(0x00020200), U32(0x00820000), U32(0x80800200),
|
||||||
|
U32(0x00800000), U32(0x80000200), U32(0x80020000), U32(0x00000000),
|
||||||
|
U32(0x00020000), U32(0x00800000), U32(0x80800200), U32(0x00820200),
|
||||||
|
U32(0x80000000), U32(0x80820000), U32(0x00000200), U32(0x80020200),
|
||||||
|
],
|
||||||
|
|
||||||
|
# nibble 1
|
||||||
|
[
|
||||||
|
U32(0x10042004), U32(0x00000000), U32(0x00042000), U32(0x10040000),
|
||||||
|
U32(0x10000004), U32(0x00002004), U32(0x10002000), U32(0x00042000),
|
||||||
|
U32(0x00002000), U32(0x10040004), U32(0x00000004), U32(0x10002000),
|
||||||
|
U32(0x00040004), U32(0x10042000), U32(0x10040000), U32(0x00000004),
|
||||||
|
U32(0x00040000), U32(0x10002004), U32(0x10040004), U32(0x00002000),
|
||||||
|
U32(0x00042004), U32(0x10000000), U32(0x00000000), U32(0x00040004),
|
||||||
|
U32(0x10002004), U32(0x00042004), U32(0x10042000), U32(0x10000004),
|
||||||
|
U32(0x10000000), U32(0x00040000), U32(0x00002004), U32(0x10042004),
|
||||||
|
U32(0x00040004), U32(0x10042000), U32(0x10002000), U32(0x00042004),
|
||||||
|
U32(0x10042004), U32(0x00040004), U32(0x10000004), U32(0x00000000),
|
||||||
|
U32(0x10000000), U32(0x00002004), U32(0x00040000), U32(0x10040004),
|
||||||
|
U32(0x00002000), U32(0x10000000), U32(0x00042004), U32(0x10002004),
|
||||||
|
U32(0x10042000), U32(0x00002000), U32(0x00000000), U32(0x10000004),
|
||||||
|
U32(0x00000004), U32(0x10042004), U32(0x00042000), U32(0x10040000),
|
||||||
|
U32(0x10040004), U32(0x00040000), U32(0x00002004), U32(0x10002000),
|
||||||
|
U32(0x10002004), U32(0x00000004), U32(0x10040000), U32(0x00042000),
|
||||||
|
],
|
||||||
|
|
||||||
|
# nibble 2
|
||||||
|
[
|
||||||
|
U32(0x41000000), U32(0x01010040), U32(0x00000040), U32(0x41000040),
|
||||||
|
U32(0x40010000), U32(0x01000000), U32(0x41000040), U32(0x00010040),
|
||||||
|
U32(0x01000040), U32(0x00010000), U32(0x01010000), U32(0x40000000),
|
||||||
|
U32(0x41010040), U32(0x40000040), U32(0x40000000), U32(0x41010000),
|
||||||
|
U32(0x00000000), U32(0x40010000), U32(0x01010040), U32(0x00000040),
|
||||||
|
U32(0x40000040), U32(0x41010040), U32(0x00010000), U32(0x41000000),
|
||||||
|
U32(0x41010000), U32(0x01000040), U32(0x40010040), U32(0x01010000),
|
||||||
|
U32(0x00010040), U32(0x00000000), U32(0x01000000), U32(0x40010040),
|
||||||
|
U32(0x01010040), U32(0x00000040), U32(0x40000000), U32(0x00010000),
|
||||||
|
U32(0x40000040), U32(0x40010000), U32(0x01010000), U32(0x41000040),
|
||||||
|
U32(0x00000000), U32(0x01010040), U32(0x00010040), U32(0x41010000),
|
||||||
|
U32(0x40010000), U32(0x01000000), U32(0x41010040), U32(0x40000000),
|
||||||
|
U32(0x40010040), U32(0x41000000), U32(0x01000000), U32(0x41010040),
|
||||||
|
U32(0x00010000), U32(0x01000040), U32(0x41000040), U32(0x00010040),
|
||||||
|
U32(0x01000040), U32(0x00000000), U32(0x41010000), U32(0x40000040),
|
||||||
|
U32(0x41000000), U32(0x40010040), U32(0x00000040), U32(0x01010000),
|
||||||
|
],
|
||||||
|
|
||||||
|
# nibble 3
|
||||||
|
[
|
||||||
|
U32(0x00100402), U32(0x04000400), U32(0x00000002), U32(0x04100402),
|
||||||
|
U32(0x00000000), U32(0x04100000), U32(0x04000402), U32(0x00100002),
|
||||||
|
U32(0x04100400), U32(0x04000002), U32(0x04000000), U32(0x00000402),
|
||||||
|
U32(0x04000002), U32(0x00100402), U32(0x00100000), U32(0x04000000),
|
||||||
|
U32(0x04100002), U32(0x00100400), U32(0x00000400), U32(0x00000002),
|
||||||
|
U32(0x00100400), U32(0x04000402), U32(0x04100000), U32(0x00000400),
|
||||||
|
U32(0x00000402), U32(0x00000000), U32(0x00100002), U32(0x04100400),
|
||||||
|
U32(0x04000400), U32(0x04100002), U32(0x04100402), U32(0x00100000),
|
||||||
|
U32(0x04100002), U32(0x00000402), U32(0x00100000), U32(0x04000002),
|
||||||
|
U32(0x00100400), U32(0x04000400), U32(0x00000002), U32(0x04100000),
|
||||||
|
U32(0x04000402), U32(0x00000000), U32(0x00000400), U32(0x00100002),
|
||||||
|
U32(0x00000000), U32(0x04100002), U32(0x04100400), U32(0x00000400),
|
||||||
|
U32(0x04000000), U32(0x04100402), U32(0x00100402), U32(0x00100000),
|
||||||
|
U32(0x04100402), U32(0x00000002), U32(0x04000400), U32(0x00100402),
|
||||||
|
U32(0x00100002), U32(0x00100400), U32(0x04100000), U32(0x04000402),
|
||||||
|
U32(0x00000402), U32(0x04000000), U32(0x04000002), U32(0x04100400),
|
||||||
|
],
|
||||||
|
|
||||||
|
# nibble 4
|
||||||
|
[
|
||||||
|
U32(0x02000000), U32(0x00004000), U32(0x00000100), U32(0x02004108),
|
||||||
|
U32(0x02004008), U32(0x02000100), U32(0x00004108), U32(0x02004000),
|
||||||
|
U32(0x00004000), U32(0x00000008), U32(0x02000008), U32(0x00004100),
|
||||||
|
U32(0x02000108), U32(0x02004008), U32(0x02004100), U32(0x00000000),
|
||||||
|
U32(0x00004100), U32(0x02000000), U32(0x00004008), U32(0x00000108),
|
||||||
|
U32(0x02000100), U32(0x00004108), U32(0x00000000), U32(0x02000008),
|
||||||
|
U32(0x00000008), U32(0x02000108), U32(0x02004108), U32(0x00004008),
|
||||||
|
U32(0x02004000), U32(0x00000100), U32(0x00000108), U32(0x02004100),
|
||||||
|
U32(0x02004100), U32(0x02000108), U32(0x00004008), U32(0x02004000),
|
||||||
|
U32(0x00004000), U32(0x00000008), U32(0x02000008), U32(0x02000100),
|
||||||
|
U32(0x02000000), U32(0x00004100), U32(0x02004108), U32(0x00000000),
|
||||||
|
U32(0x00004108), U32(0x02000000), U32(0x00000100), U32(0x00004008),
|
||||||
|
U32(0x02000108), U32(0x00000100), U32(0x00000000), U32(0x02004108),
|
||||||
|
U32(0x02004008), U32(0x02004100), U32(0x00000108), U32(0x00004000),
|
||||||
|
U32(0x00004100), U32(0x02004008), U32(0x02000100), U32(0x00000108),
|
||||||
|
U32(0x00000008), U32(0x00004108), U32(0x02004000), U32(0x02000008),
|
||||||
|
],
|
||||||
|
|
||||||
|
# nibble 5
|
||||||
|
[
|
||||||
|
U32(0x20000010), U32(0x00080010), U32(0x00000000), U32(0x20080800),
|
||||||
|
U32(0x00080010), U32(0x00000800), U32(0x20000810), U32(0x00080000),
|
||||||
|
U32(0x00000810), U32(0x20080810), U32(0x00080800), U32(0x20000000),
|
||||||
|
U32(0x20000800), U32(0x20000010), U32(0x20080000), U32(0x00080810),
|
||||||
|
U32(0x00080000), U32(0x20000810), U32(0x20080010), U32(0x00000000),
|
||||||
|
U32(0x00000800), U32(0x00000010), U32(0x20080800), U32(0x20080010),
|
||||||
|
U32(0x20080810), U32(0x20080000), U32(0x20000000), U32(0x00000810),
|
||||||
|
U32(0x00000010), U32(0x00080800), U32(0x00080810), U32(0x20000800),
|
||||||
|
U32(0x00000810), U32(0x20000000), U32(0x20000800), U32(0x00080810),
|
||||||
|
U32(0x20080800), U32(0x00080010), U32(0x00000000), U32(0x20000800),
|
||||||
|
U32(0x20000000), U32(0x00000800), U32(0x20080010), U32(0x00080000),
|
||||||
|
U32(0x00080010), U32(0x20080810), U32(0x00080800), U32(0x00000010),
|
||||||
|
U32(0x20080810), U32(0x00080800), U32(0x00080000), U32(0x20000810),
|
||||||
|
U32(0x20000010), U32(0x20080000), U32(0x00080810), U32(0x00000000),
|
||||||
|
U32(0x00000800), U32(0x20000010), U32(0x20000810), U32(0x20080800),
|
||||||
|
U32(0x20080000), U32(0x00000810), U32(0x00000010), U32(0x20080010),
|
||||||
|
],
|
||||||
|
|
||||||
|
# nibble 6
|
||||||
|
[
|
||||||
|
U32(0x00001000), U32(0x00000080), U32(0x00400080), U32(0x00400001),
|
||||||
|
U32(0x00401081), U32(0x00001001), U32(0x00001080), U32(0x00000000),
|
||||||
|
U32(0x00400000), U32(0x00400081), U32(0x00000081), U32(0x00401000),
|
||||||
|
U32(0x00000001), U32(0x00401080), U32(0x00401000), U32(0x00000081),
|
||||||
|
U32(0x00400081), U32(0x00001000), U32(0x00001001), U32(0x00401081),
|
||||||
|
U32(0x00000000), U32(0x00400080), U32(0x00400001), U32(0x00001080),
|
||||||
|
U32(0x00401001), U32(0x00001081), U32(0x00401080), U32(0x00000001),
|
||||||
|
U32(0x00001081), U32(0x00401001), U32(0x00000080), U32(0x00400000),
|
||||||
|
U32(0x00001081), U32(0x00401000), U32(0x00401001), U32(0x00000081),
|
||||||
|
U32(0x00001000), U32(0x00000080), U32(0x00400000), U32(0x00401001),
|
||||||
|
U32(0x00400081), U32(0x00001081), U32(0x00001080), U32(0x00000000),
|
||||||
|
U32(0x00000080), U32(0x00400001), U32(0x00000001), U32(0x00400080),
|
||||||
|
U32(0x00000000), U32(0x00400081), U32(0x00400080), U32(0x00001080),
|
||||||
|
U32(0x00000081), U32(0x00001000), U32(0x00401081), U32(0x00400000),
|
||||||
|
U32(0x00401080), U32(0x00000001), U32(0x00001001), U32(0x00401081),
|
||||||
|
U32(0x00400001), U32(0x00401080), U32(0x00401000), U32(0x00001001),
|
||||||
|
],
|
||||||
|
|
||||||
|
# nibble 7
|
||||||
|
[
|
||||||
|
U32(0x08200020), U32(0x08208000), U32(0x00008020), U32(0x00000000),
|
||||||
|
U32(0x08008000), U32(0x00200020), U32(0x08200000), U32(0x08208020),
|
||||||
|
U32(0x00000020), U32(0x08000000), U32(0x00208000), U32(0x00008020),
|
||||||
|
U32(0x00208020), U32(0x08008020), U32(0x08000020), U32(0x08200000),
|
||||||
|
U32(0x00008000), U32(0x00208020), U32(0x00200020), U32(0x08008000),
|
||||||
|
U32(0x08208020), U32(0x08000020), U32(0x00000000), U32(0x00208000),
|
||||||
|
U32(0x08000000), U32(0x00200000), U32(0x08008020), U32(0x08200020),
|
||||||
|
U32(0x00200000), U32(0x00008000), U32(0x08208000), U32(0x00000020),
|
||||||
|
U32(0x00200000), U32(0x00008000), U32(0x08000020), U32(0x08208020),
|
||||||
|
U32(0x00008020), U32(0x08000000), U32(0x00000000), U32(0x00208000),
|
||||||
|
U32(0x08200020), U32(0x08008020), U32(0x08008000), U32(0x00200020),
|
||||||
|
U32(0x08208000), U32(0x00000020), U32(0x00200020), U32(0x08008000),
|
||||||
|
U32(0x08208020), U32(0x00200000), U32(0x08200000), U32(0x08000020),
|
||||||
|
U32(0x00208000), U32(0x00008020), U32(0x08008020), U32(0x08200000),
|
||||||
|
U32(0x00000020), U32(0x08208000), U32(0x00208020), U32(0x00000000),
|
||||||
|
U32(0x08000000), U32(0x08200020), U32(0x00008000), U32(0x00208020),
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
# static unsigned long des_skb[8][64]={
|
||||||
|
|
||||||
|
des_skb = \
|
||||||
|
[
|
||||||
|
# for C bits (numbered as per FIPS 46) 1 2 3 4 5 6
|
||||||
|
[
|
||||||
|
U32(0x00000000), U32(0x00000010), U32(0x20000000), U32(0x20000010),
|
||||||
|
U32(0x00010000), U32(0x00010010), U32(0x20010000), U32(0x20010010),
|
||||||
|
U32(0x00000800), U32(0x00000810), U32(0x20000800), U32(0x20000810),
|
||||||
|
U32(0x00010800), U32(0x00010810), U32(0x20010800), U32(0x20010810),
|
||||||
|
U32(0x00000020), U32(0x00000030), U32(0x20000020), U32(0x20000030),
|
||||||
|
U32(0x00010020), U32(0x00010030), U32(0x20010020), U32(0x20010030),
|
||||||
|
U32(0x00000820), U32(0x00000830), U32(0x20000820), U32(0x20000830),
|
||||||
|
U32(0x00010820), U32(0x00010830), U32(0x20010820), U32(0x20010830),
|
||||||
|
U32(0x00080000), U32(0x00080010), U32(0x20080000), U32(0x20080010),
|
||||||
|
U32(0x00090000), U32(0x00090010), U32(0x20090000), U32(0x20090010),
|
||||||
|
U32(0x00080800), U32(0x00080810), U32(0x20080800), U32(0x20080810),
|
||||||
|
U32(0x00090800), U32(0x00090810), U32(0x20090800), U32(0x20090810),
|
||||||
|
U32(0x00080020), U32(0x00080030), U32(0x20080020), U32(0x20080030),
|
||||||
|
U32(0x00090020), U32(0x00090030), U32(0x20090020), U32(0x20090030),
|
||||||
|
U32(0x00080820), U32(0x00080830), U32(0x20080820), U32(0x20080830),
|
||||||
|
U32(0x00090820), U32(0x00090830), U32(0x20090820), U32(0x20090830),
|
||||||
|
],
|
||||||
|
|
||||||
|
# for C bits (numbered as per FIPS 46) 7 8 10 11 12 13
|
||||||
|
[
|
||||||
|
U32(0x00000000), U32(0x02000000), U32(0x00002000), U32(0x02002000),
|
||||||
|
U32(0x00200000), U32(0x02200000), U32(0x00202000), U32(0x02202000),
|
||||||
|
U32(0x00000004), U32(0x02000004), U32(0x00002004), U32(0x02002004),
|
||||||
|
U32(0x00200004), U32(0x02200004), U32(0x00202004), U32(0x02202004),
|
||||||
|
U32(0x00000400), U32(0x02000400), U32(0x00002400), U32(0x02002400),
|
||||||
|
U32(0x00200400), U32(0x02200400), U32(0x00202400), U32(0x02202400),
|
||||||
|
U32(0x00000404), U32(0x02000404), U32(0x00002404), U32(0x02002404),
|
||||||
|
U32(0x00200404), U32(0x02200404), U32(0x00202404), U32(0x02202404),
|
||||||
|
U32(0x10000000), U32(0x12000000), U32(0x10002000), U32(0x12002000),
|
||||||
|
U32(0x10200000), U32(0x12200000), U32(0x10202000), U32(0x12202000),
|
||||||
|
U32(0x10000004), U32(0x12000004), U32(0x10002004), U32(0x12002004),
|
||||||
|
U32(0x10200004), U32(0x12200004), U32(0x10202004), U32(0x12202004),
|
||||||
|
U32(0x10000400), U32(0x12000400), U32(0x10002400), U32(0x12002400),
|
||||||
|
U32(0x10200400), U32(0x12200400), U32(0x10202400), U32(0x12202400),
|
||||||
|
U32(0x10000404), U32(0x12000404), U32(0x10002404), U32(0x12002404),
|
||||||
|
U32(0x10200404), U32(0x12200404), U32(0x10202404), U32(0x12202404),
|
||||||
|
],
|
||||||
|
|
||||||
|
# for C bits (numbered as per FIPS 46) 14 15 16 17 19 20
|
||||||
|
[
|
||||||
|
U32(0x00000000), U32(0x00000001), U32(0x00040000), U32(0x00040001),
|
||||||
|
U32(0x01000000), U32(0x01000001), U32(0x01040000), U32(0x01040001),
|
||||||
|
U32(0x00000002), U32(0x00000003), U32(0x00040002), U32(0x00040003),
|
||||||
|
U32(0x01000002), U32(0x01000003), U32(0x01040002), U32(0x01040003),
|
||||||
|
U32(0x00000200), U32(0x00000201), U32(0x00040200), U32(0x00040201),
|
||||||
|
U32(0x01000200), U32(0x01000201), U32(0x01040200), U32(0x01040201),
|
||||||
|
U32(0x00000202), U32(0x00000203), U32(0x00040202), U32(0x00040203),
|
||||||
|
U32(0x01000202), U32(0x01000203), U32(0x01040202), U32(0x01040203),
|
||||||
|
U32(0x08000000), U32(0x08000001), U32(0x08040000), U32(0x08040001),
|
||||||
|
U32(0x09000000), U32(0x09000001), U32(0x09040000), U32(0x09040001),
|
||||||
|
U32(0x08000002), U32(0x08000003), U32(0x08040002), U32(0x08040003),
|
||||||
|
U32(0x09000002), U32(0x09000003), U32(0x09040002), U32(0x09040003),
|
||||||
|
U32(0x08000200), U32(0x08000201), U32(0x08040200), U32(0x08040201),
|
||||||
|
U32(0x09000200), U32(0x09000201), U32(0x09040200), U32(0x09040201),
|
||||||
|
U32(0x08000202), U32(0x08000203), U32(0x08040202), U32(0x08040203),
|
||||||
|
U32(0x09000202), U32(0x09000203), U32(0x09040202), U32(0x09040203),
|
||||||
|
],
|
||||||
|
|
||||||
|
# for C bits (numbered as per FIPS 46) 21 23 24 26 27 28
|
||||||
|
[
|
||||||
|
U32(0x00000000), U32(0x00100000), U32(0x00000100), U32(0x00100100),
|
||||||
|
U32(0x00000008), U32(0x00100008), U32(0x00000108), U32(0x00100108),
|
||||||
|
U32(0x00001000), U32(0x00101000), U32(0x00001100), U32(0x00101100),
|
||||||
|
U32(0x00001008), U32(0x00101008), U32(0x00001108), U32(0x00101108),
|
||||||
|
U32(0x04000000), U32(0x04100000), U32(0x04000100), U32(0x04100100),
|
||||||
|
U32(0x04000008), U32(0x04100008), U32(0x04000108), U32(0x04100108),
|
||||||
|
U32(0x04001000), U32(0x04101000), U32(0x04001100), U32(0x04101100),
|
||||||
|
U32(0x04001008), U32(0x04101008), U32(0x04001108), U32(0x04101108),
|
||||||
|
U32(0x00020000), U32(0x00120000), U32(0x00020100), U32(0x00120100),
|
||||||
|
U32(0x00020008), U32(0x00120008), U32(0x00020108), U32(0x00120108),
|
||||||
|
U32(0x00021000), U32(0x00121000), U32(0x00021100), U32(0x00121100),
|
||||||
|
U32(0x00021008), U32(0x00121008), U32(0x00021108), U32(0x00121108),
|
||||||
|
U32(0x04020000), U32(0x04120000), U32(0x04020100), U32(0x04120100),
|
||||||
|
U32(0x04020008), U32(0x04120008), U32(0x04020108), U32(0x04120108),
|
||||||
|
U32(0x04021000), U32(0x04121000), U32(0x04021100), U32(0x04121100),
|
||||||
|
U32(0x04021008), U32(0x04121008), U32(0x04021108), U32(0x04121108),
|
||||||
|
],
|
||||||
|
|
||||||
|
# for D bits (numbered as per FIPS 46) 1 2 3 4 5 6
|
||||||
|
[
|
||||||
|
U32(0x00000000), U32(0x10000000), U32(0x00010000), U32(0x10010000),
|
||||||
|
U32(0x00000004), U32(0x10000004), U32(0x00010004), U32(0x10010004),
|
||||||
|
U32(0x20000000), U32(0x30000000), U32(0x20010000), U32(0x30010000),
|
||||||
|
U32(0x20000004), U32(0x30000004), U32(0x20010004), U32(0x30010004),
|
||||||
|
U32(0x00100000), U32(0x10100000), U32(0x00110000), U32(0x10110000),
|
||||||
|
U32(0x00100004), U32(0x10100004), U32(0x00110004), U32(0x10110004),
|
||||||
|
U32(0x20100000), U32(0x30100000), U32(0x20110000), U32(0x30110000),
|
||||||
|
U32(0x20100004), U32(0x30100004), U32(0x20110004), U32(0x30110004),
|
||||||
|
U32(0x00001000), U32(0x10001000), U32(0x00011000), U32(0x10011000),
|
||||||
|
U32(0x00001004), U32(0x10001004), U32(0x00011004), U32(0x10011004),
|
||||||
|
U32(0x20001000), U32(0x30001000), U32(0x20011000), U32(0x30011000),
|
||||||
|
U32(0x20001004), U32(0x30001004), U32(0x20011004), U32(0x30011004),
|
||||||
|
U32(0x00101000), U32(0x10101000), U32(0x00111000), U32(0x10111000),
|
||||||
|
U32(0x00101004), U32(0x10101004), U32(0x00111004), U32(0x10111004),
|
||||||
|
U32(0x20101000), U32(0x30101000), U32(0x20111000), U32(0x30111000),
|
||||||
|
U32(0x20101004), U32(0x30101004), U32(0x20111004), U32(0x30111004),
|
||||||
|
],
|
||||||
|
|
||||||
|
# for D bits (numbered as per FIPS 46) 8 9 11 12 13 14
|
||||||
|
[
|
||||||
|
U32(0x00000000), U32(0x08000000), U32(0x00000008), U32(0x08000008),
|
||||||
|
U32(0x00000400), U32(0x08000400), U32(0x00000408), U32(0x08000408),
|
||||||
|
U32(0x00020000), U32(0x08020000), U32(0x00020008), U32(0x08020008),
|
||||||
|
U32(0x00020400), U32(0x08020400), U32(0x00020408), U32(0x08020408),
|
||||||
|
U32(0x00000001), U32(0x08000001), U32(0x00000009), U32(0x08000009),
|
||||||
|
U32(0x00000401), U32(0x08000401), U32(0x00000409), U32(0x08000409),
|
||||||
|
U32(0x00020001), U32(0x08020001), U32(0x00020009), U32(0x08020009),
|
||||||
|
U32(0x00020401), U32(0x08020401), U32(0x00020409), U32(0x08020409),
|
||||||
|
U32(0x02000000), U32(0x0A000000), U32(0x02000008), U32(0x0A000008),
|
||||||
|
U32(0x02000400), U32(0x0A000400), U32(0x02000408), U32(0x0A000408),
|
||||||
|
U32(0x02020000), U32(0x0A020000), U32(0x02020008), U32(0x0A020008),
|
||||||
|
U32(0x02020400), U32(0x0A020400), U32(0x02020408), U32(0x0A020408),
|
||||||
|
U32(0x02000001), U32(0x0A000001), U32(0x02000009), U32(0x0A000009),
|
||||||
|
U32(0x02000401), U32(0x0A000401), U32(0x02000409), U32(0x0A000409),
|
||||||
|
U32(0x02020001), U32(0x0A020001), U32(0x02020009), U32(0x0A020009),
|
||||||
|
U32(0x02020401), U32(0x0A020401), U32(0x02020409), U32(0x0A020409),
|
||||||
|
],
|
||||||
|
|
||||||
|
# for D bits (numbered as per FIPS 46) 16 17 18 19 20 21
|
||||||
|
[
|
||||||
|
U32(0x00000000), U32(0x00000100), U32(0x00080000), U32(0x00080100),
|
||||||
|
U32(0x01000000), U32(0x01000100), U32(0x01080000), U32(0x01080100),
|
||||||
|
U32(0x00000010), U32(0x00000110), U32(0x00080010), U32(0x00080110),
|
||||||
|
U32(0x01000010), U32(0x01000110), U32(0x01080010), U32(0x01080110),
|
||||||
|
U32(0x00200000), U32(0x00200100), U32(0x00280000), U32(0x00280100),
|
||||||
|
U32(0x01200000), U32(0x01200100), U32(0x01280000), U32(0x01280100),
|
||||||
|
U32(0x00200010), U32(0x00200110), U32(0x00280010), U32(0x00280110),
|
||||||
|
U32(0x01200010), U32(0x01200110), U32(0x01280010), U32(0x01280110),
|
||||||
|
U32(0x00000200), U32(0x00000300), U32(0x00080200), U32(0x00080300),
|
||||||
|
U32(0x01000200), U32(0x01000300), U32(0x01080200), U32(0x01080300),
|
||||||
|
U32(0x00000210), U32(0x00000310), U32(0x00080210), U32(0x00080310),
|
||||||
|
U32(0x01000210), U32(0x01000310), U32(0x01080210), U32(0x01080310),
|
||||||
|
U32(0x00200200), U32(0x00200300), U32(0x00280200), U32(0x00280300),
|
||||||
|
U32(0x01200200), U32(0x01200300), U32(0x01280200), U32(0x01280300),
|
||||||
|
U32(0x00200210), U32(0x00200310), U32(0x00280210), U32(0x00280310),
|
||||||
|
U32(0x01200210), U32(0x01200310), U32(0x01280210), U32(0x01280310),
|
||||||
|
],
|
||||||
|
|
||||||
|
# for D bits (numbered as per FIPS 46) 22 23 24 25 27 28
|
||||||
|
[
|
||||||
|
U32(0x00000000), U32(0x04000000), U32(0x00040000), U32(0x04040000),
|
||||||
|
U32(0x00000002), U32(0x04000002), U32(0x00040002), U32(0x04040002),
|
||||||
|
U32(0x00002000), U32(0x04002000), U32(0x00042000), U32(0x04042000),
|
||||||
|
U32(0x00002002), U32(0x04002002), U32(0x00042002), U32(0x04042002),
|
||||||
|
U32(0x00000020), U32(0x04000020), U32(0x00040020), U32(0x04040020),
|
||||||
|
U32(0x00000022), U32(0x04000022), U32(0x00040022), U32(0x04040022),
|
||||||
|
U32(0x00002020), U32(0x04002020), U32(0x00042020), U32(0x04042020),
|
||||||
|
U32(0x00002022), U32(0x04002022), U32(0x00042022), U32(0x04042022),
|
||||||
|
U32(0x00000800), U32(0x04000800), U32(0x00040800), U32(0x04040800),
|
||||||
|
U32(0x00000802), U32(0x04000802), U32(0x00040802), U32(0x04040802),
|
||||||
|
U32(0x00002800), U32(0x04002800), U32(0x00042800), U32(0x04042800),
|
||||||
|
U32(0x00002802), U32(0x04002802), U32(0x00042802), U32(0x04042802),
|
||||||
|
U32(0x00000820), U32(0x04000820), U32(0x00040820), U32(0x04040820),
|
||||||
|
U32(0x00000822), U32(0x04000822), U32(0x00040822), U32(0x04040822),
|
||||||
|
U32(0x00002820), U32(0x04002820), U32(0x00042820), U32(0x04042820),
|
||||||
|
U32(0x00002822), U32(0x04002822), U32(0x00042822), U32(0x04042822),
|
||||||
|
]
|
||||||
|
|
||||||
|
]
|
|
@ -0,0 +1,67 @@
|
||||||
|
# This library is free software: you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation, either
|
||||||
|
# version 3 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
# This library is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||||
|
|
||||||
|
import struct
|
||||||
|
|
||||||
|
"""
|
||||||
|
This is not the easiest structure to understand, ultimately this is a set structure
|
||||||
|
as defined by Microsoft. Channel Binding Tokens set the SHA256 hash of the server
|
||||||
|
certificate to the application_data field and then ultimately creates the MD5 hash
|
||||||
|
to include in the NTLM auth from there. This class is just designed to create the
|
||||||
|
bindings structure which is then used by compute_response.py to do the rest of the
|
||||||
|
work.
|
||||||
|
|
||||||
|
For more infor on how this works and how it is derived, this is a great link;
|
||||||
|
https://blogs.msdn.microsoft.com/openspecification/2013/03/26/ntlm-and-channel-binding-hash-aka-extended-protection-for-authentication/
|
||||||
|
"""
|
||||||
|
class GssChannelBindingsStruct(object):
|
||||||
|
INITIATOR_ADDTYPE = 'initiator_addtype'
|
||||||
|
INITIATOR_ADDRESS_LENGTH = 'initiator_address_length'
|
||||||
|
ACCEPTOR_ADDRTYPE = 'acceptor_addrtype'
|
||||||
|
ACCEPTOR_ADDRESS_LENGTH = 'acceptor_address_length'
|
||||||
|
APPLICATION_DATA_LENGTH = 'application_data_length'
|
||||||
|
INITIATOR_ADDRESS = 'initiator_address'
|
||||||
|
ACCEPTOR_ADDRESS = 'acceptor_address'
|
||||||
|
APPLICATION_DATA = 'application_data'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.fields = {}
|
||||||
|
self.fields[self.INITIATOR_ADDTYPE] = 0
|
||||||
|
self.fields[self.INITIATOR_ADDRESS_LENGTH] = 0
|
||||||
|
self.fields[self.ACCEPTOR_ADDRTYPE] = 0
|
||||||
|
self.fields[self.ACCEPTOR_ADDRESS_LENGTH] = 0
|
||||||
|
self.fields[self.APPLICATION_DATA_LENGTH] = 0
|
||||||
|
self.fields[self.INITIATOR_ADDRESS] = b''
|
||||||
|
self.fields[self.ACCEPTOR_ADDRESS] = b''
|
||||||
|
self.fields[self.APPLICATION_DATA] = b''
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self.fields[key] = value
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
# Set the lengths of each len field in case they have changed
|
||||||
|
self.fields[self.INITIATOR_ADDRESS_LENGTH] = len(self.fields[self.INITIATOR_ADDRESS])
|
||||||
|
self.fields[self.ACCEPTOR_ADDRESS_LENGTH] = len(self.fields[self.ACCEPTOR_ADDRESS])
|
||||||
|
self.fields[self.APPLICATION_DATA_LENGTH] = len(self.fields[self.APPLICATION_DATA])
|
||||||
|
|
||||||
|
# Add all the values together to create the gss_channel_bindings_struct
|
||||||
|
data = struct.pack('<L', self.fields[self.INITIATOR_ADDTYPE]) + \
|
||||||
|
struct.pack('<L', self.fields[self.INITIATOR_ADDRESS_LENGTH]) + \
|
||||||
|
self.fields[self.INITIATOR_ADDRESS] + \
|
||||||
|
struct.pack('<L', self.fields[self.ACCEPTOR_ADDRTYPE]) + \
|
||||||
|
struct.pack('<L', self.fields[self.ACCEPTOR_ADDRESS_LENGTH]) + \
|
||||||
|
self.fields[self.ACCEPTOR_ADDRESS] + \
|
||||||
|
struct.pack('<L', self.fields[self.APPLICATION_DATA_LENGTH]) + \
|
||||||
|
self.fields[self.APPLICATION_DATA]
|
||||||
|
|
||||||
|
return data
|
359
plugin/packages/wakatime/packages/ntlm_auth/messages.py
Normal file
359
plugin/packages/wakatime/packages/ntlm_auth/messages.py
Normal file
|
@ -0,0 +1,359 @@
|
||||||
|
# This library is free software: you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation, either
|
||||||
|
# version 3 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
# This library is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||||
|
|
||||||
|
import hmac
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
from ntlm_auth.compute_response import ComputeResponse
|
||||||
|
from ntlm_auth.constants import NegotiateFlags, MessageTypes, NTLM_SIGNATURE, AvFlags
|
||||||
|
from ntlm_auth.rc4 import ARC4
|
||||||
|
from ntlm_auth.target_info import TargetInfo
|
||||||
|
|
||||||
|
class NegotiateMessage(object):
|
||||||
|
EXPECTED_BODY_LENGTH = 40
|
||||||
|
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
2.2.1.1 NEGOTIATE_MESSAGE
|
||||||
|
The NEGOTIATE_MESSAGE defines an NTLM Negotiate message that is sent from the client to
|
||||||
|
the server. This message allows the client to specify its supported NTLM options to
|
||||||
|
the server.
|
||||||
|
|
||||||
|
:param negotiate_flags: A NEGOTIATE structure that contains a set of bit flags. These flags are the options the client supports
|
||||||
|
:param domain_name: The domain name of the user to authenticate with, default is None
|
||||||
|
:param workstation: The worksation of the client machine, default is None
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
signature: An 8-byte character array that MUST contain the ASCII string 'NTLMSSP\0'
|
||||||
|
message_type: A 32-bit unsigned integer that indicates the message type. This field must be set to 0x00000001
|
||||||
|
negotiate_flags: A NEGOTIATE structure that contains a set of bit flags. These flags are the options the client supports
|
||||||
|
version: Contains the windows version info of the client. It is used only debugging purposes and are only set when NTLMSSP_NEGOTIATE_VERSION flag is set
|
||||||
|
domain_name: A byte-array that contains the name of the client authentication domain that MUST Be encoded in the negotiated character set
|
||||||
|
workstation: A byte-array that contains the name of the client machine that MUST Be encoded in the negotiated character set
|
||||||
|
"""
|
||||||
|
def __init__(self, negotiate_flags, domain_name, workstation):
|
||||||
|
self.signature = NTLM_SIGNATURE
|
||||||
|
self.message_type = struct.pack('<L', MessageTypes.NTLM_NEGOTIATE)
|
||||||
|
|
||||||
|
# Check if the domain_name value is set, if it is, make sure the negotiate_flag is also set
|
||||||
|
if domain_name is None:
|
||||||
|
self.domain_name = ''
|
||||||
|
else:
|
||||||
|
self.domain_name = domain_name
|
||||||
|
negotiate_flags |= NegotiateFlags.NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED
|
||||||
|
|
||||||
|
# Check if the workstation value is set, if it is, make sure the negotiate_flag is also set
|
||||||
|
if workstation is None:
|
||||||
|
self.workstation = ''
|
||||||
|
else:
|
||||||
|
self.workstation = workstation
|
||||||
|
negotiate_flags |= NegotiateFlags.NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED
|
||||||
|
|
||||||
|
# Set the encoding flag to use OEM, remove UNICODE if set as it isn't support in this message
|
||||||
|
negotiate_flags -= NegotiateFlags.NTLMSSP_NEGOTIATE_UNICODE
|
||||||
|
negotiate_flags |= NegotiateFlags.NTLMSSP_NEGOTIATE_OEM
|
||||||
|
self.domain_name = self.domain_name.encode('ascii')
|
||||||
|
self.workstation = self.workstation.encode('ascii')
|
||||||
|
|
||||||
|
self.version = get_version(negotiate_flags)
|
||||||
|
|
||||||
|
self.negotiate_flags = struct.pack('<I', negotiate_flags)
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
payload_offset = self.EXPECTED_BODY_LENGTH
|
||||||
|
|
||||||
|
# DomainNameFields - 8 bytes
|
||||||
|
domain_name_len = struct.pack('<H', len(self.domain_name))
|
||||||
|
domain_name_max_len = struct.pack('<H', len(self.domain_name))
|
||||||
|
domain_name_buffer_offset = struct.pack('<I', payload_offset)
|
||||||
|
payload_offset += len(self.domain_name)
|
||||||
|
|
||||||
|
# WorkstationFields - 8 bytes
|
||||||
|
workstation_len = struct.pack('<H', len(self.workstation))
|
||||||
|
workstation_max_len = struct.pack('<H', len(self.workstation))
|
||||||
|
workstation_buffer_offset = struct.pack('<I', payload_offset)
|
||||||
|
payload_offset += len(self.workstation)
|
||||||
|
|
||||||
|
# Payload - variable length
|
||||||
|
payload = self.domain_name
|
||||||
|
payload += self.workstation
|
||||||
|
|
||||||
|
# Bring the header values together into 1 message
|
||||||
|
msg1 = self.signature
|
||||||
|
msg1 += self.message_type
|
||||||
|
msg1 += self.negotiate_flags
|
||||||
|
msg1 += domain_name_len
|
||||||
|
msg1 += domain_name_max_len
|
||||||
|
msg1 += domain_name_buffer_offset
|
||||||
|
msg1 += workstation_len
|
||||||
|
msg1 += workstation_max_len
|
||||||
|
msg1 += workstation_buffer_offset
|
||||||
|
msg1 += self.version
|
||||||
|
|
||||||
|
assert self.EXPECTED_BODY_LENGTH == len(msg1), "BODY_LENGTH: %d != msg1: %d" % (self.EXPECTED_BODY_LENGTH, len(msg1))
|
||||||
|
|
||||||
|
# Adding the payload data to the message
|
||||||
|
msg1 += payload
|
||||||
|
return msg1
|
||||||
|
|
||||||
|
class ChallengeMessage(object):
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
2.2.1.2 CHALLENGE_MESSAGE
|
||||||
|
The CHALLENGE_MESSAGE defines an NTLM challenge message that is sent from the server to
|
||||||
|
the client. The CHALLENGE_MESSAGE is used by the server to challenge the client to prove
|
||||||
|
its identity, For connection-oriented requests, the CHALLENGE_MESSAGE generated by the
|
||||||
|
server is in response to the NEGOTIATE_MESSAGE from the client.
|
||||||
|
|
||||||
|
:param msg2: The CHALLENGE_MESSAGE received from the server after sending our NEGOTIATE_MESSAGE. This has
|
||||||
|
been decoded from a base64 string
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
signature: An 8-byte character array that MUST contain the ASCII string 'NTLMSSP\0'
|
||||||
|
message_type: A 32-bit unsigned integer that indicates the message type. This field must be set to 0x00000002
|
||||||
|
negotiate_flags: A NEGOTIATE strucutre that contains a set of bit flags. The server sets flags to indicate options it supports
|
||||||
|
server_challenge: A 64-bit value that contains the NTLM challenge. The challenge is a 64-bit nonce. Used in the AuthenticateMessage message
|
||||||
|
reserved: An 8-byte array whose elements MUST be zero when sent and MUST be ignored on receipt
|
||||||
|
version: When NTLMSSP_NEGOTIATE_VERSION flag is set in negotiate_flags field which contains the windows version info. Used only for debugging purposes
|
||||||
|
target_name: When NTLMSSP_REQUEST_TARGET is set is a byte array that contains the name of the server authentication realm. In a domain environment this is the domain name not server name
|
||||||
|
target_info: When NTLMSSP_NEGOTIATE_TARGET_INFO is set is a byte array that contains a sequence of AV_PAIR structures (target_info.py)
|
||||||
|
"""
|
||||||
|
def __init__(self, msg2):
|
||||||
|
self.data = msg2
|
||||||
|
# Setting the object values from the raw_challenge_message
|
||||||
|
self.signature = msg2[0:8]
|
||||||
|
self.message_type = struct.unpack("<I", msg2[8:12])[0]
|
||||||
|
self.negotiate_flags = struct.unpack("<I", msg2[20:24])[0]
|
||||||
|
self.server_challenge = msg2[24:32]
|
||||||
|
self.reserved = msg2[32:40]
|
||||||
|
|
||||||
|
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_VERSION and self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||||
|
size = len(msg2)
|
||||||
|
self.version = struct.unpack("<q", msg2[48:56])[0]
|
||||||
|
else:
|
||||||
|
self.version = None
|
||||||
|
|
||||||
|
if self.negotiate_flags & NegotiateFlags.NTLMSSP_REQUEST_TARGET:
|
||||||
|
target_name_len = struct.unpack("<H", msg2[12:14])[0]
|
||||||
|
target_name_max_len = struct.unpack("<H", msg2[14:16])[0]
|
||||||
|
target_name_buffer_offset = struct.unpack("<I", msg2[16:20])[0]
|
||||||
|
self.target_name = msg2[target_name_buffer_offset:target_name_buffer_offset + target_name_len]
|
||||||
|
else:
|
||||||
|
self.target_name = None
|
||||||
|
|
||||||
|
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_TARGET_INFO:
|
||||||
|
target_info_len = struct.unpack("<H", msg2[40:42])[0]
|
||||||
|
target_info_max_len = struct.unpack("<H", msg2[42:44])[0]
|
||||||
|
target_info_buffer_offset = struct.unpack("<I", msg2[44:48])[0]
|
||||||
|
|
||||||
|
target_info_raw = msg2[target_info_buffer_offset:target_info_buffer_offset + target_info_len]
|
||||||
|
self.target_info = TargetInfo(target_info_raw)
|
||||||
|
else:
|
||||||
|
self.target_info = None
|
||||||
|
|
||||||
|
# Verify initial integrity of the message, it matches what should be there
|
||||||
|
assert self.signature == NTLM_SIGNATURE
|
||||||
|
assert self.message_type == MessageTypes.NTLM_CHALLENGE
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
return self.data
|
||||||
|
|
||||||
|
class AuthenticateMessage(object):
|
||||||
|
EXPECTED_BODY_LENGTH = 72
|
||||||
|
EXPECTED_BODY_LENGTH_WITH_MIC = 88
|
||||||
|
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
2.2.1.3 AUTHENTICATE_MESSAGE
|
||||||
|
The AUTHENTICATE_MESSAGE defines an NTLM authenticate message that is sent from the
|
||||||
|
client to the server after the CHALLENGE_MESSAGE is processed by the client.
|
||||||
|
|
||||||
|
:param user_name: The user name of the user we are trying to authenticate with
|
||||||
|
:param password: The password of the user we are trying to authenticate with
|
||||||
|
:param domain_name: The domain name of the user account we are authenticated with, default is None
|
||||||
|
:param workstation: The workstation we are using to authenticate with, default is None
|
||||||
|
:param challenge_message: A ChallengeMessage object that was received from the server after the negotiate_message
|
||||||
|
:param ntlm_compatibility: The Lan Manager Compatibility Level, used to determine what NTLM auth version to use, see Ntlm in ntlm.py for more details
|
||||||
|
:param server_certificate_hash: The SHA256 hash string of the server certificate (DER encoded) NTLM is authenticating to. This is used to add
|
||||||
|
to the gss_channel_bindings_struct for Channel Binding Tokens support. If none is passed through then ntlm-auth
|
||||||
|
will not use Channel Binding Tokens when authenticating with the server which could cause issues if it is set to
|
||||||
|
only authenticate when these are present. This is only used for NTLMv2 authentication.
|
||||||
|
|
||||||
|
Message Attributes (Attributes not used to compute the message structure):
|
||||||
|
signature: An 8-byte character array that MUST contain the ASCII string 'NTLMSSP\0'
|
||||||
|
message_type: A 32-bit unsigned integer that indicates the message type. This field must be set to 0x00000003
|
||||||
|
negotiate_flags: A NEGOTIATE strucutre that contains a set of bit flags. These flags are the choices the client has made from the CHALLENGE_MESSAGE options
|
||||||
|
version: Contains the windows version info of the client. It is used only debugging purposes and are only set when NTLMSSP_NEGOTIATE_VERSION flag is set
|
||||||
|
mic: The message integrity for the NEGOTIATE_MESSAGE, CHALLENGE_MESSAGE and AUTHENTICATE_MESSAGE
|
||||||
|
lm_challenge_response: An LM_RESPONSE of LMv2_RESPONSE structure that contains the computed LM response to the challenge
|
||||||
|
nt_challenge_response: An NTLM_RESPONSE or NTLMv2_RESPONSE structure that contains the computed NT response to the challenge
|
||||||
|
domain_name: The domain or computer name hosting the user account, MUST be encoded in the negotiated character set
|
||||||
|
user_name: The name of the user to be authenticated, MUST be encoded in the negotiated character set
|
||||||
|
workstation: The name of the computer to which the user is logged on, MUST Be encoded in the negotiated character set
|
||||||
|
encrypted_random_session_key: The client's encrypted random session key
|
||||||
|
|
||||||
|
Non-Message Attributes (Attributes not used to compute the message structure):
|
||||||
|
exported_session_key: A randomly generated session key based on other keys, used to derive the SIGNKEY and SEALKEY
|
||||||
|
target_info: The AV_PAIR structure used in the nt response calculation
|
||||||
|
"""
|
||||||
|
def __init__(self, user_name, password, domain_name, workstation, challenge_message, ntlm_compatibility, server_certificate_hash):
|
||||||
|
self.signature = NTLM_SIGNATURE
|
||||||
|
self.message_type = struct.pack('<L', MessageTypes.NTLM_AUTHENTICATE)
|
||||||
|
self.negotiate_flags = challenge_message.negotiate_flags
|
||||||
|
self.version = get_version(self.negotiate_flags)
|
||||||
|
self.mic = None
|
||||||
|
|
||||||
|
if domain_name is None:
|
||||||
|
self.domain_name = ''
|
||||||
|
else:
|
||||||
|
self.domain_name = domain_name
|
||||||
|
|
||||||
|
if workstation is None:
|
||||||
|
self.workstation = ''
|
||||||
|
else:
|
||||||
|
self.workstation = workstation
|
||||||
|
|
||||||
|
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_UNICODE:
|
||||||
|
self.negotiate_flags -= NegotiateFlags.NTLMSSP_NEGOTIATE_OEM
|
||||||
|
encoding_value = 'utf-16-le'
|
||||||
|
else:
|
||||||
|
encoding_value = 'ascii'
|
||||||
|
|
||||||
|
self.domain_name = self.domain_name.encode(encoding_value)
|
||||||
|
self.user_name = user_name.encode(encoding_value)
|
||||||
|
self.workstation = self.workstation.encode(encoding_value)
|
||||||
|
|
||||||
|
compute_response = ComputeResponse(user_name, password, domain_name, challenge_message,
|
||||||
|
ntlm_compatibility)
|
||||||
|
|
||||||
|
self.lm_challenge_response = compute_response.get_lm_challenge_response()
|
||||||
|
self.nt_challenge_response, key_exchange_key, target_info = compute_response.get_nt_challenge_response(
|
||||||
|
self.lm_challenge_response, server_certificate_hash)
|
||||||
|
self.target_info = target_info
|
||||||
|
|
||||||
|
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_KEY_EXCH:
|
||||||
|
self.exported_session_key = get_random_export_session_key()
|
||||||
|
|
||||||
|
rc4_handle = ARC4(key_exchange_key)
|
||||||
|
self.encrypted_random_session_key = rc4_handle.update(self.exported_session_key)
|
||||||
|
else:
|
||||||
|
self.exported_session_key = key_exchange_key
|
||||||
|
self.encrypted_random_session_key = b''
|
||||||
|
|
||||||
|
self.negotiate_flags = struct.pack('<I', self.negotiate_flags)
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
if self.mic is None:
|
||||||
|
mic = b''
|
||||||
|
expected_body_length = self.EXPECTED_BODY_LENGTH
|
||||||
|
else:
|
||||||
|
mic = self.mic
|
||||||
|
expected_body_length = self.EXPECTED_BODY_LENGTH_WITH_MIC
|
||||||
|
|
||||||
|
payload_offset = expected_body_length
|
||||||
|
|
||||||
|
# DomainNameFields - 8 bytes
|
||||||
|
domain_name_len = struct.pack('<H', len(self.domain_name))
|
||||||
|
domain_name_max_len = struct.pack('<H', len(self.domain_name))
|
||||||
|
domain_name_buffer_offset = struct.pack('<I', payload_offset)
|
||||||
|
payload_offset += len(self.domain_name)
|
||||||
|
|
||||||
|
# UserNameFields - 8 bytes
|
||||||
|
user_name_len = struct.pack('<H', len(self.user_name))
|
||||||
|
user_name_max_len = struct.pack('<H', len(self.user_name))
|
||||||
|
user_name_buffer_offset = struct.pack('<I', payload_offset)
|
||||||
|
payload_offset += len(self.user_name)
|
||||||
|
|
||||||
|
# WorkstatonFields - 8 bytes
|
||||||
|
workstation_len = struct.pack('<H', len(self.workstation))
|
||||||
|
workstation_max_len = struct.pack('<H', len(self.workstation))
|
||||||
|
workstation_buffer_offset = struct.pack('<I', payload_offset)
|
||||||
|
payload_offset += len(self.workstation)
|
||||||
|
|
||||||
|
# LmChallengeResponseFields - 8 bytes
|
||||||
|
lm_challenge_response_len = struct.pack('<H', len(self.lm_challenge_response))
|
||||||
|
lm_challenge_response_max_len = struct.pack('<H', len(self.lm_challenge_response))
|
||||||
|
lm_challenge_response_buffer_offset = struct.pack('<I', payload_offset)
|
||||||
|
payload_offset += len(self.lm_challenge_response)
|
||||||
|
|
||||||
|
# NtChallengeResponseFields - 8 bytes
|
||||||
|
nt_challenge_response_len = struct.pack('<H', len(self.nt_challenge_response))
|
||||||
|
nt_challenge_response_max_len = struct.pack('<H', len(self.nt_challenge_response))
|
||||||
|
nt_challenge_response_buffer_offset = struct.pack('<I', payload_offset)
|
||||||
|
payload_offset += len(self.nt_challenge_response)
|
||||||
|
|
||||||
|
# EncryptedRandomSessionKeyFields - 8 bytes
|
||||||
|
encrypted_random_session_key_len = struct.pack('<H', len(self.encrypted_random_session_key))
|
||||||
|
encrypted_random_session_key_max_len = struct.pack('<H', len(self.encrypted_random_session_key))
|
||||||
|
encrypted_random_session_key_buffer_offset = struct.pack('<I', payload_offset)
|
||||||
|
payload_offset += len(self.encrypted_random_session_key)
|
||||||
|
|
||||||
|
# Payload - variable length
|
||||||
|
payload = self.domain_name
|
||||||
|
payload += self.user_name
|
||||||
|
payload += self.workstation
|
||||||
|
payload += self.lm_challenge_response
|
||||||
|
payload += self.nt_challenge_response
|
||||||
|
payload += self.encrypted_random_session_key
|
||||||
|
|
||||||
|
msg3 = self.signature
|
||||||
|
msg3 += self.message_type
|
||||||
|
msg3 += lm_challenge_response_len + lm_challenge_response_max_len + lm_challenge_response_buffer_offset
|
||||||
|
msg3 += nt_challenge_response_len + nt_challenge_response_max_len + nt_challenge_response_buffer_offset
|
||||||
|
msg3 += domain_name_len + domain_name_max_len + domain_name_buffer_offset
|
||||||
|
msg3 += user_name_len + user_name_max_len + user_name_buffer_offset
|
||||||
|
msg3 += workstation_len + workstation_max_len + workstation_buffer_offset
|
||||||
|
msg3 += encrypted_random_session_key_len + encrypted_random_session_key_max_len + encrypted_random_session_key_buffer_offset
|
||||||
|
msg3 += self.negotiate_flags
|
||||||
|
msg3 += self.version
|
||||||
|
msg3 += mic
|
||||||
|
|
||||||
|
# Adding the payload data to the message
|
||||||
|
msg3 += payload
|
||||||
|
|
||||||
|
return msg3
|
||||||
|
|
||||||
|
def add_mic(self, negotiate_message, challenge_message):
|
||||||
|
if self.target_info is not None:
|
||||||
|
av_flags = self.target_info[TargetInfo.MSV_AV_FLAGS]
|
||||||
|
|
||||||
|
if av_flags is not None and av_flags[1] == struct.pack("<L", AvFlags.MIC_PROVIDED):
|
||||||
|
self.mic = struct.pack("<IIII", 0, 0, 0, 0)
|
||||||
|
negotiate_data = negotiate_message.get_data()
|
||||||
|
challenge_data = challenge_message.get_data()
|
||||||
|
authenticate_data = self.get_data()
|
||||||
|
|
||||||
|
mic = hmac.new(self.exported_session_key,
|
||||||
|
(negotiate_data + challenge_data + authenticate_data)).digest()
|
||||||
|
self.mic = mic
|
||||||
|
|
||||||
|
def get_version(negotiate_flags):
|
||||||
|
# Check the negotiate_flag version is set, if it is make sure the version info is added to the data
|
||||||
|
if negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_VERSION:
|
||||||
|
# TODO: Get the major and minor version of Windows instead of using default values
|
||||||
|
product_major_version = struct.pack('<B', 6)
|
||||||
|
product_minor_version = struct.pack('<B', 1)
|
||||||
|
product_build = struct.pack('<H', 7601)
|
||||||
|
version_reserved = b'\0' * 3
|
||||||
|
ntlm_revision_current = struct.pack('<B', 15)
|
||||||
|
version = product_major_version + product_minor_version + product_build + version_reserved + ntlm_revision_current
|
||||||
|
else:
|
||||||
|
version = b'\0' * 8
|
||||||
|
|
||||||
|
return version
|
||||||
|
|
||||||
|
def get_random_export_session_key():
|
||||||
|
return os.urandom(16)
|
146
plugin/packages/wakatime/packages/ntlm_auth/ntlm.py
Normal file
146
plugin/packages/wakatime/packages/ntlm_auth/ntlm.py
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
# This library is free software: you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation, either
|
||||||
|
# version 3 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
# This library is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
from ntlm_auth.constants import NegotiateFlags
|
||||||
|
from ntlm_auth.messages import NegotiateMessage, ChallengeMessage, AuthenticateMessage
|
||||||
|
from ntlm_auth.session_security import SessionSecurity
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
utility functions for Microsoft NTLM authentication
|
||||||
|
|
||||||
|
References:
|
||||||
|
[MS-NLMP]: NT LAN Manager (NTLM) Authentication Protocol Specification
|
||||||
|
http://download.microsoft.com/download/a/e/6/ae6e4142-aa58-45c6-8dcf-a657e5900cd3/%5BMS-NLMP%5D.pdf
|
||||||
|
|
||||||
|
[MS-NTHT]: NTLM Over HTTP Protocol Specification
|
||||||
|
http://download.microsoft.com/download/a/e/6/ae6e4142-aa58-45c6-8dcf-a657e5900cd3/%5BMS-NTHT%5D.pdf
|
||||||
|
|
||||||
|
Cntlm Authentication Proxy
|
||||||
|
http://cntlm.awk.cz/
|
||||||
|
|
||||||
|
NTLM Authorization Proxy Server
|
||||||
|
http://sourceforge.net/projects/ntlmaps/
|
||||||
|
|
||||||
|
Optimized Attack for NTLM2 Session Response
|
||||||
|
http://www.blackhat.com/presentations/bh-asia-04/bh-jp-04-pdfs/bh-jp-04-seki.pdf
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Ntlm(object):
|
||||||
|
"""
|
||||||
|
Initialises the NTLM context to use when sending and receiving messages to and from the server. You should be
|
||||||
|
using this object as it supports NTLMv2 authenticate and it easier to use than before. It also brings in the
|
||||||
|
ability to use signing and sealing with session_security and generate a MIC structure.
|
||||||
|
|
||||||
|
:param ntlm_compatibility: The Lan Manager Compatibility Level to use withe the auth message - Default 3
|
||||||
|
This is set by an Administrator in the registry key
|
||||||
|
'HKLM\SYSTEM\CurrentControlSet\Control\Lsa\LmCompatibilityLevel'
|
||||||
|
The values correspond to the following;
|
||||||
|
0 : LM and NTLMv1
|
||||||
|
1 : LM, NTLMv1 and NTLMv1 with Extended Session Security
|
||||||
|
2 : NTLMv1 and NTLMv1 with Extended Session Security
|
||||||
|
3-5 : NTLMv2 Only
|
||||||
|
Note: Values 3 to 5 are no different as the client supports the same types
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
negotiate_flags: A NEGOTIATE structure that contains a set of bit flags. These flags are the options the client supports and are sent in the negotiate_message
|
||||||
|
ntlm_compatibility: The Lan Manager Compatibility Level, same as the input if supplied
|
||||||
|
negotiate_message: A NegotiateMessage object that is sent to the server
|
||||||
|
challenge_message: A ChallengeMessage object that has been created from the server response
|
||||||
|
authenticate_message: An AuthenticateMessage object that is sent to the server based on the ChallengeMessage
|
||||||
|
session_security: A SessionSecurity structure that can be used to sign and seal messages sent after the authentication challenge
|
||||||
|
"""
|
||||||
|
def __init__(self, ntlm_compatibility=3):
|
||||||
|
self.ntlm_compatibility = ntlm_compatibility
|
||||||
|
|
||||||
|
# Setting up our flags so the challenge message returns the target info block if supported
|
||||||
|
self.negotiate_flags = NegotiateFlags.NTLMSSP_NEGOTIATE_TARGET_INFO | \
|
||||||
|
NegotiateFlags.NTLMSSP_NEGOTIATE_128 | \
|
||||||
|
NegotiateFlags.NTLMSSP_NEGOTIATE_56 | \
|
||||||
|
NegotiateFlags.NTLMSSP_NEGOTIATE_UNICODE | \
|
||||||
|
NegotiateFlags.NTLMSSP_NEGOTIATE_VERSION | \
|
||||||
|
NegotiateFlags.NTLMSSP_NEGOTIATE_KEY_EXCH | \
|
||||||
|
NegotiateFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN | \
|
||||||
|
NegotiateFlags.NTLMSSP_NEGOTIATE_SIGN | \
|
||||||
|
NegotiateFlags.NTLMSSP_NEGOTIATE_SEAL
|
||||||
|
|
||||||
|
# Setting the message types based on the ntlm_compatibility level
|
||||||
|
self._set_ntlm_compatibility_flags(self.ntlm_compatibility)
|
||||||
|
|
||||||
|
self.negotiate_message = None
|
||||||
|
self.challenge_message = None
|
||||||
|
self.authenticate_message = None
|
||||||
|
self.session_security = None
|
||||||
|
|
||||||
|
|
||||||
|
def create_negotiate_message(self, domain_name=None, workstation=None):
|
||||||
|
"""
|
||||||
|
Create an NTLM NEGOTIATE_MESSAGE
|
||||||
|
|
||||||
|
:param domain_name: The domain name of the user account we are authenticating with, default is None
|
||||||
|
:param worksation: The workstation we are using to authenticate with, default is None
|
||||||
|
:return: A base64 encoded string of the NEGOTIATE_MESSAGE
|
||||||
|
"""
|
||||||
|
self.negotiate_message = NegotiateMessage(self.negotiate_flags, domain_name, workstation)
|
||||||
|
|
||||||
|
return base64.b64encode(self.negotiate_message.get_data())
|
||||||
|
|
||||||
|
def parse_challenge_message(self, msg2):
|
||||||
|
"""
|
||||||
|
Parse the NTLM CHALLENGE_MESSAGE from the server and add it to the Ntlm context fields
|
||||||
|
|
||||||
|
:param msg2: A base64 encoded string of the CHALLENGE_MESSAGE
|
||||||
|
"""
|
||||||
|
msg2 = base64.b64decode(msg2)
|
||||||
|
self.challenge_message = ChallengeMessage(msg2)
|
||||||
|
|
||||||
|
def create_authenticate_message(self, user_name, password, domain_name=None, workstation=None, server_certificate_hash=None):
|
||||||
|
"""
|
||||||
|
Create an NTLM AUTHENTICATE_MESSAGE based on the Ntlm context and the previous messages sent and received
|
||||||
|
|
||||||
|
:param user_name: The user name of the user we are trying to authenticate with
|
||||||
|
:param password: The password of the user we are trying to authenticate with
|
||||||
|
:param domain_name: The domain name of the user account we are authenticated with, default is None
|
||||||
|
:param workstation: The workstation we are using to authenticate with, default is None
|
||||||
|
:param server_certificate_hash: The SHA256 hash string of the server certificate (DER encoded) NTLM is authenticating to. Used for Channel
|
||||||
|
Binding Tokens. If nothing is supplied then the CBT hash will not be sent. See messages.py AuthenticateMessage
|
||||||
|
for more details
|
||||||
|
:return: A base64 encoded string of the AUTHENTICATE_MESSAGE
|
||||||
|
"""
|
||||||
|
self.authenticate_message = AuthenticateMessage(user_name, password, domain_name, workstation,
|
||||||
|
self.challenge_message, self.ntlm_compatibility,
|
||||||
|
server_certificate_hash)
|
||||||
|
self.authenticate_message.add_mic(self.negotiate_message, self.challenge_message)
|
||||||
|
|
||||||
|
# Setups up the session_security context used to sign and seal messages if wanted
|
||||||
|
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SEAL or self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SIGN:
|
||||||
|
self.session_security = SessionSecurity(struct.unpack("<I", self.authenticate_message.negotiate_flags)[0],
|
||||||
|
self.authenticate_message.exported_session_key)
|
||||||
|
|
||||||
|
return base64.b64encode(self.authenticate_message.get_data())
|
||||||
|
|
||||||
|
def _set_ntlm_compatibility_flags(self, ntlm_compatibility):
|
||||||
|
if (ntlm_compatibility >= 0) and (ntlm_compatibility <= 5):
|
||||||
|
if ntlm_compatibility == 0:
|
||||||
|
self.negotiate_flags |= NegotiateFlags.NTLMSSP_NEGOTIATE_NTLM | \
|
||||||
|
NegotiateFlags.NTLMSSP_NEGOTIATE_LM_KEY
|
||||||
|
elif ntlm_compatibility == 1:
|
||||||
|
self.negotiate_flags |= NegotiateFlags.NTLMSSP_NEGOTIATE_NTLM | \
|
||||||
|
NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
|
||||||
|
else:
|
||||||
|
self.negotiate_flags |= NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
|
||||||
|
else:
|
||||||
|
raise Exception("Unknown ntlm_compatibility level - expecting value between 0 and 5")
|
51
plugin/packages/wakatime/packages/ntlm_auth/rc4.py
Normal file
51
plugin/packages/wakatime/packages/ntlm_auth/rc4.py
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
# This library is free software: you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation, either
|
||||||
|
# version 3 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
# This library is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||||
|
|
||||||
|
class ARC4(object):
|
||||||
|
state = None
|
||||||
|
i = 0
|
||||||
|
j = 0
|
||||||
|
|
||||||
|
def __init__(self, key):
|
||||||
|
# Split up the key into a list
|
||||||
|
if isinstance(key, str):
|
||||||
|
key = [ord(c) for c in key]
|
||||||
|
else:
|
||||||
|
key = [c for c in key]
|
||||||
|
|
||||||
|
#Key-scheduling algorithm (KSA)
|
||||||
|
self.state = [n for n in range(256)]
|
||||||
|
j = 0
|
||||||
|
for i in range(256):
|
||||||
|
j = (j + self.state[i] + key[i % len(key)]) % 256
|
||||||
|
self.state[i], self.state[j] = self.state[j], self.state[i]
|
||||||
|
|
||||||
|
def update(self, value):
|
||||||
|
chars = []
|
||||||
|
random_gen = self._random_generator()
|
||||||
|
for char in value:
|
||||||
|
if isinstance(value, str):
|
||||||
|
byte = ord(char)
|
||||||
|
else:
|
||||||
|
byte = char
|
||||||
|
updated_byte = byte ^ next(random_gen)
|
||||||
|
chars.append(updated_byte)
|
||||||
|
return bytes(bytearray(chars))
|
||||||
|
|
||||||
|
def _random_generator(self):
|
||||||
|
#Pseudo-Random Generation Algorithm (PRGA)
|
||||||
|
while True:
|
||||||
|
self.i = (self.i + 1) % 256
|
||||||
|
self.j = (self.j + self.state[self.i]) % 256
|
||||||
|
self.state[self.i], self.state[self.j] = self.state[self.j], self.state[self.i]
|
||||||
|
yield self.state[(self.state[self.i] + self.state[self.j]) % 256]
|
250
plugin/packages/wakatime/packages/ntlm_auth/session_security.py
Normal file
250
plugin/packages/wakatime/packages/ntlm_auth/session_security.py
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
# This library is free software: you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation, either
|
||||||
|
# version 3 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
# This library is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||||
|
|
||||||
|
import binascii
|
||||||
|
import hmac
|
||||||
|
import struct
|
||||||
|
import ntlm_auth.compute_keys as compkeys
|
||||||
|
from ntlm_auth.constants import NegotiateFlags, SignSealConstants
|
||||||
|
from ntlm_auth.rc4 import ARC4
|
||||||
|
|
||||||
|
|
||||||
|
class _NtlmMessageSignature1(object):
|
||||||
|
EXPECTED_BODY_LENGTH = 16
|
||||||
|
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
2.2.2.9.1 NTLMSSP_MESSAGE_SIGNATURE
|
||||||
|
This version of the NTLMSSP_MESSAGE_SIGNATURE structure MUST be used when the
|
||||||
|
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY flag is not negotiated.
|
||||||
|
|
||||||
|
:param random_pad: A 4-byte array that contains the random pad for the emssage
|
||||||
|
:param checksum: A 4-byte array that contains the checksum for the message
|
||||||
|
:param seq_num: A 32-bit unsigned integer that contains the NTLM sequence number for this application message
|
||||||
|
"""
|
||||||
|
def __init__(self, random_pad, checksum, seq_num):
|
||||||
|
self.version = struct.pack("<I", 1)
|
||||||
|
self.random_pad = random_pad
|
||||||
|
self.checksum = checksum
|
||||||
|
self.seq_num = seq_num
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
signature = self.version
|
||||||
|
signature += self.random_pad
|
||||||
|
signature += self.checksum
|
||||||
|
signature += self.seq_num
|
||||||
|
|
||||||
|
assert self.EXPECTED_BODY_LENGTH == len(signature), "BODY_LENGTH: %d != signature: %d" % (
|
||||||
|
self.EXPECTED_BODY_LENGTH, len(signature))
|
||||||
|
|
||||||
|
return signature
|
||||||
|
|
||||||
|
class _NtlmMessageSignature2(object):
|
||||||
|
EXPECTED_BODY_LENGTH = 16
|
||||||
|
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
2.2.2.9.2 NTLMSSP_MESSAGE_SIGNATURE for Extended Session Security
|
||||||
|
This version of the NTLMSSP_MESSAGE_SIGNATURE structure MUST be used when the
|
||||||
|
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY flag is negotiated
|
||||||
|
|
||||||
|
:param checksum: An 8-byte array that contains the checksum for the message
|
||||||
|
:param seq_num: A 32-bit unsigned integer that contains the NTLM sequence number for this application message
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, checksum, seq_num):
|
||||||
|
self.version = struct.pack("<I", 1)
|
||||||
|
self.checksum = checksum
|
||||||
|
self.seq_num = seq_num
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
signature = self.version
|
||||||
|
signature += self.checksum
|
||||||
|
signature += self.seq_num
|
||||||
|
|
||||||
|
assert self.EXPECTED_BODY_LENGTH == len(signature), "BODY_LENGTH: %d != signature: %d" % (
|
||||||
|
self.EXPECTED_BODY_LENGTH, len(signature))
|
||||||
|
|
||||||
|
return signature
|
||||||
|
|
||||||
|
class SessionSecurity(object):
|
||||||
|
"""
|
||||||
|
Initialises a security session context that can be used by libraries that call ntlm-auth to sign and seal
|
||||||
|
messages send to the server as well as verify and unseal messages that have been received from the server.
|
||||||
|
This is similar to the GSS_Wrap functions specified in the MS-NLMP document which does the same task.
|
||||||
|
|
||||||
|
:param negotiate_flags: The negotiate flag structure that has been negotiated with the server
|
||||||
|
:param exported_session_key: A 128-bit session key used to derive signing and sealing keys
|
||||||
|
:param source: The source of the message, only used in test scenarios when testing out a server sealing and unsealing
|
||||||
|
"""
|
||||||
|
def __init__(self, negotiate_flags, exported_session_key, source="client"):
|
||||||
|
self.negotiate_flags = negotiate_flags
|
||||||
|
self.outgoing_seq_num = 0
|
||||||
|
self.incoming_seq_num = 0
|
||||||
|
|
||||||
|
client_sealing_key = compkeys.get_seal_key(self.negotiate_flags, exported_session_key, SignSealConstants.CLIENT_SEALING)
|
||||||
|
server_sealing_key = compkeys.get_seal_key(self.negotiate_flags, exported_session_key, SignSealConstants.SERVER_SEALING)
|
||||||
|
|
||||||
|
if source == "client":
|
||||||
|
self.outgoing_signing_key = compkeys.get_sign_key(exported_session_key, SignSealConstants.CLIENT_SIGNING)
|
||||||
|
self.incoming_signing_key = compkeys.get_sign_key(exported_session_key, SignSealConstants.SERVER_SIGNING)
|
||||||
|
self.outgoing_handle = ARC4(client_sealing_key)
|
||||||
|
self.incoming_handle = ARC4(server_sealing_key)
|
||||||
|
elif source == "server":
|
||||||
|
self.outgoing_signing_key = compkeys.get_sign_key(exported_session_key, SignSealConstants.SERVER_SIGNING)
|
||||||
|
self.incoming_signing_key = compkeys.get_sign_key(exported_session_key, SignSealConstants.CLIENT_SIGNING)
|
||||||
|
self.outgoing_handle = ARC4(server_sealing_key)
|
||||||
|
self.incoming_handle = ARC4(client_sealing_key)
|
||||||
|
else:
|
||||||
|
raise Exception("Invalid source parameter %s, must be client or server" % source)
|
||||||
|
|
||||||
|
def wrap(self, message):
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
3.4.6 GSS_WrapEx()
|
||||||
|
Emulates the GSS_Wrap() implementation to sign and seal messages if the correct flags
|
||||||
|
are set.
|
||||||
|
|
||||||
|
@param message: The message data that will be wrapped
|
||||||
|
@return message: The message that has been sealed if flags are set
|
||||||
|
@return signature: The signature of the message, None if flags are not set
|
||||||
|
"""
|
||||||
|
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SEAL:
|
||||||
|
encrypted_message = self._seal_message(message)
|
||||||
|
signature = self._get_signature(message)
|
||||||
|
message = encrypted_message
|
||||||
|
|
||||||
|
elif self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SIGN:
|
||||||
|
signature = self._get_signature(message)
|
||||||
|
else:
|
||||||
|
signature = None
|
||||||
|
|
||||||
|
return message, signature
|
||||||
|
|
||||||
|
def unwrap(self, message, signature):
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
3.4.7 GSS_UnwrapEx()
|
||||||
|
Emulates the GSS_Unwrap() implementation to unseal messages and verify the signature
|
||||||
|
sent matches what has been computed locally. Will throw an Exception if the signature
|
||||||
|
doesn't match
|
||||||
|
|
||||||
|
@param message: The message data received from the server
|
||||||
|
@param signature: The signature of the message
|
||||||
|
@return message: The message that has been unsealed if flags are set
|
||||||
|
"""
|
||||||
|
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SEAL:
|
||||||
|
message = self._unseal_message(message)
|
||||||
|
self._verify_signature(message, signature)
|
||||||
|
|
||||||
|
elif self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SIGN:
|
||||||
|
self._verify_signature(message, signature)
|
||||||
|
|
||||||
|
return message
|
||||||
|
|
||||||
|
def _seal_message(self, message):
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
3.4.3 Message Confidentiality
|
||||||
|
Will generate an encrypted message using RC4 based on the ClientSealingKey
|
||||||
|
|
||||||
|
@param message: The message to be sealed (encrypted)
|
||||||
|
@return encrypted_message: The encrypted message
|
||||||
|
"""
|
||||||
|
encrypted_message = self.outgoing_handle.update(message)
|
||||||
|
return encrypted_message
|
||||||
|
|
||||||
|
def _unseal_message(self, message):
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
3.4.3 Message Confidentiality
|
||||||
|
Will generate a dencrypted message using RC4 based on the ServerSealingKey
|
||||||
|
|
||||||
|
@param message: The message to be unsealed (dencrypted)
|
||||||
|
@return decrypted_message: The decrypted message
|
||||||
|
"""
|
||||||
|
decrypted_message = self.incoming_handle.update(message)
|
||||||
|
return decrypted_message
|
||||||
|
|
||||||
|
def _get_signature(self, message):
|
||||||
|
"""
|
||||||
|
[MS-NLMP] v28.0 2016-07-14
|
||||||
|
|
||||||
|
3.4.4 Message Signature Functions
|
||||||
|
Will create the signature based on the message to send to the server. Depending on the negotiate_flags
|
||||||
|
set this could either be an NTLMv1 signature or NTLMv2 with Extended Session Security signature.
|
||||||
|
|
||||||
|
@param message: The message data that will be signed
|
||||||
|
@return signature: Either _NtlmMessageSignature1 or _NtlmMessageSignature2 depending on the flags set
|
||||||
|
"""
|
||||||
|
signature = calc_signature(message, self.negotiate_flags, self.outgoing_signing_key, self.outgoing_seq_num, self.outgoing_handle)
|
||||||
|
self.outgoing_seq_num += 1
|
||||||
|
|
||||||
|
return signature.get_data()
|
||||||
|
|
||||||
|
def _verify_signature(self, message, signature):
|
||||||
|
"""
|
||||||
|
Will verify that the signature received from the server matches up with the expected signature
|
||||||
|
computed locally. Will throw an exception if they do not match
|
||||||
|
|
||||||
|
@param message: The message data that is received from the server
|
||||||
|
@param signature: The signature of the message received from the server
|
||||||
|
"""
|
||||||
|
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||||
|
actual_checksum = signature[4:12]
|
||||||
|
actual_seq_num = struct.unpack("<I", signature[12:16])[0]
|
||||||
|
else:
|
||||||
|
actual_checksum = signature[8:12]
|
||||||
|
actual_seq_num = struct.unpack("<I", signature[12:16])[0]
|
||||||
|
|
||||||
|
expected_signature = calc_signature(message, self.negotiate_flags, self.incoming_signing_key, self.incoming_seq_num, self.incoming_handle)
|
||||||
|
expected_checksum = expected_signature.checksum
|
||||||
|
expected_seq_num = struct.unpack("<I", expected_signature.seq_num)[0]
|
||||||
|
|
||||||
|
if actual_checksum != expected_checksum:
|
||||||
|
raise Exception("The signature checksum does not match, message has been altered")
|
||||||
|
|
||||||
|
if actual_seq_num != expected_seq_num:
|
||||||
|
raise Exception("The signature sequence number does not match up, message not received in the correct sequence")
|
||||||
|
|
||||||
|
self.incoming_seq_num += 1
|
||||||
|
|
||||||
|
|
||||||
|
def calc_signature(message, negotiate_flags, signing_key, seq_num, handle):
|
||||||
|
seq_num = struct.pack("<I", seq_num)
|
||||||
|
if negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||||
|
checksum_hmac = hmac.new(signing_key, seq_num + message)
|
||||||
|
if negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_KEY_EXCH:
|
||||||
|
checksum = handle.update(checksum_hmac.digest()[:8])
|
||||||
|
else:
|
||||||
|
checksum = checksum_hmac.digest()[:8]
|
||||||
|
|
||||||
|
signature = _NtlmMessageSignature2(checksum, seq_num)
|
||||||
|
|
||||||
|
else:
|
||||||
|
message_crc = binascii.crc32(message) % (1 << 32)
|
||||||
|
checksum = struct.pack("<I", message_crc)
|
||||||
|
random_pad = handle.update(struct.pack("<I", 0))
|
||||||
|
checksum = handle.update(checksum)
|
||||||
|
seq_num = handle.update(seq_num)
|
||||||
|
random_pad = struct.pack("<I", 0)
|
||||||
|
|
||||||
|
signature = _NtlmMessageSignature1(random_pad, checksum, seq_num)
|
||||||
|
|
||||||
|
return signature
|
68
plugin/packages/wakatime/packages/ntlm_auth/target_info.py
Normal file
68
plugin/packages/wakatime/packages/ntlm_auth/target_info.py
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
"""
|
||||||
|
Original Author: Ian Clegg
|
||||||
|
Project: ntlmlib
|
||||||
|
URL: https://github.com/ianclegg/ntlmlib
|
||||||
|
License: Apache 2.0 License
|
||||||
|
Notes: Most of this code has been copied from the messages.py in the ntlmlib repo.
|
||||||
|
Some minor changes such as the name of the AV Pairs and extra comments have been added.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import struct
|
||||||
|
try:
|
||||||
|
from collections import OrderedDict
|
||||||
|
except ImportError:
|
||||||
|
from ordereddict import OrderedDict
|
||||||
|
|
||||||
|
class TargetInfo(object):
|
||||||
|
MSV_AV_EOL = 0x00
|
||||||
|
MSV_AV_NB_COMPUTER_NAME = 0x01
|
||||||
|
MSV_AV_NB_DOMAIN_NAME = 0x02
|
||||||
|
MSV_AV_DNS_COMPUTER_NAME = 0x03
|
||||||
|
MSV_AV_DNS_DOMAIN_NAME = 0x04
|
||||||
|
MSV_AV_DNS_TREE_NAME = 0x05
|
||||||
|
MSV_AV_FLAGS = 0x06
|
||||||
|
MSV_AV_TIMESTAMP = 0x07
|
||||||
|
MSV_AV_SINGLE_HOST = 0x08
|
||||||
|
MSV_AV_TARGET_NAME = 0x09
|
||||||
|
MSV_AV_CHANNEL_BINDINGS = 0x0a
|
||||||
|
|
||||||
|
def __init__(self, data=None):
|
||||||
|
self.fields = OrderedDict()
|
||||||
|
if data is not None:
|
||||||
|
self.from_string(data)
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self.fields[key] = (len(value), value)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if key in self.fields:
|
||||||
|
return self.fields[key]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
del self.fields[key]
|
||||||
|
|
||||||
|
def from_string(self, data):
|
||||||
|
attribute_type = 0xff
|
||||||
|
while attribute_type is not TargetInfo.MSV_AV_EOL:
|
||||||
|
# Parse the Attribute Value pair from the structure
|
||||||
|
attribute_type = struct.unpack('<H', data[:struct.calcsize('<H')])[0]
|
||||||
|
data = data[struct.calcsize('<H'):]
|
||||||
|
length = struct.unpack('<H', data[:struct.calcsize('<H')])[0]
|
||||||
|
data = data[struct.calcsize('<H'):]
|
||||||
|
# Add a new field to the object for the parse attribute value
|
||||||
|
self.fields[attribute_type] = (length, data[:length])
|
||||||
|
data = data[length:]
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
if TargetInfo.MSV_AV_EOL in self.fields:
|
||||||
|
del self.fields[TargetInfo.MSV_AV_EOL]
|
||||||
|
|
||||||
|
data = b''
|
||||||
|
for i in self.fields.keys():
|
||||||
|
data += struct.pack('<HH', i, self[i][0])
|
||||||
|
data += self[i][1]
|
||||||
|
|
||||||
|
# end with a NTLMSSP_AV_EOL
|
||||||
|
data += struct.pack('<HH', TargetInfo.MSV_AV_EOL, 0)
|
||||||
|
return data
|
|
@ -450,6 +450,7 @@ class TypeScriptLexer(RegexLexer):
|
||||||
aliases = ['ts', 'typescript']
|
aliases = ['ts', 'typescript']
|
||||||
filenames = ['*.ts', '*.tsx']
|
filenames = ['*.ts', '*.tsx']
|
||||||
mimetypes = ['text/x-typescript']
|
mimetypes = ['text/x-typescript']
|
||||||
|
priority = 0.11
|
||||||
|
|
||||||
flags = re.DOTALL | re.MULTILINE
|
flags = re.DOTALL | re.MULTILINE
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,8 @@ on how to use these modules.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# The IANA (nee Olson) database is updated several times a year.
|
# The IANA (nee Olson) database is updated several times a year.
|
||||||
OLSON_VERSION = '2016f'
|
OLSON_VERSION = '2016j'
|
||||||
VERSION = '2016.6.1' # Switching to pip compatible version numbering.
|
VERSION = '2016.10' # Switching to pip compatible version numbering.
|
||||||
__version__ = VERSION
|
__version__ = VERSION
|
||||||
|
|
||||||
OLSEN_VERSION = OLSON_VERSION # Old releases had this misspelling
|
OLSEN_VERSION = OLSON_VERSION # Old releases had this misspelling
|
||||||
|
@ -731,6 +731,7 @@ all_timezones = \
|
||||||
'Asia/Aqtobe',
|
'Asia/Aqtobe',
|
||||||
'Asia/Ashgabat',
|
'Asia/Ashgabat',
|
||||||
'Asia/Ashkhabad',
|
'Asia/Ashkhabad',
|
||||||
|
'Asia/Atyrau',
|
||||||
'Asia/Baghdad',
|
'Asia/Baghdad',
|
||||||
'Asia/Bahrain',
|
'Asia/Bahrain',
|
||||||
'Asia/Baku',
|
'Asia/Baku',
|
||||||
|
@ -751,6 +752,7 @@ all_timezones = \
|
||||||
'Asia/Dili',
|
'Asia/Dili',
|
||||||
'Asia/Dubai',
|
'Asia/Dubai',
|
||||||
'Asia/Dushanbe',
|
'Asia/Dushanbe',
|
||||||
|
'Asia/Famagusta',
|
||||||
'Asia/Gaza',
|
'Asia/Gaza',
|
||||||
'Asia/Harbin',
|
'Asia/Harbin',
|
||||||
'Asia/Hebron',
|
'Asia/Hebron',
|
||||||
|
@ -816,6 +818,7 @@ all_timezones = \
|
||||||
'Asia/Vientiane',
|
'Asia/Vientiane',
|
||||||
'Asia/Vladivostok',
|
'Asia/Vladivostok',
|
||||||
'Asia/Yakutsk',
|
'Asia/Yakutsk',
|
||||||
|
'Asia/Yangon',
|
||||||
'Asia/Yekaterinburg',
|
'Asia/Yekaterinburg',
|
||||||
'Asia/Yerevan',
|
'Asia/Yerevan',
|
||||||
'Atlantic/Azores',
|
'Atlantic/Azores',
|
||||||
|
@ -955,6 +958,7 @@ all_timezones = \
|
||||||
'Europe/Samara',
|
'Europe/Samara',
|
||||||
'Europe/San_Marino',
|
'Europe/San_Marino',
|
||||||
'Europe/Sarajevo',
|
'Europe/Sarajevo',
|
||||||
|
'Europe/Saratov',
|
||||||
'Europe/Simferopol',
|
'Europe/Simferopol',
|
||||||
'Europe/Skopje',
|
'Europe/Skopje',
|
||||||
'Europe/Sofia',
|
'Europe/Sofia',
|
||||||
|
@ -1301,6 +1305,7 @@ common_timezones = \
|
||||||
'Asia/Aqtau',
|
'Asia/Aqtau',
|
||||||
'Asia/Aqtobe',
|
'Asia/Aqtobe',
|
||||||
'Asia/Ashgabat',
|
'Asia/Ashgabat',
|
||||||
|
'Asia/Atyrau',
|
||||||
'Asia/Baghdad',
|
'Asia/Baghdad',
|
||||||
'Asia/Bahrain',
|
'Asia/Bahrain',
|
||||||
'Asia/Baku',
|
'Asia/Baku',
|
||||||
|
@ -1317,6 +1322,7 @@ common_timezones = \
|
||||||
'Asia/Dili',
|
'Asia/Dili',
|
||||||
'Asia/Dubai',
|
'Asia/Dubai',
|
||||||
'Asia/Dushanbe',
|
'Asia/Dushanbe',
|
||||||
|
'Asia/Famagusta',
|
||||||
'Asia/Gaza',
|
'Asia/Gaza',
|
||||||
'Asia/Hebron',
|
'Asia/Hebron',
|
||||||
'Asia/Ho_Chi_Minh',
|
'Asia/Ho_Chi_Minh',
|
||||||
|
@ -1351,7 +1357,6 @@ common_timezones = \
|
||||||
'Asia/Pyongyang',
|
'Asia/Pyongyang',
|
||||||
'Asia/Qatar',
|
'Asia/Qatar',
|
||||||
'Asia/Qyzylorda',
|
'Asia/Qyzylorda',
|
||||||
'Asia/Rangoon',
|
|
||||||
'Asia/Riyadh',
|
'Asia/Riyadh',
|
||||||
'Asia/Sakhalin',
|
'Asia/Sakhalin',
|
||||||
'Asia/Samarkand',
|
'Asia/Samarkand',
|
||||||
|
@ -1372,6 +1377,7 @@ common_timezones = \
|
||||||
'Asia/Vientiane',
|
'Asia/Vientiane',
|
||||||
'Asia/Vladivostok',
|
'Asia/Vladivostok',
|
||||||
'Asia/Yakutsk',
|
'Asia/Yakutsk',
|
||||||
|
'Asia/Yangon',
|
||||||
'Asia/Yekaterinburg',
|
'Asia/Yekaterinburg',
|
||||||
'Asia/Yerevan',
|
'Asia/Yerevan',
|
||||||
'Atlantic/Azores',
|
'Atlantic/Azores',
|
||||||
|
@ -1444,6 +1450,7 @@ common_timezones = \
|
||||||
'Europe/Samara',
|
'Europe/Samara',
|
||||||
'Europe/San_Marino',
|
'Europe/San_Marino',
|
||||||
'Europe/Sarajevo',
|
'Europe/Sarajevo',
|
||||||
|
'Europe/Saratov',
|
||||||
'Europe/Simferopol',
|
'Europe/Simferopol',
|
||||||
'Europe/Skopje',
|
'Europe/Skopje',
|
||||||
'Europe/Sofia',
|
'Europe/Sofia',
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
plugin/packages/wakatime/packages/pytz/zoneinfo/Asia/Atyrau
Normal file
BIN
plugin/packages/wakatime/packages/pytz/zoneinfo/Asia/Atyrau
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
plugin/packages/wakatime/packages/pytz/zoneinfo/Asia/Famagusta
Normal file
BIN
plugin/packages/wakatime/packages/pytz/zoneinfo/Asia/Famagusta
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
plugin/packages/wakatime/packages/pytz/zoneinfo/Asia/Yangon
Normal file
BIN
plugin/packages/wakatime/packages/pytz/zoneinfo/Asia/Yangon
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue