add helpers for NTLM HTTP Authentication

This commit is contained in:
Joshua M. Clulow 2012-06-01 18:53:19 +10:00
parent 43ae5fbc7f
commit 6df4ea376e
4 changed files with 318 additions and 1 deletions

View File

@ -7,11 +7,15 @@ NT Hashes used by SMB/CIFS servers. It was written to populate
the sambaLMPassword and sambaNTPassword values in an LDAP directory
for use with Samba.
In addition, the library also provides helper methods for encoding
and decoding the headers used during NTLM HTTP authentication. This
functionality should presently be considered experimental.
## Installation
npm install smbhash
## Usage
## Hash Usage
```javascript
var lmhash = require('smbhash').lmhash;
@ -29,6 +33,31 @@ LM Hash: 4FB7D301186E0EB3AAD3B435B51404EE
NT Hash: 5FBC3D5FEC8206A30F4B6C473D68AE76
```
## NTLM Usage
NTLM HTTP Authentication headers are Base64-encoded packed structures of
three basic varieties. Type 1 & 3 are sent from the client to the server,
and Type 2 is from server to client. For example:
```javascript
var ntlm = require('smbhash').ntlm;
// Generate Type 1 to send to server in HTTP Request:
var buf = ntlm.encodeType1('hostname', 'ntdomain');
http.setHeader('Authorization', 'NTLM ' + buf.toString('base64'));
// Extract Type 2 from HTTP Response header, and use it here:
var hdr = http.getHeader('WWW-Authenticate');
var m = hdr.match('/^NTLM (.*)$/');
var inbuf = new Buffer(m[1], 'base64');
var serverNonce = ntlm.decodeType2(inbuf);
// Generate Type 3 to send as authentication to server:
var buf = ntlm.encodeType3('username', 'hostname', 'ntdomain',
serverNonce, 'password');
http.setHeader('Authorization', 'NTLM ' + buf.toString('base64'));
```
## References
The NTLM Authentication Protocol and Security Support Provider

221
lib/ntlm.js Executable file
View File

