446 lines
15 KiB
JavaScript
446 lines
15 KiB
JavaScript
var crypto = require('crypto');
|
|
|
|
var flags = {
|
|
NTLM_NegotiateUnicode: 0x00000001,
|
|
NTLM_NegotiateOEM: 0x00000002,
|
|
NTLM_RequestTarget: 0x00000004,
|
|
NTLM_Unknown9: 0x00000008,
|
|
NTLM_NegotiateSign: 0x00000010,
|
|
NTLM_NegotiateSeal: 0x00000020,
|
|
NTLM_NegotiateDatagram: 0x00000040,
|
|
NTLM_NegotiateLanManagerKey: 0x00000080,
|
|
NTLM_Unknown8: 0x00000100,
|
|
NTLM_NegotiateNTLM: 0x00000200,
|
|
NTLM_NegotiateNTOnly: 0x00000400,
|
|
NTLM_Anonymous: 0x00000800,
|
|
NTLM_NegotiateOemDomainSupplied: 0x00001000,
|
|
NTLM_NegotiateOemWorkstationSupplied: 0x00002000,
|
|
NTLM_Unknown6: 0x00004000,
|
|
NTLM_NegotiateAlwaysSign: 0x00008000,
|
|
NTLM_TargetTypeDomain: 0x00010000,
|
|
NTLM_TargetTypeServer: 0x00020000,
|
|
NTLM_TargetTypeShare: 0x00040000,
|
|
NTLM_NegotiateExtendedSecurity: 0x00080000,
|
|
NTLM_NegotiateIdentify: 0x00100000,
|
|
NTLM_Unknown5: 0x00200000,
|
|
NTLM_RequestNonNTSessionKey: 0x00400000,
|
|
NTLM_NegotiateTargetInfo: 0x00800000,
|
|
NTLM_Unknown4: 0x01000000,
|
|
NTLM_NegotiateVersion: 0x02000000,
|
|
NTLM_Unknown3: 0x04000000,
|
|
NTLM_Unknown2: 0x08000000,
|
|
NTLM_Unknown1: 0x10000000,
|
|
NTLM_Negotiate128: 0x20000000,
|
|
NTLM_NegotiateKeyExchange: 0x40000000,
|
|
NTLM_Negotiate56: 0x80000000
|
|
}
|
|
var typeflags = {
|
|
NTLM_TYPE1_FLAGS: flags.NTLM_NegotiateUnicode
|
|
+ flags.NTLM_NegotiateOEM
|
|
+ flags.NTLM_RequestTarget
|
|
+ flags.NTLM_NegotiateNTLM
|
|
+ flags.NTLM_NegotiateOemDomainSupplied
|
|
+ flags.NTLM_NegotiateOemWorkstationSupplied
|
|
+ flags.NTLM_NegotiateAlwaysSign
|
|
+ flags.NTLM_NegotiateExtendedSecurity
|
|
+ flags.NTLM_NegotiateVersion
|
|
+ flags.NTLM_Negotiate128
|
|
+ flags.NTLM_Negotiate56,
|
|
|
|
NTLM_TYPE2_FLAGS: flags.NTLM_NegotiateUnicode
|
|
+ flags.NTLM_RequestTarget
|
|
+ flags.NTLM_NegotiateNTLM
|
|
+ flags.NTLM_NegotiateAlwaysSign
|
|
+ flags.NTLM_NegotiateExtendedSecurity
|
|
+ flags.NTLM_NegotiateTargetInfo
|
|
+ flags.NTLM_NegotiateVersion
|
|
+ flags.NTLM_Negotiate128
|
|
+ flags.NTLM_Negotiate56
|
|
}
|
|
|
|
exports.createType1Message = function (options) {
|
|
var domain = escape(options.domain.toUpperCase());
|
|
var workstation = escape(options.workstation.toUpperCase());
|
|
var protocol = 'NTLMSSP\0';
|
|
|
|
var BODY_LENGTH = 40;
|
|
|
|
var type1flags = typeflags.NTLM_TYPE1_FLAGS;
|
|
if (!domain || domain == '')
|
|
type1flags = type1flags - flags.NTLM_NegotiateOemDomainSupplied;
|
|
|
|
var pos = 0;
|
|
var buf = new Buffer(BODY_LENGTH + domain.length + workstation.length);
|
|
|
|
|
|
buf.write(protocol, pos, protocol.length);
|
|
pos += protocol.length; // protocol
|
|
buf.writeUInt32LE(1, pos);
|
|
pos += 4; // type 1
|
|
buf.writeUInt32LE(type1flags, pos);
|
|
pos += 4; // TYPE1 flag
|
|
|
|
buf.writeUInt16LE(domain.length, pos);
|
|
pos += 2; // domain length
|
|
buf.writeUInt16LE(domain.length, pos);
|
|
pos += 2; // domain max length
|
|
buf.writeUInt32LE(BODY_LENGTH + workstation.length, pos);
|
|
pos += 4; // domain buffer offset
|
|
|
|
buf.writeUInt16LE(workstation.length, pos);
|
|
pos += 2; // workstation length
|
|
buf.writeUInt16LE(workstation.length, pos);
|
|
pos += 2; // workstation max length
|
|
buf.writeUInt32LE(BODY_LENGTH, pos);
|
|
pos += 4; // workstation buffer offset
|
|
|
|
buf.writeUInt8(5, pos);
|
|
pos += 1; //ProductMajorVersion
|
|
buf.writeUInt8(1, pos);
|
|
pos += 1; //ProductMinorVersion
|
|
buf.writeUInt16LE(2600, pos);
|
|
pos += 2; //ProductBuild
|
|
|
|
buf.writeUInt8(0, pos);
|
|
pos += 1; //VersionReserved1
|
|
buf.writeUInt8(0, pos);
|
|
pos += 1; //VersionReserved2
|
|
buf.writeUInt8(0, pos);
|
|
pos += 1; //VersionReserved3
|
|
buf.writeUInt8(15, pos);
|
|
pos += 1; //NTLMRevisionCurrent
|
|
|
|
buf.write(workstation, pos, workstation.length, 'ascii');
|
|
pos += workstation.length; // workstation string
|
|
buf.write(domain, pos, domain.length, 'ascii');
|
|
pos += domain.length;
|
|
|
|
return 'NTLM ' + buf.toString('base64');
|
|
}
|
|
|
|
exports.parseType2Message = function (rawmsg, callback) {
|
|
var match = rawmsg.match(/NTLM (.+)?/);
|
|
if (!match || !match[1])
|
|
return callback(new Error("Couldn't find NTLM in the message type2 comming from the server"));
|
|
|
|
var buf = new Buffer(match[1], 'base64');
|
|
|
|
var msg = {};
|
|
|
|
msg.signature = buf.slice(0, 8);
|
|
msg.type = buf.readInt16LE(8);
|
|
|
|
if (msg.type != 2)
|
|
return callback(new Error("Server didn't return a type 2 message"));
|
|
|
|
msg.targetNameLen = buf.readInt16LE(12);
|
|
msg.targetNameMaxLen = buf.readInt16LE(14);
|
|
msg.targetNameOffset = buf.readInt32LE(16);
|
|
msg.targetName = buf.slice(msg.targetNameOffset, msg.targetNameOffset + msg.targetNameMaxLen);
|
|
|
|
msg.negotiateFlags = buf.readInt32LE(20);
|
|
msg.serverChallenge = buf.slice(24, 32);
|
|
msg.reserved = buf.slice(32, 40);
|
|
|
|
if (msg.negotiateFlags & flags.NTLM_NegotiateTargetInfo) {
|
|
msg.targetInfoLen = buf.readInt16LE(40);
|
|
msg.targetInfoMaxLen = buf.readInt16LE(42);
|
|
msg.targetInfoOffset = buf.readInt32LE(44);
|
|
msg.targetInfo = buf.slice(msg.targetInfoOffset, msg.targetInfoOffset + msg.targetInfoLen);
|
|
}
|
|
return msg;
|
|
}
|
|
|
|
exports.createType3Message = function (msg2, options) {
|
|
var nonce = msg2.serverChallenge;
|
|
var username = options.username;
|
|
var password = options.password;
|
|
var negotiateFlags = msg2.negotiateFlags;
|
|
|
|
var isUnicode = negotiateFlags & flags.NTLM_NegotiateUnicode;
|
|
var isNegotiateExtendedSecurity = negotiateFlags & flags.NTLM_NegotiateExtendedSecurity;
|
|
|
|
var BODY_LENGTH = 72;
|
|
|
|
var domainName = escape(options.domain.toUpperCase());
|
|
var workstation = escape(options.workstation.toUpperCase());
|
|
|
|
var workstationBytes, domainNameBytes, usernameBytes, encryptedRandomSessionKeyBytes;
|
|
|
|
var encryptedRandomSessionKey = "";
|
|
if (isUnicode) {
|
|
workstationBytes = new Buffer(workstation, 'utf16le');
|
|
domainNameBytes = new Buffer(domainName, 'utf16le');
|
|
usernameBytes = new Buffer(username, 'utf16le');
|
|
encryptedRandomSessionKeyBytes = new Buffer(encryptedRandomSessionKey, 'utf16le');
|
|
}
|
|
else {
|
|
workstationBytes = new Buffer(workstation, 'ascii');
|
|
domainNameBytes = new Buffer(domainName, 'ascii');
|
|
usernameBytes = new Buffer(username, 'ascii');
|
|
encryptedRandomSessionKeyBytes = new Buffer(encryptedRandomSessionKey, 'ascii');
|
|
}
|
|
|
|
var lmChallengeResponse = calc_resp(create_LM_hashed_password_v1(password), nonce);
|
|
var ntChallengeResponse = calc_resp(create_NT_hashed_password_v1(password), nonce);
|
|
|
|
if (isNegotiateExtendedSecurity) {
|
|
var pwhash = create_NT_hashed_password_v1(password);
|
|
var clientChallenge = "";
|
|
for (var i = 0; i < 8; i++) {
|
|
clientChallenge += String.fromCharCode(Math.floor(Math.random() * 256));
|
|
}
|
|
var clientChallengeBytes = new Buffer(clientChallenge, 'ascii');
|
|
var challenges = ntlm2sr_calc_resp(pwhash, nonce, clientChallengeBytes);
|
|
lmChallengeResponse = challenges.lmChallengeResponse;
|
|
ntChallengeResponse = challenges.ntChallengeResponse;
|
|
}
|
|
|
|
var signature = 'NTLMSSP\0';
|
|
|
|
var pos = 0;
|
|
var buf = new Buffer(BODY_LENGTH + domainNameBytes.length + usernameBytes.length + workstationBytes.length +
|
|
lmChallengeResponse.length + ntChallengeResponse.length +
|
|
encryptedRandomSessionKeyBytes.length);
|
|
|
|
buf.write(signature, pos, signature.length);
|
|
pos += signature.length;
|
|
buf.writeUInt32LE(3, pos);
|
|
pos += 4; // type 1
|
|
|
|
buf.writeUInt16LE(lmChallengeResponse.length, pos);
|
|
pos += 2; // LmChallengeResponseLen
|
|
buf.writeUInt16LE(lmChallengeResponse.length, pos);
|
|
pos += 2; // LmChallengeResponseMaxLen
|
|
buf.writeUInt32LE(BODY_LENGTH + domainNameBytes.length + usernameBytes.length + workstationBytes.length, pos);
|
|
pos += 4; // LmChallengeResponseOffset
|
|
|
|
buf.writeUInt16LE(ntChallengeResponse.length, pos);
|
|
pos += 2; // NtChallengeResponseLen
|
|
buf.writeUInt16LE(ntChallengeResponse.length, pos);
|
|
pos += 2; // NtChallengeResponseMaxLen
|
|
buf.writeUInt32LE(BODY_LENGTH + domainNameBytes.length + usernameBytes.length + workstationBytes.length +
|
|
lmChallengeResponse.length, pos);
|
|
pos += 4; // NtChallengeResponseOffset
|
|
|
|
buf.writeUInt16LE(domainNameBytes.length, pos);
|
|
pos += 2; // DomainNameLen
|
|
buf.writeUInt16LE(domainNameBytes.length, pos);
|
|
pos += 2; // DomainNameMaxLen
|
|
buf.writeUInt32LE(BODY_LENGTH, pos);
|
|
pos += 4; // DomainNameOffset
|
|
|
|
buf.writeUInt16LE(usernameBytes.length, pos);
|
|
pos += 2; // UserNameLen
|
|
buf.writeUInt16LE(usernameBytes.length, pos);
|
|
pos += 2; // UserNameMaxLen
|
|
buf.writeUInt32LE(BODY_LENGTH + domainNameBytes.length, pos);
|
|
pos += 4; // UserNameOffset
|
|
|
|
buf.writeUInt16LE(workstationBytes.length, pos);
|
|
pos += 2; // WorkstationLen
|
|
buf.writeUInt16LE(workstationBytes.length, pos);
|
|
pos += 2; // WorkstationMaxLen
|
|
buf.writeUInt32LE(BODY_LENGTH + domainNameBytes.length + usernameBytes.length, pos);
|
|
pos += 4; // WorkstationOffset
|
|
|
|
buf.writeUInt16LE(encryptedRandomSessionKeyBytes.length, pos);
|
|
pos += 2; // EncryptedRandomSessionKeyLen
|
|
buf.writeUInt16LE(encryptedRandomSessionKeyBytes.length, pos);
|
|
pos += 2; // EncryptedRandomSessionKeyMaxLen
|
|
buf.writeUInt32LE(BODY_LENGTH + domainNameBytes.length + usernameBytes.length + workstationBytes.length +
|
|
lmChallengeResponse.length + ntChallengeResponse.length, pos);
|
|
pos += 4; // EncryptedRandomSessionKeyOffset
|
|
|
|
buf.writeUInt32LE(typeflags.NTLM_TYPE2_FLAGS, pos);
|
|
pos += 4; // NegotiateFlags
|
|
|
|
buf.writeUInt8(5, pos);
|
|
pos++; // ProductMajorVersion
|
|
buf.writeUInt8(1, pos);
|
|
pos++; // ProductMinorVersion
|
|
buf.writeUInt16LE(2600, pos);
|
|
pos += 2; // ProductBuild
|
|
buf.writeUInt8(0, pos);
|
|
pos++; // VersionReserved1
|
|
buf.writeUInt8(0, pos);
|
|
pos++; // VersionReserved2
|
|
buf.writeUInt8(0, pos);
|
|
pos++; // VersionReserved3
|
|
buf.writeUInt8(15, pos);
|
|
pos++; // NTLMRevisionCurrent
|
|
|
|
domainNameBytes.copy(buf, pos);
|
|
pos += domainNameBytes.length;
|
|
usernameBytes.copy(buf, pos);
|
|
pos += usernameBytes.length;
|
|
workstationBytes.copy(buf, pos);
|
|
pos += workstationBytes.length;
|
|
lmChallengeResponse.copy(buf, pos);
|
|
pos += lmChallengeResponse.length;
|
|
ntChallengeResponse.copy(buf, pos);
|
|
pos += ntChallengeResponse.length;
|
|
encryptedRandomSessionKeyBytes.copy(buf, pos);
|
|
pos += encryptedRandomSessionKeyBytes.length;
|
|
|
|
return 'NTLM ' + buf.toString('base64');
|
|
}
|
|
|
|
function create_LM_hashed_password_v1 (password) {
|
|
// fix the password length to 14 bytes
|
|
var password = password.toUpperCase();
|
|
var passwordBytes = new Buffer(password, 'ascii');
|
|
|
|
var passwordBytesPadded = new Buffer(14);
|
|
passwordBytesPadded.fill("\0");
|
|
var sourceEnd = 14;
|
|
if (passwordBytes.length < 14) sourceEnd = passwordBytes.length;
|
|
passwordBytes.copy(passwordBytesPadded, 0, 0, sourceEnd);
|
|
|
|
// split into 2 parts of 7 bytes:
|
|
var firstPart = passwordBytesPadded.slice(0, 7);
|
|
var secondPart = passwordBytesPadded.slice(7);
|
|
|
|
function encrypt (buf) {
|
|
var key = insertZerosEvery7Bits(buf);
|
|
var des = crypto.createCipheriv('DES-ECB', key, '');
|
|
return des.update("KGS!@#$%"); // page 57 in [MS-NLMP]);
|
|
}
|
|
|
|
var firstPartEncrypted = encrypt(firstPart);
|
|
var secondPartEncrypted = encrypt(secondPart);
|
|
|
|
return Buffer.concat([firstPartEncrypted, secondPartEncrypted]);
|
|
}
|
|
|
|
function insertZerosEvery7Bits (buf) {
|
|
var binaryArray = bytes2binaryArray(buf);
|
|
var newBinaryArray = [];
|
|
for (var i = 0; i < binaryArray.length; i++) {
|
|
newBinaryArray.push(binaryArray[i]);
|
|
|
|
if ((i + 1) % 7 == 0) {
|
|
newBinaryArray.push(0);
|
|
}
|
|
}
|
|
return binaryArray2bytes(newBinaryArray);
|
|
}
|
|
|
|
function bytes2binaryArray (buf) {
|
|
var hex2binary = {
|
|
0: [0, 0, 0, 0],
|
|
1: [0, 0, 0, 1],
|
|
2: [0, 0, 1, 0],
|
|
3: [0, 0, 1, 1],
|
|
4: [0, 1, 0, 0],
|
|
5: [0, 1, 0, 1],
|
|
6: [0, 1, 1, 0],
|
|
7: [0, 1, 1, 1],
|
|
8: [1, 0, 0, 0],
|
|
9: [1, 0, 0, 1],
|
|
A: [1, 0, 1, 0],
|
|
B: [1, 0, 1, 1],
|
|
C: [1, 1, 0, 0],
|
|
D: [1, 1, 0, 1],
|
|
E: [1, 1, 1, 0],
|
|
F: [1, 1, 1, 1]
|
|
};
|
|
|
|
var hexString = buf.toString('hex').toUpperCase();
|
|
var array = [];
|
|
for (var i = 0; i < hexString.length; i++) {
|
|
var hexchar = hexString.charAt(i);
|
|
array = array.concat(hex2binary[hexchar]);
|
|
}
|
|
return array;
|
|
}
|
|
|
|
function binaryArray2bytes (array) {
|
|
var binary2hex = {
|
|
'0000': 0,
|
|
'0001': 1,
|
|
'0010': 2,
|
|
'0011': 3,
|
|
'0100': 4,
|
|
'0101': 5,
|
|
'0110': 6,
|
|
'0111': 7,
|
|
'1000': 8,
|
|
'1001': 9,
|
|
'1010': 'A',
|
|
'1011': 'B',
|
|
'1100': 'C',
|
|
'1101': 'D',
|
|
'1110': 'E',
|
|
'1111': 'F'
|
|
}
|
|
|
|
var bufArray = [];
|
|
|
|
for (var i = 0; i < array.length; i += 8) {
|
|
if ((i + 7) > array.length)
|
|
break;
|
|
|
|
var binString1 = '' + array[i] + '' + array[i + 1] + '' + array[i + 2] + '' + array[i + 3];
|
|
var binString2 = '' + array[i + 4] + '' + array[i + 5] + '' + array[i + 6] + '' + array[i + 7];
|
|
var hexchar1 = binary2hex[binString1];
|
|
var hexchar2 = binary2hex[binString2];
|
|
|
|
var buf = new Buffer(hexchar1 + '' + hexchar2, 'hex');
|
|
bufArray.push(buf);
|
|
}
|
|
|
|
return Buffer.concat(bufArray);
|
|
}
|
|
|
|
function create_NT_hashed_password_v1 (password) {
|
|
var buf = new Buffer(password, 'utf16le');
|
|
var md4 = crypto.createHash('md4');
|
|
md4.update(buf);
|
|
return new Buffer(md4.digest());
|
|
}
|
|
|
|
function calc_resp (password_hash, server_challenge) {
|
|
// padding with zeros to make the hash 21 bytes long
|
|
var passHashPadded = new Buffer(21);
|
|
passHashPadded.fill("\0");
|
|
password_hash.copy(passHashPadded, 0, 0, password_hash.length);
|
|
|
|
var resArray = [];
|
|
|
|
var des = crypto.createCipheriv('DES-ECB', insertZerosEvery7Bits(passHashPadded.slice(0, 7)), '');
|
|
resArray.push(des.update(server_challenge.slice(0, 8)));
|
|
|
|
var des = crypto.createCipheriv('DES-ECB', insertZerosEvery7Bits(passHashPadded.slice(7, 14)), '');
|
|
resArray.push(des.update(server_challenge.slice(0, 8)));
|
|
|
|
var des = crypto.createCipheriv('DES-ECB', insertZerosEvery7Bits(passHashPadded.slice(14, 21)), '');
|
|
resArray.push(des.update(server_challenge.slice(0, 8)));
|
|
|
|
return Buffer.concat(resArray);
|
|
}
|
|
|
|
function ntlm2sr_calc_resp (responseKeyNT, serverChallenge, clientChallenge) {
|
|
// padding with zeros to make the hash 16 bytes longer
|
|
var lmChallengeResponse = new Buffer(clientChallenge.length + 16);
|
|
lmChallengeResponse.fill("\0");
|
|
clientChallenge.copy(lmChallengeResponse, 0, 0, clientChallenge.length);
|
|
|
|
var buf = Buffer.concat([serverChallenge, clientChallenge]);
|
|
var md5 = crypto.createHash('md5');
|
|
md5.update(buf);
|
|
var sess = md5.digest();
|
|
var ntChallengeResponse = calc_resp(responseKeyNT, sess.slice(0, 8));
|
|
|
|
return {
|
|
lmChallengeResponse: lmChallengeResponse,
|
|
ntChallengeResponse: ntChallengeResponse
|
|
};
|
|
}
|
|
|
|
exports.isChallengeMessage = function (res) {
|
|
return !!exports.parseType2Message(res.headers['www-authenticate'], function () {
|
|
return void 0;
|
|
});
|
|
};
|