Skip to content

Commit

Permalink
feat: fail ntlm auth gracefully when md4 hashing is not available (#1400
Browse files Browse the repository at this point in the history
)
  • Loading branch information
mShan0 authored Jun 8, 2022
1 parent 734a9df commit 57a50d4
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 68 deletions.
44 changes: 28 additions & 16 deletions src/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3416,22 +3416,34 @@ Connection.prototype.STATE = {
this.transitionTo(this.STATE.LOGGED_IN_SENDING_INITIAL_SQL);
}
} else if (this.ntlmpacket) {
const authentication = this.config.authentication as NtlmAuthentication;

const payload = new NTLMResponsePayload({
domain: authentication.options.domain,
userName: authentication.options.userName,
password: authentication.options.password,
ntlmpacket: this.ntlmpacket
});

this.messageIo.sendMessage(TYPE.NTLMAUTH_PKT, payload.data);
this.debug.payload(function() {
return payload.toString(' ');
});

this.ntlmpacket = undefined;

try {
const authentication = this.config.authentication as NtlmAuthentication;

const payload = new NTLMResponsePayload({
domain: authentication.options.domain,
userName: authentication.options.userName,
password: authentication.options.password,
ntlmpacket: this.ntlmpacket
});

this.messageIo.sendMessage(TYPE.NTLMAUTH_PKT, payload.data);
this.debug.payload(function() {
return payload.toString(' ');
});

this.ntlmpacket = undefined;

} catch (error: any) {
if (error.code === 'ERR_OSSL_EVP_UNSUPPORTED') {
const node17Message = new ConnectionError('Node 17 now uses OpenSSL 3, which considers md4 encryption a legacy type.' +
' In order to use NTLM with Node 17, enable the `--openssl-legacy-provider` command line flag.' +
' Check the Tedious FAQ for more information.', 'ELOGIN');
this.emit('connect', new AggregateError([error, node17Message]));
} else {
throw error;
}
this.transitionTo(this.STATE.FINAL);
}
} else if (this.loginError) {
if (isTransientError(this.loginError)) {
this.debug.log('Initiating retry on transient error');
Expand Down
23 changes: 23 additions & 0 deletions test/integration/child-processes/ntlm-connect-node17.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const { Connection } = require('../../../');
const fs = require('fs');
const homedir = require('os').homedir();

function getNtlmConfig() {
return JSON.parse(
fs.readFileSync(homedir + '/.tedious/test-connection.json', 'utf8')
).ntlm;
}
const ntlmConfig = getNtlmConfig();

const connection = new Connection(ntlmConfig);

connection.connect(function(err) {
if (err) {
if (err instanceof AggregateError) {
throw err.errors;
} else {
throw err;
}
}
connection.close();
});
45 changes: 36 additions & 9 deletions test/integration/connection-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const fs = require('fs');
const homedir = require('os').homedir();
const assert = require('chai').assert;
const os = require('os');
const childProcess = require('child_process');

import Connection from '../../src/connection';
import { ConnectionError, RequestError } from '../../src/errors';
Expand Down Expand Up @@ -455,17 +456,43 @@ describe('Ntlm Test', function() {
});
}

it('should ntlm', function(done) {
runNtlmTest.call(this, done, DomainCaseEnum.AsIs);
});
const nodeVersion = parseInt(process.versions.node.split('.')[0]);
if (nodeVersion <= 16 || (process.execArgv.includes('--openssl-legacy-provider') && nodeVersion >= 17)) {
it('should ntlm', function(done) {
runNtlmTest.call(this, done, DomainCaseEnum.AsIs);
});

it('should ntlm lower', function(done) {
runNtlmTest.call(this, done, DomainCaseEnum.Lower);
});
it('should ntlm lower', function(done) {
runNtlmTest.call(this, done, DomainCaseEnum.Lower);
});

it('should ntlm upper', function(done) {
runNtlmTest.call(this, done, DomainCaseEnum.Upper);
});

} else {

it('should throw an aggregate error with node 17', () => {
const child = childProcess.spawnSync(process.execPath,
['./test/integration/child-processes/ntlm-connect-node17.js'],
{ encoding: 'utf8' });
const thrownError = child.stderr;
assert.isTrue(thrownError.includes('ERR_OSSL_EVP_UNSUPPORTED'));
assert.isTrue(thrownError.includes('ConnectionError'));
assert.isTrue(thrownError.includes('--openssl-legacy-provider'));
assert.strictEqual(child.status, 1);
});

it('should ntlm with node 17 when `--openssl-legacy-provider` flag enabled', () => {
const child = childProcess.spawnSync(process.execPath,
['--openssl-legacy-provider',
'./test/integration/child-processes/ntlm-connect-node17.js'],
{ encoding: 'utf8' });
assert.strictEqual(child.status, 0);
});

}

it('should ntlm upper', function(done) {
runNtlmTest.call(this, done, DomainCaseEnum.Upper);
});
});

