add helpers for NTLM HTTP Authentication
This commit is contained in:
parent
43ae5fbc7f
commit
6df4ea376e
31
README.md
31
README.md
|
@ -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
|
the sambaLMPassword and sambaNTPassword values in an LDAP directory
|
||||||
for use with Samba.
|
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
|
## Installation
|
||||||
|
|
||||||
npm install smbhash
|
npm install smbhash
|
||||||
|
|
||||||
## Usage
|
## Hash Usage
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var lmhash = require('smbhash').lmhash;
|
var lmhash = require('smbhash').lmhash;
|
||||||
|
@ -29,6 +33,31 @@ LM Hash: 4FB7D301186E0EB3AAD3B435B51404EE
|
||||||
NT Hash: 5FBC3D5FEC8206A30F4B6C473D68AE76
|
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
|
## References
|
||||||
|
|
||||||
The NTLM Authentication Protocol and Security Support Provider
|
The NTLM Authentication Protocol and Security Support Provider
|
||||||
|
|
|
@ -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;
|
|
@ -79,3 +79,5 @@ module.exports.lmhashbuf = lmhashbuf;
|
||||||
|
|
||||||
module.exports.nthash = nthash;
|
module.exports.nthash = nthash;
|
||||||
module.exports.lmhash = lmhash;
|
module.exports.lmhash = lmhash;
|
||||||
|
|
||||||
|
module.exports.ntlm = require('./ntlm');
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
Loading…
Reference in New Issue