Skip to content

Commit 268877c

Browse files
committed
express-ntlm now authenticates against an Active Directory (fixes #4, #5)
1 parent 21786c4 commit 268877c

File tree

10 files changed

+714
-91
lines changed

10 files changed

+714
-91
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
test.js

buffer.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
function emptyBuffer(length) {
2+
var buf = new Buffer(length);
3+
for (var i = 0; i < length; i++) {
4+
buf.writeUInt8(0, i);
5+
}
6+
return buf;
7+
}
8+
9+
for (var i = 0; i < 100; i++) {
10+
console.log((emptyBuffer(30)).toString('hex'));
11+
}

lib/ASN1.js

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/* jshint node:true */
2+
/* global toBinary */
3+
4+
var assert = require('assert');
5+
6+
var utils = require('./utils');
7+
8+
var ASN1 = {
9+
maketlv: function(dertype, payload) {
10+
if (typeof payload === 'string') {
11+
payload = new Buffer(payload);
12+
}
13+
14+
var tlv,
15+
offset;
16+
if (payload.length < 128) {
17+
tlv = new Buffer(1 + 1 + payload.length);
18+
tlv.writeUInt8(dertype, 0);
19+
tlv.writeUInt8(payload.length, 1);
20+
offset = 2;
21+
} else if (payload.length < 256) {
22+
tlv = new Buffer(1 + 2 + payload.length);
23+
tlv.writeUInt8(dertype, 0);
24+
tlv.writeUInt8(utils.toBinary('10000001'), 1); // Number of length bytes = 1
25+
tlv.writeUInt8(payload.length, 2);
26+
offset = 3;
27+
} else {
28+
tlv = new Buffer(1 + 3 + payload.length);
29+
tlv.writeUInt8(dertype, 0);
30+
tlv.writeUInt8(utils.toBinary('10000010'), 1); // Number of length bytes = 2
31+
tlv.writeUInt16LE(payload.length, 2);
32+
offset = 4;
33+
}
34+
35+
for (var i = 0; i < payload.length; i++) {
36+
tlv.writeUInt8(payload.readUInt8(i), offset++);
37+
}
38+
39+
return tlv;
40+
},
41+
makeint: function(number, tag) {
42+
if (!tag) {
43+
tag = 0x02;
44+
}
45+
46+
var payload;
47+
48+
if (number <= 0) {
49+
payload = new Buffer('\0');
50+
} else {
51+
payload = new Buffer(0);
52+
while (number > 0) {
53+
var buf = new Buffer(1);
54+
buf.writeUInt8(number & 255, 0);
55+
payload = buf + payload;
56+
number = number >>> 8;
57+
}
58+
}
59+
60+
return ASN1.maketlv(tag, payload);
61+
},
62+
makeseq: function(payload) {
63+
return ASN1.maketlv(0x30, payload);
64+
},
65+
makeoctstr: function(payload) {
66+
return ASN1.maketlv(0x04, payload);
67+
},
68+
69+
parselen: function(berobj) {
70+
var length = berobj.readUInt8(1);
71+
72+
if (length < 128) {
73+
return [length, 2];
74+
}
75+
76+
var nlength = length & utils.toBinary('01111111');
77+
length = 0;
78+
79+
for (var i = 2; i < 2 + nlength; i++) {
80+
length = length * 256 + berobj.readUInt8(i);
81+
}
82+
83+
return [length, 2 + nlength];
84+
},
85+
parsetlv: function(dertype, derobj, partial) {
86+
if (!partial) partial = false;
87+
88+
if (derobj.readUInt8(0) != dertype) {
89+
throw new Error('BER element ' + derobj.toString('hex') + ' does not start type 0x' + dertype.toString(16));
90+
}
91+
92+
var lengths = ASN1.parselen(derobj),
93+
length = lengths[0],
94+
pstart = lengths[1];
95+
96+
if (partial) {
97+
if (derobj.length < length + pstart) {
98+
throw new Error('BER payload ' + derobj.toString('hex') + ' is shorter than expected (' + length + ' bytes, type 0x' + dertype.toString(16) + ').');
99+
}
100+
return [derobj.slice(pstart, pstart + length), derobj.slice(pstart + length)];
101+
}
102+
103+
if (derobj.length != length + pstart) {
104+
throw new Error('BER payload ' + derobj.toString('hex') + ' is not ' + length + ' bytes long (type 0x' + dertype.toString(16) + ').');
105+
}
106+
return derobj.slice(pstart);
107+
},
108+
parseint: function(payload, partial, tag) {
109+
if (!partial) partial = false;
110+
if (!tag) tag = 0x02;
111+
112+
var res = ASN1.parsetlv(tag, payload, partial);
113+
if (partial) {
114+
payload = res[0];
115+
} else {
116+
payload = res;
117+
}
118+
119+
var value = 0;
120+
121+
assert.equal(payload.readUInt8(0) & utils.toBinary('10000000'), 0x00);
122+
for (var i = 0; i < payload.length; i++) {
123+
value = value * 256 + payload.readUInt8(i);
124+
}
125+
if (partial) {
126+
return [value, res[1]];
127+
} else {
128+
return value;
129+
}
130+
},
131+
parseenum: function(payload, partial) {
132+
if (!partial) partial = false;
133+
134+
return ASN1.parseint(payload, partial, 0x0A);
135+
},
136+
parseseq: function(payload, partial) {
137+
if (!partial) partial = false;
138+
139+
return ASN1.parsetlv(0x30, payload, partial);
140+
},
141+
parseoctstr: function(payload, partial) {
142+
if (!partial) partial = false;
143+
144+
return ASN1.parsetlv(0x04, payload, partial);
145+
}
146+
};
147+
148+
module.exports = ASN1;

lib/Cache.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/* jshint node: true */
2+
3+
function Cache() {
4+
this._cache = {};
5+
}
6+
7+
Cache.prototype.remove = function(id) {
8+
var proxy = this._cache[id];
9+
if (proxy) {
10+
proxy[0].close();
11+
delete this._cache[id];
12+
}
13+
};
14+
15+
Cache.prototype.add = function(id, proxy) {
16+
this._cache[id] = [proxy, Date.now()];
17+
};
18+
19+
Cache.prototype.clean = function() {
20+
var now = Date.now();
21+
for (var id in this._cache) {
22+
if (this._cache[id][1] + 60 * 1000 < now) {
23+
this.remove(id);
24+
}
25+
}
26+
};
27+
28+
Cache.prototype.get_proxy = function(id) {
29+
return this._cache[id][0];
30+
};
31+
32+
module.exports = Cache;

lib/NTLM_AD_Proxy.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/* jshint node:true */
2+
3+
var util = require('util');
4+
5+
var ASN1 = require('./ASN1'),
6+
NTLM_Proxy = require('./NTLM_Proxy'),
7+
utils = require('./utils');
8+
9+
function LDAP_Context() {
10+
this.messageID = 0;
11+
12+
this.LDAP_Result_success = 0;
13+
this.LDAP_Result_saslBindInProgress = 14;
14+
}
15+
16+
LDAP_Context.prototype.make_session_setup_req = function(ntlm_token, type1) {
17+
18+
var authentication = ASN1.maketlv(0xA3, utils.concatBuffer(ASN1.makeoctstr('GSS-SPNEGO'), ASN1.makeoctstr(ntlm_token))),
19+
bindRequest = ASN1.maketlv(0x60, utils.concatBuffer(ASN1.makeint(3), ASN1.makeoctstr(''), authentication));
20+
21+
this.messageID++;
22+
23+
return ASN1.makeseq(utils.concatBuffer(ASN1.makeint(this.messageID), bindRequest));
24+
};
25+
26+
LDAP_Context.prototype.make_negotiate_protocol_req = function() {
27+
return;
28+
};
29+
30+
LDAP_Context.prototype.parse_session_setup_resp = function(response, callback) {
31+
try {
32+
var data = ASN1.parseseq(response);
33+
34+
var messageID = ASN1.parseint(data, true);
35+
data = messageID[1];
36+
messageID = messageID[0];
37+
38+
39+
if (messageID != this.messageID) {
40+
throw new Error('Unexpected MessageID: ' + messageID + ' instead of ' + this.messageID);
41+
}
42+
43+
var controls = ASN1.parsetlv(0x61, data, true);
44+
data = controls[0];
45+
controls = controls[1];
46+
47+
var resultCode = ASN1.parseenum(data, true);
48+
data = resultCode[1];
49+
resultCode = resultCode[0];
50+
51+
var matchedDN = ASN1.parseoctstr(data, true);
52+
data = matchedDN[1];
53+
matchedDN = matchedDN[0];
54+
55+
var diagnosticMessage = ASN1.parseoctstr(data, true);
56+
data = diagnosticMessage[1];
57+
diagnosticMessage = diagnosticMessage[0];
58+
59+
if (resultCode == this.LDAP_Result_success) {
60+
return callback(null, true, '');
61+
}
62+
63+
if (resultCode != this.LDAP_Result_saslBindInProgress) {
64+
return callback(null, false, '');
65+
}
66+
67+
var serverSaslCreds = ASN1.parsetlv(0x87, data);
68+
return callback(null, true, serverSaslCreds);
69+
}
70+
catch (error) {
71+
return callback(error);
72+
}
73+
};
74+
75+
function NTLM_AD_Proxy(ipad, domain, base) {
76+
this._portad = 389;
77+
78+
NTLM_Proxy.call(this, ipad, this._portad, domain, LDAP_Context);
79+
this.base = base;
80+
}
81+
82+
util.inherits(NTLM_AD_Proxy, NTLM_Proxy);
83+
84+
module.exports = NTLM_AD_Proxy;

lib/NTLM_No_Proxy.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/* jshint node:true */
2+
3+
function NTLM_No_Proxy() {}
4+
5+
NTLM_No_Proxy.prototype.close = function() {
6+
7+
};
8+
9+
NTLM_No_Proxy.prototype.negotiate = function(ntlm_negotiate, negotiate_callback) {
10+
var challenge = new Buffer(40),
11+
offset = 0;
12+
13+
var header = 'NTLMSSP\0';
14+
for (var i = 0; i < header.length; i++) {
15+
challenge.writeUInt8(header.charCodeAt(i), offset++);
16+
}
17+
18+
challenge.writeUInt8(0x02, offset++);
19+
challenge.writeUInt8(0x00, offset++);
20+
challenge.writeUInt8(0x00, offset++);
21+
challenge.writeUInt8(0x00, offset++);
22+
challenge.writeUInt8(0x00, offset++);
23+
challenge.writeUInt8(0x00, offset++);
24+
challenge.writeUInt8(0x00, offset++);
25+
challenge.writeUInt8(0x00, offset++);
26+
challenge.writeUInt8(0x00, offset++);
27+
challenge.writeUInt8(0x28, offset++);
28+
challenge.writeUInt8(0x00, offset++);
29+
challenge.writeUInt8(0x00, offset++);
30+
challenge.writeUInt8(0x01, offset++);
31+
challenge.writeUInt8(0x82, offset++);
32+
challenge.writeUInt8(0x00, offset++);
33+
challenge.writeUInt8(0x00, offset++);
34+
challenge.writeUInt8(0x01, offset++);
35+
challenge.writeUInt8(0x23, offset++);
36+
challenge.writeUInt8(0x45, offset++);
37+
challenge.writeUInt8(0x67, offset++);
38+
challenge.writeUInt8(0x89, offset++);
39+
challenge.writeUInt8(0xab, offset++);
40+
challenge.writeUInt8(0xcd, offset++);
41+
challenge.writeUInt8(0xef, offset++);
42+
challenge.writeUInt8(0x00, offset++);
43+
challenge.writeUInt8(0x00, offset++);
44+
challenge.writeUInt8(0x00, offset++);
45+
challenge.writeUInt8(0x00, offset++);
46+
challenge.writeUInt8(0x00, offset++);
47+
challenge.writeUInt8(0x00, offset++);
48+
challenge.writeUInt8(0x00, offset++);
49+
challenge.writeUInt8(0x00, offset++);
50+
51+
negotiate_callback(null, challenge);
52+
};
53+
54+
NTLM_No_Proxy.prototype.authenticate = function(ntlm_authenticate, authenticate_callback) {
55+
authenticate_callback(null, true);
56+
};
57+
58+
module.exports = NTLM_No_Proxy;

0 commit comments

Comments
 (0)