Add support for ARD Security Type

This commit is contained in:
pdlan 2022-04-02 21:26:34 -04:00
parent 7730814b8d
commit ff8e2364a5
4 changed files with 463 additions and 6 deletions

258
core/ard.js Normal file
View File

@ -0,0 +1,258 @@
import { encodeUTF8 } from './util/strings.js';
import EventTargetMixin from './util/eventtarget.js';
export class AESBlockCipher {
constructor() {
this._key = null;
this._zeroBlock = new Uint8Array(16);
}
async setKey(key) {
this._key = await window.crypto.subtle.importKey(
"raw", key, {"name": "AES-CBC"}, false, ["encrypt", "decrypt"]);
}
async encrypt(block) {
const encrypted = await window.crypto.subtle.encrypt({
name: "AES-CBC",
iv: this._zeroBlock,
}, this._key, block);
return new Uint8Array(encrypted).slice(0, 16);
}
async encryptInplace(data, offset) {
let block = data.slice(offset, offset + 16);
block = await this.encrypt(block);
data.set(block, offset);
}
}
export default class ARDAuthenticationState extends EventTargetMixin {
constructor(sock, getCredentials) {
super();
this._hasStarted = false;
this._checkSock = null;
this._checkCredentials = null;
this._sockReject = null;
this._credentialsReject = null;
this._sock = sock;
this._getCredentials = getCredentials;
}
_waitSockAsync(len) {
return new Promise((resolve, reject) => {
const hasData = () => !this._sock.rQwait('RA2', len);
if (hasData()) {
resolve();
} else {
this._checkSock = () => {
if (hasData()) {
resolve();
this._checkSock = null;
this._sockReject = null;
}
};
this._sockReject = reject;
}
});
}
_waitCredentialsAsync() {
const hasCredentials = () => (
this._getCredentials().username !== undefined && this._getCredentials().password !== undefined
);
return new Promise((resolve, reject) => {
if (hasCredentials()) {
resolve();
} else {
this._checkCredentials = () => {
if (hasCredentials()) {
resolve();
this._checkCredentials = null;
this._credentialsReject = null;
}
};
this._credentialsReject = reject;
}
});
}
_md5(msg) {
const s = [
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
];
const k = [
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,
];
let a0 = 0x67452301, b0 = 0xefcdab89, c0 = 0x98badcfe, d0 = 0x10325476;
const length = msg.length + 1;
const padLength = Math.ceil((length - 56) / 64) * 64 + 64;
const x = new Uint8Array(padLength);
x.set(msg);
x[msg.length] = 0x80;
let view = new DataView(x.buffer);
view.setUint32(padLength - 8, msg.length * 8, true);
for (let i = 0; i < padLength; i += 64) {
let a = a0, b = b0, c = c0, d = d0;
for (let j = 0; j < 64; j++) {
let f, g;
if (j < 16) {
f = (b & c) | (~b & d);
g = j;
} else if (j < 32) {
f = (d & b) | (~d & c);
g = (5 * j + 1) % 16;
} else if (j < 48) {
f = b ^ c ^ d;
g = (3 * j + 5) % 16;
} else {
f = c ^ (b | ~d);
g = (7 * j) % 16;
}
f = (f + a + k[j] + view.getUint32(i + g * 4, true)) >>> 0;
a = d;
d = c;
c = b;
b = (b + ((f << s[j]) | (f >>> (32 - s[j])))) >>> 0;
}
a0 = (a0 + a) >>> 0;
b0 = (b0 + b) >>> 0;
c0 = (c0 + c) >>> 0;
d0 = (d0 + d) >>> 0;
}
const digest = new Uint8Array(16);
view = new DataView(digest.buffer);
view.setUint32(0, a0, true);
view.setUint32(4, b0, true);
view.setUint32(8, c0, true);
view.setUint32(12, d0, true);
return digest;
}
_u8ArrayToBigInt(arr) {
let hex = '0x';
for (let i = 0; i < arr.length; i++) {
hex += arr[i].toString(16).padStart(2, '0');
}
return BigInt(hex);
}
_bigIntToU8Array(bigint, padLength) {
let hex = bigint.toString(16);
hex = hex.padStart(padLength * 2, '0');
const length = hex.length / 2;
const arr = new Uint8Array(length);
for (let i = 0; i < length; i++) {
arr[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
}
return arr;
}
_modPow(b, e, m) {
if (m === 1n) {
return 0;
}
let r = 1n;
b = b % m;
while (e > 0) {
if (e % 2n === 1n) {
r = (r * b) % m;
}
e = e / 2n;
b = (b * b) % m;
}
return r;
}
checkInternalEvents() {
if (this._checkSock !== null) {
this._checkSock();
}
if (this._checkCredentials !== null) {
this._checkCredentials();
}
}
disconnect() {
if (this._sockReject !== null) {
this._sockReject(new Error("disconnect normally"));
this._sockReject = null;
}
if (this._credentialsReject !== null) {
this._credentialsReject(new Error("disconnect normally"));
this._credentialsReject = null;
}
}
async negotiateARDAuthAsync() {
this._hasStarted = true;
await this._waitSockAsync(4);
const generator = this._sock.rQshift16();
const keyLength = this._sock.rQshift16();
await this._waitSockAsync(keyLength * 2);
const primeModulus = this._sock.rQshiftBytes(keyLength);
const serverPublickey = this._sock.rQshiftBytes(keyLength);
const primeModulusBigInt = this._u8ArrayToBigInt(primeModulus);
const serverPublickeyBigInt = this._u8ArrayToBigInt(serverPublickey);
const generatorBigInt = BigInt(generator);
const clientPrivatekey = new Uint8Array(keyLength);
window.crypto.getRandomValues(clientPrivatekey);
const clientPrivatekeyBigInt = this._u8ArrayToBigInt(clientPrivatekey);
const sharedSecret = this._bigIntToU8Array(
this._modPow(serverPublickeyBigInt, clientPrivatekeyBigInt, primeModulusBigInt), keyLength);
const clientPublickey = this._bigIntToU8Array(
this._modPow(generatorBigInt, clientPrivatekeyBigInt, primeModulusBigInt), keyLength);
const aesKey = this._md5(sharedSecret);
const cipher = new AESBlockCipher;
await cipher.setKey(aesKey);
const credentials = new Uint8Array(128);
window.crypto.getRandomValues(credentials);
this.dispatchEvent(new CustomEvent("credentialsrequired", {
detail: { types: ["username", "password"] }
}));
await this._waitCredentialsAsync();
const username = encodeUTF8(this._getCredentials().username).slice(0, 63);
const password = encodeUTF8(this._getCredentials().password).slice(0, 63);
for (let i = 0; i < username.length; i++) {
credentials[i] = username.charCodeAt(i);
}
for (let i = 0; i < password.length; i++) {
credentials[64 + i] = password.charCodeAt(i);
}
credentials[username.length] = 0;
credentials[64 + password.length] = 0;
for (let i = 0; i < 8; i++) {
await cipher.encryptInplace(credentials, i * 16);
}
this._sock.send(credentials);
this._sock.send(clientPublickey);
}
get hasStarted() {
return this._hasStarted;
}
set hasStarted(s) {
this._hasStarted = s;
}
}

View File

@ -26,6 +26,7 @@ import KeyTable from "./input/keysym.js";
import XtScancode from "./input/xtscancodes.js";
import { encodings } from "./encodings.js";
import RSAAESAuthenticationState from "./ra2.js";
import ARDAuthenticationState from "./ard.js";
import RawDecoder from "./decoders/raw.js";
import CopyRectDecoder from "./decoders/copyrect.js";
@ -102,6 +103,7 @@ export default class RFB extends EventTargetMixin {
this._rfbAuthScheme = -1;
this._rfbCleanDisconnect = true;
this._rfbRSAAESAuthenticationState = null;
this._rfbARDAuthenticationState = null;
// Server capabilities
this._rfbVersion = 0;
@ -182,6 +184,7 @@ export default class RFB extends EventTargetMixin {
handleGesture: this._handleGesture.bind(this),
handleRSAAESCredentialsRequired: this._handleRSAAESCredentialsRequired.bind(this),
handleRSAAESServerVerification: this._handleRSAAESServerVerification.bind(this),
handleARDCredentialsRequired: this._handleARDCredentialsRequired.bind(this),
};
// main setup
@ -385,6 +388,9 @@ export default class RFB extends EventTargetMixin {
if (this._rfbRSAAESAuthenticationState !== null) {
this._rfbRSAAESAuthenticationState.disconnect();
}
if (this._rfbARDAuthenticationState != null) {
this._rfbARDAuthenticationState.disconnect();
}
}
approveServer() {
@ -1288,13 +1294,13 @@ export default class RFB extends EventTargetMixin {
break;
case "003.003":
case "003.006": // UltraVNC
case "003.889": // Apple Remote Desktop
this._rfbVersion = 3.3;
break;
case "003.007":
this._rfbVersion = 3.7;
break;
case "003.008":
case "003.889": // Apple Remote Desktop
case "004.000": // Intel AMT KVM
case "004.001": // RealVNC 4.6
case "005.000": // RealVNC 5.3
@ -1354,6 +1360,8 @@ export default class RFB extends EventTargetMixin {
this._rfbAuthScheme = 2; // VNC Auth
} else if (types.includes(19)) {
this._rfbAuthScheme = 19; // VeNCrypt Auth
} else if (types.includes(30)) {
this._rfbAuthScheme = 30; // ARD Auth
} else {
return this._fail("Unsupported security types (types: " + types + ")");
}
@ -1705,6 +1713,36 @@ export default class RFB extends EventTargetMixin {
return false;
}
_handleARDCredentialsRequired(event) {
this.dispatchEvent(event);
}
_negotiateARDAuth() {
if (this._rfbARDAuthenticationState === null) {
this._rfbARDAuthenticationState = new ARDAuthenticationState(this._sock, () => this._rfbCredentials);
this._rfbARDAuthenticationState.addEventListener(
"credentialsrequired", this._eventHandlers.handleARDCredentialsRequired);
}
this._rfbARDAuthenticationState.checkInternalEvents();
if (!this._rfbARDAuthenticationState.hasStarted) {
this._rfbARDAuthenticationState.negotiateARDAuthAsync()
.catch((e) => {
if (e.message !== "disconnect normally") {
this._fail(e.message);
}
}).then(() => {
this.dispatchEvent(new CustomEvent('securityresult'));
this._rfbInitState = "SecurityResult";
this._initMsg();
}).finally(() => {
this._rfbARDAuthenticationState.removeEventListener(
"credentialsrequired", this._eventHandlers.handleARDCredentialsRequired);
this._rfbARDAuthenticationState = null;
});
}
return false;
}
_negotiateAuthentication() {
switch (this._rfbAuthScheme) {
case 1: // no auth
@ -1733,6 +1771,9 @@ export default class RFB extends EventTargetMixin {
case 6: // RA2ne Security Type
return this._negotiateRA2neAuth();
case 30: // ARD Security Type:
return this._negotiateARDAuth();
default:
return this._fail("Unsupported auth scheme (scheme: " +
this._rfbAuthScheme + ")");

158
tests/test.ard.js Normal file
View File

@ -0,0 +1,158 @@
const expect = chai.expect;
import RFB from '../core/rfb.js';
import FakeWebSocket from './fake.websocket.js';
const receiveData = new Uint8Array([
0x00, 0x02, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, 0xa2,
0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6, 0x62, 0x8b,
0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08,
0x8a, 0x67, 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6,
0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79,
0x8e, 0x34, 0x04, 0xdd, 0xef, 0x95, 0x19, 0xb3,
0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d,
0xf2, 0x5f, 0x14, 0x37, 0x4f, 0xe1, 0x35, 0x6d,
0x6d, 0x51, 0xc2, 0x45, 0xe4, 0x85, 0xb5, 0x76,
0x62, 0x5e, 0x7e, 0xc6, 0xf4, 0x4c, 0x42, 0xe9,
0xa6, 0x37, 0xed, 0x6b, 0x0b, 0xff, 0x5c, 0xb6,
0xf4, 0x06, 0xb7, 0xed, 0xee, 0x38, 0x6b, 0xfb,
0x5a, 0x89, 0x9f, 0xa5, 0xae, 0x9f, 0x24, 0x11,
0x7c, 0x4b, 0x1f, 0xe6, 0x49, 0x28, 0x66, 0x51,
0xec, 0xe6, 0x53, 0x81, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0x2e, 0xa9, 0xdc, 0x27,
0x9a, 0xa2, 0xb8, 0x7a, 0x9b, 0xd6, 0x35, 0x20,
0xfd, 0x3b, 0x83, 0x6f, 0xdc, 0x05, 0xe8, 0xfe,
0xd8, 0xaf, 0x2d, 0x19, 0xbb, 0x66, 0xb9, 0xfd,
0xf4, 0x0f, 0x43, 0xe1, 0xe7, 0xbd, 0x11, 0xe3,
0x18, 0xcf, 0x96, 0x0a, 0x54, 0x68, 0x0e, 0x69,
0x39, 0xf2, 0x26, 0xd1, 0xaf, 0x76, 0x11, 0x99,
0xb1, 0x9c, 0xd8, 0x36, 0x4a, 0x46, 0xcf, 0x23,
0xce, 0xd5, 0x19, 0x26, 0xe7, 0xc8, 0xfe, 0xb9,
0x97, 0xc4, 0xbf, 0x83, 0x13, 0xe6, 0xa3, 0xd1,
0x5a, 0x4f, 0x9a, 0x91, 0xc8, 0xc1, 0x19, 0x2e,
0x1d, 0x9c, 0x72, 0x31, 0x78, 0xae, 0x18, 0x17,
0x13, 0x32, 0xe9, 0xf6, 0x8f, 0x72, 0x96, 0x27,
0x35, 0x46, 0xa7, 0x6d, 0x0d, 0x82, 0x24, 0x5a,
0x80, 0xcb, 0xb0, 0x3c, 0x82, 0x9f, 0xb2, 0x77,
0x3e, 0x8a, 0xea, 0x5e, 0xcf, 0xbe, 0x6c, 0x66,
0x06, 0xca, 0x15, 0x82,
]);
const sendData = new Uint8Array([
0x00, 0x55, 0xd1, 0x5e, 0x65, 0x69, 0x25, 0x25,
0xc2, 0xb8, 0x0b, 0x68, 0x9c, 0x1b, 0x2c, 0xd9,
0xa0, 0xe8, 0xb1, 0x26, 0x8c, 0x7b, 0x10, 0x4e,
0xf6, 0x39, 0xbc, 0xc2, 0x17, 0x58, 0x92, 0xf0,
0x5c, 0x87, 0x63, 0x62, 0x5f, 0xea, 0x29, 0x3e,
0x12, 0xb2, 0x3e, 0xb4, 0x9b, 0x8a, 0x95, 0x5d,
0xac, 0x3b, 0xa3, 0x9f, 0x2f, 0xde, 0x45, 0xea,
0x45, 0xb8, 0x41, 0x0b, 0x9e, 0xfd, 0xad, 0x66,
0x04, 0x34, 0xca, 0xa2, 0xf4, 0xd2, 0xc3, 0x33,
0x5c, 0xad, 0x63, 0x8b, 0xae, 0x9a, 0x4f, 0xea,
0xf4, 0x1f, 0x19, 0x1b, 0xb8, 0x86, 0x9c, 0x4f,
0x39, 0x9c, 0xc3, 0x67, 0xe3, 0x02, 0x9b, 0x2a,
0x55, 0x7c, 0x8a, 0xb0, 0xc8, 0xf7, 0x92, 0x8b,
0x9d, 0xc7, 0xb6, 0xb1, 0x90, 0x7b, 0xb7, 0x4c,
0xcc, 0x73, 0x4c, 0x84, 0x0b, 0xdf, 0x91, 0xb6,
0xc5, 0xb3, 0xeb, 0x81, 0xe7, 0x49, 0x2f, 0xe3,
0xf6, 0x90, 0x56, 0xe7, 0xa2, 0xa5, 0x88, 0x04,
0x5f, 0x7b, 0x1a, 0x08, 0xcc, 0xb8, 0xa8, 0xec,
0x62, 0xfd, 0x1e, 0xc2, 0x07, 0x7c, 0x97, 0xfd,
0x77, 0x94, 0x13, 0x80, 0x11, 0xc9, 0xef, 0x4d,
0x01, 0x14, 0x7d, 0x0d, 0x08, 0x1c, 0x6e, 0x14,
0x5b, 0x4d, 0x77, 0x4e, 0xc4, 0xba, 0xb9, 0x44,
0x36, 0xd6, 0xcc, 0x3c, 0xdf, 0xfd, 0xc6, 0xf2,
0x12, 0x0b, 0xdd, 0x3e, 0x57, 0x1b, 0x03, 0x97,
0xe2, 0x17, 0x80, 0x46, 0xaf, 0xa8, 0xad, 0x0e,
0x55, 0x88, 0x38, 0x78, 0xa1, 0xce, 0x8c, 0x1d,
0x2b, 0xa9, 0x8f, 0xbd, 0x9f, 0x95, 0x42, 0x92,
0xf7, 0xcb, 0xdf, 0x94, 0xd6, 0xdf, 0x1b, 0x9a,
0x15, 0x5b, 0x8f, 0xe3, 0x5f, 0x2d, 0x54, 0x7e,
0xf9, 0x95, 0x84, 0xee, 0x0e, 0x16, 0x73, 0x81,
0xbe, 0xb5, 0xf0, 0xe2, 0x6f, 0x65, 0xb0, 0xe9,
0x29, 0x7d, 0xba, 0x8d, 0x78, 0x11, 0xb1, 0x54,
]);
describe('ARD handshake', function () {
let sock;
let rfb;
let sentData;
before(() => {
FakeWebSocket.replace();
sinon.stub(window.crypto, "getRandomValues")
.onFirstCall().callsFake((arr) => {
arr.set(new Uint8Array([
0x40, 0x26, 0x17, 0x7c, 0xca, 0x81, 0xdb, 0xe5,
0x51, 0x46, 0xdd, 0x82, 0x5a, 0xcf, 0xaa, 0xfe,
0xe6, 0x78, 0x5b, 0x6b, 0x63, 0xe4, 0x14, 0x9c,
0xa8, 0x41, 0xec, 0x64, 0x78, 0x04, 0x47, 0xc2,
0xc3, 0x76, 0x2d, 0x86, 0xe4, 0x7a, 0xd6, 0x45,
0x9d, 0x45, 0x3e, 0x81, 0x8f, 0x04, 0x77, 0x92,
0x82, 0xd6, 0x1d, 0xb4, 0x21, 0x24, 0xf3, 0x13,
0x84, 0x5b, 0xc5, 0x00, 0xa0, 0x9f, 0xd0, 0x8d,
0xd7, 0x20, 0xe0, 0xe3, 0x6c, 0xec, 0xab, 0x91,
0x88, 0x09, 0x5b, 0xd5, 0xa7, 0x6e, 0xbf, 0xdb,
0x93, 0xd9, 0x89, 0x0c, 0xa1, 0xe1, 0x48, 0xc8,
0xf5, 0x50, 0x76, 0xa1, 0x47, 0x02, 0x0e, 0x97,
0x92, 0x84, 0x25, 0x6a, 0x61, 0x03, 0x8e, 0x08,
0x90, 0xe4, 0x7c, 0x8e, 0x17, 0x59, 0xc7, 0xcc,
0xf6, 0x86, 0x26, 0xe3, 0xfc, 0xce, 0x17, 0x80,
0x25, 0xb4, 0x97, 0x4a, 0x75, 0x52, 0xfa, 0xcc,
]));
}).onSecondCall().callsFake((arr) => {
arr.set(new Uint8Array([
0xbb, 0x65, 0xa8, 0x06, 0xe9, 0xbf, 0xa5, 0xd9,
0x6d, 0x45, 0x39, 0xf2, 0xf9, 0xb0, 0xb1, 0x76,
0x8a, 0x7b, 0xff, 0xd9, 0xf2, 0x70, 0x6c, 0xa3,
0xdf, 0xbf, 0x49, 0x89, 0x18, 0xe0, 0x5f, 0xc4,
0x47, 0x9e, 0x0c, 0xd0, 0xa1, 0x70, 0x5e, 0xe2,
0xf3, 0xe0, 0x31, 0xe3, 0x94, 0x04, 0xd6, 0x6e,
0x15, 0xe2, 0xba, 0xe3, 0xe4, 0xa5, 0xfd, 0x52,
0xb3, 0x1d, 0xf0, 0x91, 0x0b, 0xe9, 0x8b, 0x1b,
0x6c, 0xa6, 0x49, 0x82, 0x51, 0xc3, 0x69, 0x8c,
0x60, 0x77, 0xd2, 0x25, 0x1b, 0x1d, 0x38, 0x03,
0x06, 0x69, 0xdc, 0xa2, 0xc7, 0xa4, 0x9f, 0x21,
0xa3, 0xcd, 0x3e, 0x70, 0xb2, 0xca, 0x54, 0x74,
0x63, 0x50, 0xe1, 0x55, 0x5c, 0xb2, 0x5a, 0x16,
0x80, 0x48, 0x7b, 0xf5, 0x06, 0x57, 0x26, 0x70,
0x65, 0x83, 0xf3, 0xdd, 0xa6, 0xd0, 0x72, 0xc1,
0xd8, 0x8e, 0x96, 0x3b, 0xdb, 0x44, 0xce, 0x34,
]));
});
});
after(() => {
FakeWebSocket.restore();
window.crypto.getRandomValues.restore();
});
it('should fire the credentialsrequired event', function (done) {
sentData = new Uint8Array();
rfb = new RFB(document.createElement('div'), "ws://example.com");
sock = rfb._sock;
sock.send = (data) => {
let res = new Uint8Array(sentData.length + data.length);
res.set(sentData);
res.set(data, sentData.length);
sentData = res;
};
rfb._rfbInitState = "Security";
rfb._rfbVersion = 3.8;
sock._websocket._receiveData(new Uint8Array([1, 30]));
rfb.addEventListener("credentialsrequired", (e) => {
expect(e.detail.types).to.eql(["username", "password"]);
done();
});
sock._websocket._receiveData(receiveData);
});
it('should match sendData after sending credentials', function (done) {
rfb.addEventListener("securityresult", (event) => {
expect(sentData.slice(1)).to.eql(sendData);
done();
});
rfb.sendCredentials({ "username": "test", "password": "123456" });
});
});

View File

@ -1048,11 +1048,6 @@ describe('Remote Frame Buffer Protocol Client', function () {
expect(client._rfbVersion).to.equal(3.3);
});
it('should interpret version 003.889 as version 3.3', function () {
sendVer('003.889', client);
expect(client._rfbVersion).to.equal(3.3);
});
it('should interpret version 003.007 as version 3.7', function () {
sendVer('003.007', client);
expect(client._rfbVersion).to.equal(3.7);
@ -1063,6 +1058,11 @@ describe('Remote Frame Buffer Protocol Client', function () {
expect(client._rfbVersion).to.equal(3.8);
});
it('should interpret version 003.889 as version 3.8', function () {
sendVer('003.889', client);
expect(client._rfbVersion).to.equal(3.8);
});
it('should interpret version 004.000 as version 3.8', function () {
sendVer('004.000', client);
expect(client._rfbVersion).to.equal(3.8);