This commit is contained in:
Pierre Ossman 2022-08-19 10:10:10 +02:00
commit 1d148a8478
2 changed files with 262 additions and 196 deletions

View File

@ -54,6 +54,21 @@ const GESTURE_SCRLSENS = 50;
const DOUBLE_TAP_TIMEOUT = 1000; const DOUBLE_TAP_TIMEOUT = 1000;
const DOUBLE_TAP_THRESHOLD = 50; const DOUBLE_TAP_THRESHOLD = 50;
// Security types
const securityTypeNone = 1;
const securityTypeVNCAuth = 2;
const securityTypeRA2ne = 6;
const securityTypeTight = 16;
const securityTypeVeNCrypt = 19;
const securityTypeXVP = 22;
const securityTypeARD = 30;
// Special Tight security types
const securityTypeUnixLogon = 129;
// VeNCrypt security types
const securityTypePlain = 256;
// Extended clipboard pseudo-encoding formats // Extended clipboard pseudo-encoding formats
const extendedClipboardFormatText = 1; const extendedClipboardFormatText = 1;
/*eslint-disable no-unused-vars */ /*eslint-disable no-unused-vars */
@ -402,7 +417,7 @@ export default class RFB extends EventTargetMixin {
sendCredentials(creds) { sendCredentials(creds) {
this._rfbCredentials = creds; this._rfbCredentials = creds;
setTimeout(this._initMsg.bind(this), 0); this._resumeAuthentication();
} }
sendCtrlAltDel() { sendCtrlAltDel() {
@ -922,8 +937,15 @@ export default class RFB extends EventTargetMixin {
} }
} }
break; break;
case 'connecting':
while (this._rfbConnectionState === 'connecting') {
if (!this._initMsg()) {
break;
}
}
break;
default: default:
this._initMsg(); Log.Error("Got data while in an invalid state");
break; break;
} }
} }
@ -1332,6 +1354,21 @@ export default class RFB extends EventTargetMixin {
this._rfbInitState = 'Security'; this._rfbInitState = 'Security';
} }
_isSupportedSecurityType(type) {
const clientTypes = [
securityTypeNone,
securityTypeVNCAuth,
securityTypeRA2ne,
securityTypeTight,
securityTypeVeNCrypt,
securityTypeXVP,
securityTypeARD,
securityTypePlain,
];
return clientTypes.includes(type);
}
_negotiateSecurity() { _negotiateSecurity() {
if (this._rfbVersion >= 3.7) { if (this._rfbVersion >= 3.7) {
// Server sends supported list, client decides // Server sends supported list, client decides
@ -1342,28 +1379,23 @@ export default class RFB extends EventTargetMixin {
this._rfbInitState = "SecurityReason"; this._rfbInitState = "SecurityReason";
this._securityContext = "no security types"; this._securityContext = "no security types";
this._securityStatus = 1; this._securityStatus = 1;
return this._initMsg(); return true;
} }
const types = this._sock.rQshiftBytes(numTypes); const types = this._sock.rQshiftBytes(numTypes);
Log.Debug("Server security types: " + types); Log.Debug("Server security types: " + types);
// Look for each auth in preferred order // Look for a matching security type in the order that the
if (types.includes(1)) { // server prefers
this._rfbAuthScheme = 1; // None this._rfbAuthScheme = -1;
} else if (types.includes(22)) { for (let type of types) {
this._rfbAuthScheme = 22; // XVP if (this._isSupportedSecurityType(type)) {
} else if (types.includes(16)) { this._rfbAuthScheme = type;
this._rfbAuthScheme = 16; // Tight break;
} else if (types.includes(6)) { }
this._rfbAuthScheme = 6; // RA2ne Auth }
} else if (types.includes(2)) {
this._rfbAuthScheme = 2; // VNC Auth if (this._rfbAuthScheme === -1) {
} else if (types.includes(30)) {
this._rfbAuthScheme = 30; // ARD Auth
} else if (types.includes(19)) {
this._rfbAuthScheme = 19; // VeNCrypt Auth
} else {
return this._fail("Unsupported security types (types: " + types + ")"); return this._fail("Unsupported security types (types: " + types + ")");
} }
@ -1377,14 +1409,14 @@ export default class RFB extends EventTargetMixin {
this._rfbInitState = "SecurityReason"; this._rfbInitState = "SecurityReason";
this._securityContext = "authentication scheme"; this._securityContext = "authentication scheme";
this._securityStatus = 1; this._securityStatus = 1;
return this._initMsg(); return true;
} }
} }
this._rfbInitState = 'Authentication'; this._rfbInitState = 'Authentication';
Log.Debug('Authenticating using scheme: ' + this._rfbAuthScheme); Log.Debug('Authenticating using scheme: ' + this._rfbAuthScheme);
return this._initMsg(); // jump to authentication return true;
} }
_handleSecurityReason() { _handleSecurityReason() {
@ -1434,7 +1466,7 @@ export default class RFB extends EventTargetMixin {
this._rfbCredentials.username + this._rfbCredentials.username +
this._rfbCredentials.target; this._rfbCredentials.target;
this._sock.sendString(xvpAuthStr); this._sock.sendString(xvpAuthStr);
this._rfbAuthScheme = 2; this._rfbAuthScheme = securityTypeVNCAuth;
return this._negotiateAuthentication(); return this._negotiateAuthentication();
} }
@ -1492,18 +1524,36 @@ export default class RFB extends EventTargetMixin {
subtypes.push(this._sock.rQshift32()); subtypes.push(this._sock.rQshift32());
} }
// 256 = Plain subtype // Look for a matching security type in the order that the
if (subtypes.indexOf(256) != -1) { // server prefers
// 0x100 = 256 this._rfbAuthScheme = -1;
this._sock.send([0, 0, 1, 0]); for (let type of subtypes) {
this._rfbVeNCryptState = 4; // Avoid getting in to a loop
} else { if (type === securityTypeVeNCrypt) {
return this._fail("VeNCrypt Plain subtype not offered by server"); continue;
}
if (this._isSupportedSecurityType(type)) {
this._rfbAuthScheme = type;
break;
} }
} }
// negotiated Plain subtype, server waits for password if (this._rfbAuthScheme === -1) {
if (this._rfbVeNCryptState == 4) { return this._fail("Unsupported security types (types: " + subtypes + ")");
}
this._sock.send([this._rfbAuthScheme >> 24,
this._rfbAuthScheme >> 16,
this._rfbAuthScheme >> 8,
this._rfbAuthScheme]);
this._rfbVeNCryptState == 4;
return true;
}
}
_negotiatePlainAuth() {
if (this._rfbCredentials.username === undefined || if (this._rfbCredentials.username === undefined ||
this._rfbCredentials.password === undefined) { this._rfbCredentials.password === undefined) {
this.dispatchEvent(new CustomEvent( this.dispatchEvent(new CustomEvent(
@ -1533,7 +1583,6 @@ export default class RFB extends EventTargetMixin {
this._rfbInitState = "SecurityResult"; this._rfbInitState = "SecurityResult";
return true; return true;
} }
}
_negotiateStdVNCAuth() { _negotiateStdVNCAuth() {
if (this._sock.rQwait("auth challenge", 16)) { return false; } if (this._sock.rQwait("auth challenge", 16)) { return false; }
@ -1661,7 +1710,7 @@ export default class RFB extends EventTargetMixin {
this._rfbCredentials.ardCredentials = encrypted; this._rfbCredentials.ardCredentials = encrypted;
this._rfbCredentials.ardPublicKey = clientPublicKey; this._rfbCredentials.ardPublicKey = clientPublicKey;
setTimeout(this._initMsg.bind(this), 0); this._resumeAuthentication();
} }
_negotiateTightUnixAuth() { _negotiateTightUnixAuth() {
@ -1771,12 +1820,12 @@ export default class RFB extends EventTargetMixin {
case 'STDVNOAUTH__': // no auth case 'STDVNOAUTH__': // no auth
this._rfbInitState = 'SecurityResult'; this._rfbInitState = 'SecurityResult';
return true; return true;
case 'STDVVNCAUTH_': // VNC auth case 'STDVVNCAUTH_':
this._rfbAuthScheme = 2; this._rfbAuthScheme = securityTypeVNCAuth;
return this._initMsg(); return true;
case 'TGHTULGNAUTH': // UNIX auth case 'TGHTULGNAUTH':
this._rfbAuthScheme = 129; this._rfbAuthScheme = securityTypeUnixLogon;
return this._initMsg(); return true;
default: default:
return this._fail("Unsupported tiny auth scheme " + return this._fail("Unsupported tiny auth scheme " +
"(scheme: " + authType + ")"); "(scheme: " + authType + ")");
@ -1813,7 +1862,7 @@ export default class RFB extends EventTargetMixin {
}).then(() => { }).then(() => {
this.dispatchEvent(new CustomEvent('securityresult')); this.dispatchEvent(new CustomEvent('securityresult'));
this._rfbInitState = "SecurityResult"; this._rfbInitState = "SecurityResult";
this._initMsg(); return true;
}).finally(() => { }).finally(() => {
this._rfbRSAAESAuthenticationState.removeEventListener( this._rfbRSAAESAuthenticationState.removeEventListener(
"serververification", this._eventHandlers.handleRSAAESServerVerification); "serververification", this._eventHandlers.handleRSAAESServerVerification);
@ -1827,33 +1876,32 @@ export default class RFB extends EventTargetMixin {
_negotiateAuthentication() { _negotiateAuthentication() {
switch (this._rfbAuthScheme) { switch (this._rfbAuthScheme) {
case 1: // no auth case securityTypeNone:
if (this._rfbVersion >= 3.8) {
this._rfbInitState = 'SecurityResult'; this._rfbInitState = 'SecurityResult';
return true; return true;
}
this._rfbInitState = 'ClientInitialisation';
return this._initMsg();
case 22: // XVP auth case securityTypeXVP:
return this._negotiateXvpAuth(); return this._negotiateXvpAuth();
case 30: // ARD auth case securityTypeARD:
return this._negotiateARDAuth(); return this._negotiateARDAuth();
case 2: // VNC authentication case securityTypeVNCAuth:
return this._negotiateStdVNCAuth(); return this._negotiateStdVNCAuth();
case 16: // TightVNC Security Type case securityTypeTight:
return this._negotiateTightAuth(); return this._negotiateTightAuth();
case 19: // VeNCrypt Security Type case securityTypeVeNCrypt:
return this._negotiateVeNCryptAuth(); return this._negotiateVeNCryptAuth();
case 129: // TightVNC UNIX Security Type case securityTypePlain:
return this._negotiatePlainAuth();
case securityTypeUnixLogon:
return this._negotiateTightUnixAuth(); return this._negotiateTightUnixAuth();
case 6: // RA2ne Security Type case securityTypeRA2ne:
return this._negotiateRA2neAuth(); return this._negotiateRA2neAuth();
default: default:
@ -1863,6 +1911,13 @@ export default class RFB extends EventTargetMixin {
} }
_handleSecurityResult() { _handleSecurityResult() {
// There is no security choice, and hence no security result
// until RFB 3.7
if (this._rfbVersion < 3.7) {
this._rfbInitState = 'ClientInitialisation';
return true;
}
if (this._sock.rQwait('VNC auth response ', 4)) { return false; } if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
const status = this._sock.rQshift32(); const status = this._sock.rQshift32();
@ -1870,13 +1925,13 @@ export default class RFB extends EventTargetMixin {
if (status === 0) { // OK if (status === 0) { // OK
this._rfbInitState = 'ClientInitialisation'; this._rfbInitState = 'ClientInitialisation';
Log.Debug('Authentication OK'); Log.Debug('Authentication OK');
return this._initMsg(); return true;
} else { } else {
if (this._rfbVersion >= 3.8) { if (this._rfbVersion >= 3.8) {
this._rfbInitState = "SecurityReason"; this._rfbInitState = "SecurityReason";
this._securityContext = "security result"; this._securityContext = "security result";
this._securityStatus = status; this._securityStatus = status;
return this._initMsg(); return true;
} else { } else {
this.dispatchEvent(new CustomEvent( this.dispatchEvent(new CustomEvent(
"securityfailure", "securityfailure",
@ -2052,6 +2107,14 @@ export default class RFB extends EventTargetMixin {
} }
} }
// Resume authentication handshake after it was paused for some
// reason, e.g. waiting for a password from the user
_resumeAuthentication() {
// We use setTimeout() so it's run in its own context, just like
// it originally did via the WebSocket's event handler
setTimeout(this._initMsg.bind(this), 0);
}
_handleSetColourMapMsg() { _handleSetColourMapMsg() {
Log.Debug("SetColorMapEntries"); Log.Debug("SetColorMapEntries");

View File

@ -1026,7 +1026,6 @@ describe('Remote Frame Buffer Protocol Client', function () {
client._rfbConnectionState = 'connecting'; client._rfbConnectionState = 'connecting';
}); });
describe('ProtocolVersion', function () {
function sendVer(ver, client) { function sendVer(ver, client) {
const arr = new Uint8Array(12); const arr = new Uint8Array(12);
for (let i = 0; i < ver.length; i++) { for (let i = 0; i < ver.length; i++) {
@ -1037,6 +1036,11 @@ describe('Remote Frame Buffer Protocol Client', function () {
client._sock._websocket._receiveData(arr); client._sock._websocket._receiveData(arr);
} }
function sendSecurity(type, cl) {
cl._sock._websocket._receiveData(new Uint8Array([1, type]));
}
describe('ProtocolVersion', function () {
describe('version parsing', function () { describe('version parsing', function () {
it('should interpret version 003.003 as version 3.3', function () { it('should interpret version 003.003 as version 3.3', function () {
sendVer('003.003', client); sendVer('003.003', client);
@ -1127,44 +1131,24 @@ describe('Remote Frame Buffer Protocol Client', function () {
describe('Security', function () { describe('Security', function () {
beforeEach(function () { beforeEach(function () {
client._rfbInitState = 'Security'; sendVer('003.008\n', client);
client._sock._websocket._getSentData();
}); });
it('should simply receive the auth scheme when for versions < 3.7', function () { it('should respect server preference order', function () {
client._rfbVersion = 3.6; const authSchemes = [ 6, 79, 30, 188, 16, 6, 1 ];
const authSchemeRaw = [1, 2, 3, 4];
const authScheme = (authSchemeRaw[0] << 24) + (authSchemeRaw[1] << 16) +
(authSchemeRaw[2] << 8) + authSchemeRaw[3];
client._sock._websocket._receiveData(new Uint8Array(authSchemeRaw));
expect(client._rfbAuthScheme).to.equal(authScheme);
});
it('should prefer no authentication is possible', function () {
client._rfbVersion = 3.7;
const authSchemes = [2, 1, 3];
client._sock._websocket._receiveData(new Uint8Array(authSchemes)); client._sock._websocket._receiveData(new Uint8Array(authSchemes));
expect(client._rfbAuthScheme).to.equal(1); expect(client._sock).to.have.sent(new Uint8Array([30]));
expect(client._sock).to.have.sent(new Uint8Array([1, 1]));
}); });
it('should choose for the most prefered scheme possible for versions >= 3.7', function () { it('should fail if there are no supported schemes', function () {
client._rfbVersion = 3.7;
const authSchemes = [2, 22, 16];
client._sock._websocket._receiveData(new Uint8Array(authSchemes));
expect(client._rfbAuthScheme).to.equal(22);
expect(client._sock).to.have.sent(new Uint8Array([22]));
});
it('should fail if there are no supported schemes for versions >= 3.7', function () {
sinon.spy(client, "_fail"); sinon.spy(client, "_fail");
client._rfbVersion = 3.7;
const authSchemes = [1, 32]; const authSchemes = [1, 32];
client._sock._websocket._receiveData(new Uint8Array(authSchemes)); client._sock._websocket._receiveData(new Uint8Array(authSchemes));
expect(client._fail).to.have.been.calledOnce; expect(client._fail).to.have.been.calledOnce;
}); });
it('should fail with the appropriate message if no types are sent for versions >= 3.7', function () { it('should fail with the appropriate message if no types are sent', function () {
client._rfbVersion = 3.7;
const failureData = [0, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115]; const failureData = [0, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
sinon.spy(client, '_fail'); sinon.spy(client, '_fail');
client._sock._websocket._receiveData(new Uint8Array(failureData)); client._sock._websocket._receiveData(new Uint8Array(failureData));
@ -1175,7 +1159,6 @@ describe('Remote Frame Buffer Protocol Client', function () {
}); });
it('should transition to the Authentication state and continue on successful negotiation', function () { it('should transition to the Authentication state and continue on successful negotiation', function () {
client._rfbVersion = 3.7;
const authSchemes = [1, 1]; const authSchemes = [1, 1];
client._negotiateAuthentication = sinon.spy(); client._negotiateAuthentication = sinon.spy();
client._sock._websocket._receiveData(new Uint8Array(authSchemes)); client._sock._websocket._receiveData(new Uint8Array(authSchemes));
@ -1184,17 +1167,8 @@ describe('Remote Frame Buffer Protocol Client', function () {
}); });
}); });
describe('Authentication', function () { describe('Legacy Authentication', function () {
beforeEach(function () {
client._rfbInitState = 'Security';
});
function sendSecurity(type, cl) {
cl._sock._websocket._receiveData(new Uint8Array([1, type]));
}
it('should fail on auth scheme 0 (pre 3.7) with the given message', function () { it('should fail on auth scheme 0 (pre 3.7) with the given message', function () {
client._rfbVersion = 3.6;
const errMsg = "Whoopsies"; const errMsg = "Whoopsies";
const data = [0, 0, 0, 0]; const data = [0, 0, 0, 0];
const errLen = errMsg.length; const errLen = errMsg.length;
@ -1203,37 +1177,42 @@ describe('Remote Frame Buffer Protocol Client', function () {
data.push(errMsg.charCodeAt(i)); data.push(errMsg.charCodeAt(i));
} }
sendVer('003.006\n', client);
client._sock._websocket._getSentData();
sinon.spy(client, '_fail'); sinon.spy(client, '_fail');
client._sock._websocket._receiveData(new Uint8Array(data)); client._sock._websocket._receiveData(new Uint8Array(data));
expect(client._fail).to.have.been.calledWith( expect(client._fail).to.have.been.calledWith(
'Security negotiation failed on authentication scheme (reason: Whoopsies)'); 'Security negotiation failed on authentication scheme (reason: Whoopsies)');
}); });
it('should transition straight to SecurityResult on "no auth" (1) for versions >= 3.8', function () { it('should transition straight to ServerInitialisation on "no auth" for versions < 3.7', function () {
client._rfbVersion = 3.8; sendVer('003.006\n', client);
client._sock._websocket._getSentData();
client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 1]));
expect(client._rfbInitState).to.equal('ServerInitialisation');
});
});
describe('Authentication', function () {
beforeEach(function () {
sendVer('003.008\n', client);
client._sock._websocket._getSentData();
});
it('should transition straight to SecurityResult on "no auth" (1)', function () {
sendSecurity(1, client); sendSecurity(1, client);
expect(client._rfbInitState).to.equal('SecurityResult'); expect(client._rfbInitState).to.equal('SecurityResult');
}); });
it('should transition straight to ServerInitialisation on "no auth" for versions < 3.8', function () {
client._rfbVersion = 3.7;
sendSecurity(1, client);
expect(client._rfbInitState).to.equal('ServerInitialisation');
});
it('should fail on an unknown auth scheme', function () { it('should fail on an unknown auth scheme', function () {
sinon.spy(client, "_fail"); sinon.spy(client, "_fail");
client._rfbVersion = 3.8;
sendSecurity(57, client); sendSecurity(57, client);
expect(client._fail).to.have.been.calledOnce; expect(client._fail).to.have.been.calledOnce;
}); });
describe('VNC Authentication (type 2) Handler', function () { describe('VNC Authentication (type 2) Handler', function () {
beforeEach(function () {
client._rfbInitState = 'Security';
client._rfbVersion = 3.8;
});
it('should fire the credentialsrequired event if missing a password', function () { it('should fire the credentialsrequired event if missing a password', function () {
const spy = sinon.spy(); const spy = sinon.spy();
client.addEventListener("credentialsrequired", spy); client.addEventListener("credentialsrequired", spy);
@ -1274,12 +1253,6 @@ describe('Remote Frame Buffer Protocol Client', function () {
}); });
describe('ARD Authentication (type 30) Handler', function () { describe('ARD Authentication (type 30) Handler', function () {
beforeEach(function () {
client._rfbInitState = 'Security';
client._rfbVersion = 3.8;
});
it('should fire the credentialsrequired event if all credentials are missing', function () { it('should fire the credentialsrequired event if all credentials are missing', function () {
const spy = sinon.spy(); const spy = sinon.spy();
client.addEventListener("credentialsrequired", spy); client.addEventListener("credentialsrequired", spy);
@ -1347,11 +1320,6 @@ describe('Remote Frame Buffer Protocol Client', function () {
}); });
describe('XVP Authentication (type 22) Handler', function () { describe('XVP Authentication (type 22) Handler', function () {
beforeEach(function () {
client._rfbInitState = 'Security';
client._rfbVersion = 3.8;
});
it('should fall through to standard VNC authentication upon completion', function () { it('should fall through to standard VNC authentication upon completion', function () {
client._rfbCredentials = { username: 'user', client._rfbCredentials = { username: 'user',
target: 'target', target: 'target',
@ -1400,8 +1368,6 @@ describe('Remote Frame Buffer Protocol Client', function () {
describe('TightVNC Authentication (type 16) Handler', function () { describe('TightVNC Authentication (type 16) Handler', function () {
beforeEach(function () { beforeEach(function () {
client._rfbInitState = 'Security';
client._rfbVersion = 3.8;
sendSecurity(16, client); sendSecurity(16, client);
client._sock._websocket._getSentData(); // skip the security reply client._sock._websocket._getSentData(); // skip the security reply
}); });
@ -1487,8 +1453,6 @@ describe('Remote Frame Buffer Protocol Client', function () {
describe('VeNCrypt Authentication (type 19) Handler', function () { describe('VeNCrypt Authentication (type 19) Handler', function () {
beforeEach(function () { beforeEach(function () {
client._rfbInitState = 'Security';
client._rfbVersion = 3.8;
sendSecurity(19, client); sendSecurity(19, client);
expect(client._sock).to.have.sent(new Uint8Array([19])); expect(client._sock).to.have.sent(new Uint8Array([19]));
}); });
@ -1499,18 +1463,70 @@ describe('Remote Frame Buffer Protocol Client', function () {
expect(client._fail).to.have.been.calledOnce; 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 // VeNCrypt version
client._sock._websocket._receiveData(new Uint8Array([0, 2])); client._sock._websocket._receiveData(new Uint8Array([0, 2]));
expect(client._sock).to.have.sent(new Uint8Array([0, 2])); expect(client._sock).to.have.sent(new Uint8Array([0, 2]));
// Server ACK. // Server ACK.
client._sock._websocket._receiveData(new Uint8Array([0])); client._sock._websocket._receiveData(new Uint8Array([0]));
// Subtype list, only list subtype 1. // Subtype list
sinon.spy(client, "_fail"); 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; 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 () { it('should support Plain authentication', function () {
client._rfbCredentials = { username: 'username', password: 'password' }; client._rfbCredentials = { username: 'username', password: 'password' };
// VeNCrypt version // VeNCrypt version
@ -1582,9 +1598,30 @@ describe('Remote Frame Buffer Protocol Client', function () {
}); });
}); });
describe('Legacy SecurityResult', function () {
beforeEach(function () {
sendVer('003.007\n', client);
client._sock._websocket._getSentData();
sendSecurity(1, client);
client._sock._websocket._getSentData();
});
it('should not include reason in securityfailure event', function () {
const spy = sinon.spy();
client.addEventListener("securityfailure", spy);
client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 2]));
expect(spy).to.have.been.calledOnce;
expect(spy.args[0][0].detail.status).to.equal(2);
expect('reason' in spy.args[0][0].detail).to.be.false;
});
});
describe('SecurityResult', function () { describe('SecurityResult', function () {
beforeEach(function () { beforeEach(function () {
client._rfbInitState = 'SecurityResult'; sendVer('003.008\n', client);
client._sock._websocket._getSentData();
sendSecurity(1, client);
client._sock._websocket._getSentData();
}); });
it('should fall through to ServerInitialisation on a response code of 0', function () { it('should fall through to ServerInitialisation on a response code of 0', function () {
@ -1592,60 +1629,26 @@ describe('Remote Frame Buffer Protocol Client', function () {
expect(client._rfbInitState).to.equal('ServerInitialisation'); expect(client._rfbInitState).to.equal('ServerInitialisation');
}); });
it('should fail on an error code of 1 with the given message for versions >= 3.8', function () {
client._rfbVersion = 3.8;
sinon.spy(client, '_fail');
const failureData = [0, 0, 0, 1, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
client._sock._websocket._receiveData(new Uint8Array(failureData));
expect(client._fail).to.have.been.calledWith(
'Security negotiation failed on security result (reason: whoops)');
});
it('should fail on an error code of 1 with a standard message for version < 3.8', function () {
sinon.spy(client, '_fail');
client._rfbVersion = 3.7;
client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 1]));
expect(client._fail).to.have.been.calledWith(
'Security handshake failed');
});
it('should result in securityfailure event when receiving a non zero status', function () {
const spy = sinon.spy();
client.addEventListener("securityfailure", spy);
client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 2]));
expect(spy).to.have.been.calledOnce;
expect(spy.args[0][0].detail.status).to.equal(2);
});
it('should include reason when provided in securityfailure event', function () { it('should include reason when provided in securityfailure event', function () {
client._rfbVersion = 3.8;
const spy = sinon.spy(); const spy = sinon.spy();
client.addEventListener("securityfailure", spy); client.addEventListener("securityfailure", spy);
const failureData = [0, 0, 0, 1, 0, 0, 0, 12, 115, 117, 99, 104, const failureData = [0, 0, 0, 1, 0, 0, 0, 12, 115, 117, 99, 104,
32, 102, 97, 105, 108, 117, 114, 101]; 32, 102, 97, 105, 108, 117, 114, 101];
client._sock._websocket._receiveData(new Uint8Array(failureData)); client._sock._websocket._receiveData(new Uint8Array(failureData));
expect(spy).to.have.been.calledOnce;
expect(spy.args[0][0].detail.status).to.equal(1); expect(spy.args[0][0].detail.status).to.equal(1);
expect(spy.args[0][0].detail.reason).to.equal('such failure'); expect(spy.args[0][0].detail.reason).to.equal('such failure');
}); });
it('should not include reason when length is zero in securityfailure event', function () { it('should not include reason when length is zero in securityfailure event', function () {
client._rfbVersion = 3.9;
const spy = sinon.spy(); const spy = sinon.spy();
client.addEventListener("securityfailure", spy); client.addEventListener("securityfailure", spy);
const failureData = [0, 0, 0, 1, 0, 0, 0, 0]; const failureData = [0, 0, 0, 1, 0, 0, 0, 0];
client._sock._websocket._receiveData(new Uint8Array(failureData)); client._sock._websocket._receiveData(new Uint8Array(failureData));
expect(spy).to.have.been.calledOnce;
expect(spy.args[0][0].detail.status).to.equal(1); expect(spy.args[0][0].detail.status).to.equal(1);
expect('reason' in spy.args[0][0].detail).to.be.false; expect('reason' in spy.args[0][0].detail).to.be.false;
}); });
it('should not include reason in securityfailure event for version < 3.8', function () {
client._rfbVersion = 3.6;
const spy = sinon.spy();
client.addEventListener("securityfailure", spy);
client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 2]));
expect(spy.args[0][0].detail.status).to.equal(2);
expect('reason' in spy.args[0][0].detail).to.be.false;
});
}); });
describe('ClientInitialisation', function () { describe('ClientInitialisation', function () {