Merge branch 'authprio' of https://github.com/CendioOssman/noVNC
This commit is contained in:
commit
1d148a8478
223
core/rfb.js
223
core/rfb.js
|
@ -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,49 +1524,66 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// negotiated Plain subtype, server waits for password
|
if (this._isSupportedSecurityType(type)) {
|
||||||
if (this._rfbVeNCryptState == 4) {
|
this._rfbAuthScheme = type;
|
||||||
if (this._rfbCredentials.username === undefined ||
|
break;
|
||||||
this._rfbCredentials.password === undefined) {
|
}
|
||||||
this.dispatchEvent(new CustomEvent(
|
|
||||||
"credentialsrequired",
|
|
||||||
{ detail: { types: ["username", "password"] } }));
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = encodeUTF8(this._rfbCredentials.username);
|
if (this._rfbAuthScheme === -1) {
|
||||||
const pass = encodeUTF8(this._rfbCredentials.password);
|
return this._fail("Unsupported security types (types: " + subtypes + ")");
|
||||||
|
}
|
||||||
|
|
||||||
this._sock.send([
|
this._sock.send([this._rfbAuthScheme >> 24,
|
||||||
(user.length >> 24) & 0xFF,
|
this._rfbAuthScheme >> 16,
|
||||||
(user.length >> 16) & 0xFF,
|
this._rfbAuthScheme >> 8,
|
||||||
(user.length >> 8) & 0xFF,
|
this._rfbAuthScheme]);
|
||||||
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";
|
this._rfbVeNCryptState == 4;
|
||||||
return true;
|
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() {
|
_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");
|
||||||
|
|
||||||
|
|
|
@ -1026,17 +1026,21 @@ 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++) {
|
arr[i+4] = ver.charCodeAt(i);
|
||||||
arr[i+4] = ver.charCodeAt(i);
|
|
||||||
}
|
|
||||||
arr[0] = 'R'; arr[1] = 'F'; arr[2] = 'B'; arr[3] = ' ';
|
|
||||||
arr[11] = '\n';
|
|
||||||
client._sock._websocket._receiveData(arr);
|
|
||||||
}
|
}
|
||||||
|
arr[0] = 'R'; arr[1] = 'F'; arr[2] = 'B'; arr[3] = ' ';
|
||||||
|
arr[11] = '\n';
|
||||||
|
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 () {
|
||||||
|
|
Loading…
Reference in New Issue