From df8d005de960e2133ded0610656af591e92fc6b7 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 18 Aug 2022 16:15:43 +0200 Subject: VeNCrypt should handle classical types VeNCrypt is a superset of the original security types, so it should be fine to send any of the classical values here as well. --- core/rfb.js | 87 ++++++++++++++++++++++++++++++++++--------------------- tests/test.rfb.js | 58 +++++++++++++++++++++++++++++++++++-- 2 files changed, 109 insertions(+), 36 deletions(-) diff --git a/core/rfb.js b/core/rfb.js index b10b502..4b3526f 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -1524,48 +1524,66 @@ export default class RFB extends EventTargetMixin { subtypes.push(this._sock.rQshift32()); } - if (subtypes.indexOf(securityTypePlain) != -1) { - // 0x100 = 256 - this._sock.send([0, 0, 1, 0]); - this._rfbVeNCryptState = 4; - } else { - return this._fail("VeNCrypt Plain subtype not offered by server"); + // Look for a matching security type in the order that the + // server prefers + this._rfbAuthScheme = -1; + for (let type of subtypes) { + // Avoid getting in to a loop + if (type === securityTypeVeNCrypt) { + continue; + } + + if (this._isSupportedSecurityType(type)) { + this._rfbAuthScheme = type; + break; + } } - } - // negotiated Plain subtype, server waits for password - if (this._rfbVeNCryptState == 4) { - if (this._rfbCredentials.username === undefined || - this._rfbCredentials.password === undefined) { - this.dispatchEvent(new CustomEvent( - "credentialsrequired", - { detail: { types: ["username", "password"] } })); - return false; + if (this._rfbAuthScheme === -1) { + return this._fail("Unsupported security types (types: " + subtypes + ")"); } - const user = encodeUTF8(this._rfbCredentials.username); - const pass = encodeUTF8(this._rfbCredentials.password); - - this._sock.send([ - (user.length >> 24) & 0xFF, - (user.length >> 16) & 0xFF, - (user.length >> 8) & 0xFF, - user.length & 0xFF - ]); - this._sock.send([ - (pass.length >> 24) & 0xFF, - (pass.length >> 16) & 0xFF, - (pass.length >> 8) & 0xFF, - pass.length & 0xFF - ]); - this._sock.sendString(user); - this._sock.sendString(pass); + this._sock.send([this._rfbAuthScheme >> 24, + this._rfbAuthScheme >> 16, + this._rfbAuthScheme >> 8, + this._rfbAuthScheme]); - this._rfbInitState = "SecurityResult"; + this._rfbVeNCryptState == 4; return true; } } + _negotiatePlainAuth() { + if (this._rfbCredentials.username === undefined || + this._rfbCredentials.password === undefined) { + this.dispatchEvent(new CustomEvent( + "credentialsrequired", + { detail: { types: ["username", "password"] } })); + return false; + } + + const user = encodeUTF8(this._rfbCredentials.username); + const pass = encodeUTF8(this._rfbCredentials.password); + + this._sock.send([ + (user.length >> 24) & 0xFF, + (user.length >> 16) & 0xFF, + (user.length >> 8) & 0xFF, + user.length & 0xFF + ]); + this._sock.send([ + (pass.length >> 24) & 0xFF, + (pass.length >> 16) & 0xFF, + (pass.length >> 8) & 0xFF, + pass.length & 0xFF + ]); + this._sock.sendString(user); + this._sock.sendString(pass); + + this._rfbInitState = "SecurityResult"; + return true; + } + _negotiateStdVNCAuth() { if (this._sock.rQwait("auth challenge", 16)) { return false; } @@ -1877,6 +1895,9 @@ export default class RFB extends EventTargetMixin { case securityTypeVeNCrypt: return this._negotiateVeNCryptAuth(); + case securityTypePlain: + return this._negotiatePlainAuth(); + case securityTypeUnixLogon: return this._negotiateTightUnixAuth(); diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 0e46ff4..75d1e11 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -1463,18 +1463,70 @@ describe('Remote Frame Buffer Protocol Client', function () { expect(client._fail).to.have.been.calledOnce; }); - it('should fail if the Plain authentication is not present', function () { + it('should fail if there are no supported subtypes', function () { // VeNCrypt version client._sock._websocket._receiveData(new Uint8Array([0, 2])); expect(client._sock).to.have.sent(new Uint8Array([0, 2])); // Server ACK. client._sock._websocket._receiveData(new Uint8Array([0])); - // Subtype list, only list subtype 1. + // Subtype list sinon.spy(client, "_fail"); - client._sock._websocket._receiveData(new Uint8Array([1, 0, 0, 0, 1])); + client._sock._websocket._receiveData(new Uint8Array([2, 0, 0, 0, 9, 0, 0, 1, 4])); expect(client._fail).to.have.been.calledOnce; }); + it('should support standard types', function () { + // VeNCrypt version + client._sock._websocket._receiveData(new Uint8Array([0, 2])); + expect(client._sock).to.have.sent(new Uint8Array([0, 2])); + // Server ACK. + client._sock._websocket._receiveData(new Uint8Array([0])); + // Subtype list + client._sock._websocket._receiveData(new Uint8Array([2, 0, 0, 0, 2, 0, 0, 1, 4])); + + let expectedResponse = []; + push32(expectedResponse, 2); // Chosen subtype. + + expect(client._sock).to.have.sent(new Uint8Array(expectedResponse)); + }); + + it('should respect server preference order', function () { + // VeNCrypt version + client._sock._websocket._receiveData(new Uint8Array([0, 2])); + expect(client._sock).to.have.sent(new Uint8Array([0, 2])); + // Server ACK. + client._sock._websocket._receiveData(new Uint8Array([0])); + // Subtype list + let subtypes = [ 6 ]; + push32(subtypes, 79); + push32(subtypes, 30); + push32(subtypes, 188); + push32(subtypes, 256); + push32(subtypes, 6); + push32(subtypes, 1); + client._sock._websocket._receiveData(new Uint8Array(subtypes)); + + let expectedResponse = []; + push32(expectedResponse, 30); // Chosen subtype. + + expect(client._sock).to.have.sent(new Uint8Array(expectedResponse)); + }); + + it('should ignore redundant VeNCrypt subtype', function () { + // VeNCrypt version + client._sock._websocket._receiveData(new Uint8Array([0, 2])); + expect(client._sock).to.have.sent(new Uint8Array([0, 2])); + // Server ACK. + client._sock._websocket._receiveData(new Uint8Array([0])); + // Subtype list + client._sock._websocket._receiveData(new Uint8Array([2, 0, 0, 0, 19, 0, 0, 0, 2])); + + let expectedResponse = []; + push32(expectedResponse, 2); // Chosen subtype. + + expect(client._sock).to.have.sent(new Uint8Array(expectedResponse)); + }); + it('should support Plain authentication', function () { client._rfbCredentials = { username: 'username', password: 'password' }; // VeNCrypt version -- cgit v1.2.1