@ -0,0 +1,221 @@
/*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Copyright (C) 2012 Joshua M. Clulow <josh@sysmgr.org>
*/
var log = console.log;
var crypto = require('crypto');
var $ = require('./common');
var lmhashbuf = require('./smbhash').lmhashbuf;
var nthashbuf = require('./smbhash').nthashbuf;
function encodeType1(hostname, ntdomain) {
hostname = hostname.toUpperCase();
ntdomain = ntdomain.toUpperCase();
var hostnamelen = Buffer.byteLength(hostname, 'ascii');
var ntdomainlen = Buffer.byteLength(ntdomain, 'ascii');
var pos = 0;
var buf = new Buffer(32 + hostnamelen + ntdomainlen);
buf.write('NTLMSSP', pos, 7, 'ascii'); // byte protocol[8];
pos += 7;
buf.writeUInt8(0, pos);
pos++;
buf.writeUInt8(0x01, pos); // byte type;
pos++;
buf.fill(0x00, pos, pos + 3); // byte zero[3];
pos += 3;
buf.writeUInt16LE(0xb203, pos); // short flags;
pos += 2;
buf.fill(0x00, pos, pos + 2); // byte zero[2];
pos += 2;
buf.writeUInt16LE(ntdomainlen, pos); // short dom_len;
pos += 2;
buf.writeUInt16LE(ntdomainlen, pos); // short dom_len;
pos += 2;
var ntdomainoff = 0x20 + hostnamelen;
buf.writeUInt16LE(ntdomainoff, pos); // short dom_off;
pos += 2;
buf.fill(0x00, pos, pos + 2); // byte zero[2];
pos += 2;
buf.writeUInt16LE(hostnamelen, pos); // short host_len;
pos += 2;
buf.writeUInt16LE(hostnamelen, pos); // short host_len;
pos += 2;
buf.writeUInt16LE(0x20, pos); // short host_off;
pos += 2;
buf.fill(0x00, pos, pos + 2); // byte zero[2];
pos += 2;
buf.write(hostname, 0x20, hostnamelen, 'ascii');
buf.write(ntdomain, ntdomainoff, ntdomainlen, 'ascii');
return buf;
}
/*
*
*/
function decodeType2(buf)
{
var proto = buf.toString('ascii', 0, 7);
if (buf[7] !== 0x00 || proto !== 'NTLMSSP')
throw new Error('magic was not NTLMSSP');
var type = buf.readUInt8(8);
if (type !== 0x02)
throw new Error('message was not NTLMSSP type 0x02');
//var msg_len = buf.readUInt16LE(16);
//var flags = buf.readUInt16LE(20);
var nonce = buf.slice(24, 32);
return nonce;
}
function encodeType3(username, hostname, ntdomain, nonce, password) {
hostname = hostname.toUpperCase();
ntdomain = ntdomain.toUpperCase();
var lmh = new Buffer(21);
lmhashbuf(password).copy(lmh);
lmh.fill(0x00, 16); // null pad to 21 bytes
var nth = new Buffer(21);
nthashbuf(password).copy(nth);
nth.fill(0x00, 16); // null pad to 21 bytes
var lmr = makeResponse(lmh, nonce);
var ntr = makeResponse(nth, nonce);
var usernamelen = Buffer.byteLength(username, 'ucs2');
var hostnamelen = Buffer.byteLength(hostname, 'ucs2');
var ntdomainlen = Buffer.byteLength(ntdomain, 'ucs2');
var lmrlen = 0x18;
var ntrlen = 0x18;
var ntdomainoff = 0x40;
var usernameoff = ntdomainoff + ntdomainlen;
var hostnameoff = usernameoff + usernamelen;
var lmroff = hostnameoff + hostnamelen;
var ntroff = lmroff + lmrlen;
var pos = 0;
var msg_len = 64 + ntdomainlen + usernamelen + hostnamelen + lmrlen + ntrlen;
var buf = new Buffer(msg_len);
buf.write('NTLMSSP', pos, 7, 'ascii'); // byte protocol[8];
pos += 7;
buf.writeUInt8(0, pos);
pos++;
buf.writeUInt8(0x03, pos); // byte type;
pos++;
buf.fill(0x00, pos, pos + 3); // byte zero[3];
pos += 3;
buf.writeUInt16LE(lmrlen, pos); // short lm_resp_len;
pos += 2;
buf.writeUInt16LE(lmrlen, pos); // short lm_resp_len;
pos += 2;
buf.writeUInt16LE(lmroff, pos); // short lm_resp_off;
pos += 2;
buf.fill(0x00, pos, pos + 2); // byte zero[2];
pos += 2;
buf.writeUInt16LE(ntrlen, pos); // short nt_resp_len;
pos += 2;
buf.writeUInt16LE(ntrlen, pos); // short nt_resp_len;
pos += 2;
buf.writeUInt16LE(ntroff, pos); // short nt_resp_off;
pos += 2;
buf.fill(0x00, pos, pos + 2); // byte zero[2];
pos += 2;
buf.writeUInt16LE(ntdomainlen, pos); // short dom_len;
pos += 2;
buf.writeUInt16LE(ntdomainlen, pos); // short dom_len;
pos += 2;
buf.writeUInt16LE(ntdomainoff, pos); // short dom_off;
pos += 2;
buf.fill(0x00, pos, pos + 2); // byte zero[2];
pos += 2;
buf.writeUInt16LE(usernamelen, pos); // short user_len;
pos += 2;
buf.writeUInt16LE(usernamelen, pos); // short user_len;
pos += 2;
buf.writeUInt16LE(usernameoff, pos); // short user_off;
pos += 2;
buf.fill(0x00, pos, pos + 2); // byte zero[2];
pos += 2;
buf.writeUInt16LE(hostnamelen, pos); // short host_len;
pos += 2;
buf.writeUInt16LE(hostnamelen, pos); // short host_len;
pos += 2;
buf.writeUInt16LE(hostnameoff, pos); // short host_off;
pos += 2;
buf.fill(0x00, pos, pos + 6); // byte zero[6];
pos += 6;
buf.writeUInt16LE(msg_len, pos); // short msg_len;
pos += 2;
buf.fill(0x00, pos, pos + 2); // byte zero[2];
pos += 2;
buf.writeUInt16LE(0x8201, pos); // short flags;
pos += 2;
buf.fill(0x00, pos, pos + 2); // byte zero[2];
pos += 2;
buf.write(ntdomain, ntdomainoff, ntdomainlen, 'ucs2');
buf.write(username, usernameoff, usernamelen, 'ucs2');
buf.write(hostname, hostnameoff, hostnamelen, 'ucs2');
lmr.copy(buf, lmroff, 0, lmrlen);
ntr.copy(buf, ntroff, 0, ntrlen);
return buf;
}
function makeResponse(hash, nonce)
{
var out = new Buffer(24);
for (var i = 0; i < 3; i++) {
var keybuf = $.oddpar($.expandkey(hash.slice(i * 7, i * 7 + 7)));
var key = keybuf.toString('binary');
var des = crypto.createCipheriv('DES-ECB', key, '');
var str = des.update(nonce.toString('binary'), 'binary', 'binary');
out.write(str, i * 8, i * 8 + 8, 'binary');
}
return out;
}
module.exports.encodeType1 = encodeType1;
module.exports.decodeType2 = decodeType2;
module.exports.encodeType3 = encodeType3;