describe('Encrypt Test', function() {
Expand Down
13 changes: 13 additions & 0 deletions test/unit/child-processes/ntlm-payload-node17.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const NTLMPayload = require('../../../src/ntlm-payload');

const challenge = {
domain: 'domain',
userName: 'username',
password: 'password',
ntlmpacket: {
target: Buffer.from([170, 170, 170, 170]), // aa aa aa aa
nonce: Buffer.from([187, 187, 187, 187, 187, 187, 187, 187])
}
};

new NTLMPayload(challenge);
108 changes: 65 additions & 43 deletions test/unit/ntlm-payload-test.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,70 @@
const NTLMPayload = require('../../src/ntlm-payload');
const assert = require('chai').assert;
const childProcess = require('child_process');
const nodeVersion = parseInt(process.versions.node.split('.')[0]);

describe('ntlm payload test', function() {
it('should respond to challenge', function() {
const challenge = {
domain: 'domain',
userName: 'username',
password: 'password',
ntlmpacket: {
target: Buffer.from([170, 170, 170, 170]), // aa aa aa aa
nonce: Buffer.from([187, 187, 187, 187, 187, 187, 187, 187])
const challenge = {
domain: 'domain',
userName: 'username',
password: 'password',
ntlmpacket: {
target: Buffer.from([170, 170, 170, 170]), // aa aa aa aa
nonce: Buffer.from([187, 187, 187, 187, 187, 187, 187, 187])
}
};

if (nodeVersion <= 16 || (process.execArgv.includes('--openssl-legacy-provider') && nodeVersion >= 17)) {
describe('ntlm payload test', function() {
it('should respond to challenge', function() {

const response = new NTLMPayload(challenge);

const expectedLength =
8 + // NTLM protocol header
4 + // NTLM message type
8 + // lmv index
8 + // ntlm index
8 + // domain index
8 + // user index
16 + // header index
4 + // flags
2 * 6 + // domain
2 * 8 + // username
24 + // lmv2 data
16 + // ntlmv2 data
8 + // flags
8 + // timestamp
8 + // client nonce
4 + // placeholder
4 + // target data
4; // placeholder

const domainName = response.data.slice(64, 76).toString('ucs2');
const userName = response.data.slice(76, 92).toString('ucs2');
const targetData = response.data.slice(160, 164).toString('hex');

assert.strictEqual(domainName, 'domain');
assert.strictEqual(userName, 'username');
assert.strictEqual(targetData, 'aaaaaaaa');

assert.strictEqual(expectedLength, response.data.length);
});
});
} else {
describe('ntlm payload test node 17 and newer', function() {

it('should throw error when `--openssl-legacy-provider` is not enabled', function() {
try {
new NTLMPayload(challenge);
} catch (err) {
assert.strictEqual(err.code, 'ERR_OSSL_EVP_UNSUPPORTED');
}
};

const response = new NTLMPayload(challenge);

const expectedLength =
8 + // NTLM protocol header
4 + // NTLM message type
8 + // lmv index
8 + // ntlm index
8 + // domain index
8 + // user index
16 + // header index
4 + // flags
2 * 6 + // domain
2 * 8 + // username
24 + // lmv2 data
16 + // ntlmv2 data
8 + // flags
8 + // timestamp
8 + // client nonce
4 + // placeholder
4 + // target data
4; // placeholder

const domainName = response.data.slice(64, 76).toString('ucs2');
const userName = response.data.slice(76, 92).toString('ucs2');
const targetData = response.data.slice(160, 164).toString('hex');

assert.strictEqual(domainName, 'domain');
assert.strictEqual(userName, 'username');
assert.strictEqual(targetData, 'aaaaaaaa');

assert.strictEqual(expectedLength, response.data.length);
});

it('should respond to challenge when `--openssl-legacy-provider` is enabled`', function() {
const child = childProcess.spawnSync(process.execPath, ['node_modules/mocha/bin/mocha', '--openssl-legacy-provider',
'./test/unit/child-processes/ntlm-payload-node17.js'], { encoding: 'utf8' });
assert.strictEqual(child.status, 0);
});
});
});
}

0 comments on commit 57a50d4

Please sign in to comment.