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; }); };