View File

@ -79,3 +79,5 @@ module.exports.lmhashbuf = lmhashbuf;
module.exports.nthash = nthash;
module.exports.lmhash = lmhash;
module.exports.ntlm = require('./ntlm');

65
tests/ntlm.js Normal file
View File

@ -0,0 +1,65 @@
/*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Copyright (C) 2012 Joshua M. Clulow <josh@sysmgr.org>
*/
var $ = require('../lib/smbhash').ntlm;
var GOOD = [
{ messages: [
'TlRMTVNTUAABAAAAA7IAAAoACgApAAAACQAJACAAAABMSUdIVENJVFlVUlNBLU1JTk9S',
'TlRMTVNTUAACAAAAAAAAACgAAAABggAAU3J2Tm9uY2UAAAAAAAAAAA==',
'TlRMTVNTUAADAAAAGAAYAHIAAAAYABgAigAAABQAFABAAAAADAAMAFQAAAASABIAYAAA' +
'AAAAAACiAAAAAYIAAFUAUgBTAEEALQBNAEkATgBPAFIAWgBhAHAAaABvAGQATABJAEcA' +
'SABUAEMASQBUAFkArYfKbe/jRoW5xDxHeoxC1gBmfWiS5+iX4OAN4xBKG/IFPwfH3agt' +
'PEia6YnhsADT' ],
hostname: 'LightCity',
ntdomain: 'Ursa-Minor',
username: 'Zaphod',
password: 'Beeblebrox',
nonce: 'SrvNonce'
}
];
module.exports.type1_success = function(test) {
test.expect(GOOD.length * 1);
for (var i = 0; i < GOOD.length; i++) {
var g = GOOD[i];
var out = $.encodeType1(g.hostname, g.ntdomain);
test.strictEqual(out.toString('base64'), g.messages[0]);
}
test.done();
}
module.exports.type2_success = function(test) {
test.expect(GOOD.length * 1);
for (var i = 0; i < GOOD.length; i++) {
var g = GOOD[i];
var inbuf = new Buffer(g.messages[1], 'base64');
var out = $.decodeType2(inbuf);
test.strictEqual(out.toString('binary'), g.nonce);
}
test.done();
}
module.exports.type3_success = function(test) {
test.expect(GOOD.length * 1);
for (var i = 0; i < GOOD.length; i++) {
var g = GOOD[i];
var out = $.encodeType3(g.username, g.hostname, g.ntdomain, g.nonce,
g.password);
test.strictEqual(out.toString('base64'), g.messages[2]);
}
test.done();
}