From ff8e2364a52978a99e5a627776460b6c4af87c54 Mon Sep 17 00:00:00 2001 From: pdlan Date: Sat, 2 Apr 2022 21:26:34 -0400 Subject: [PATCH] Add support for ARD Security Type --- core/ard.js | 258 ++++++++++++++++++++++++++++++++++++++++++++++ core/rfb.js | 43 +++++++- tests/test.ard.js | 158 ++++++++++++++++++++++++++++ tests/test.rfb.js | 10 +- 4 files changed, 463 insertions(+), 6 deletions(-) create mode 100644 core/ard.js create mode 100644 tests/test.ard.js diff --git a/core/ard.js b/core/ard.js new file mode 100644 index 00000000..80c2fb47 --- /dev/null +++ b/core/ard.js @@ -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; + } +} \ No newline at end of file diff --git a/core/rfb.js b/core/rfb.js index 860a85b2..3abce452 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -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 + ")"); diff --git a/tests/test.ard.js b/tests/test.ard.js new file mode 100644 index 00000000..ea08a2eb --- /dev/null +++ b/tests/test.ard.js @@ -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" }); + }); +}); diff --git a/tests/test.rfb.js b/tests/test.rfb.js index c3aa74e2..1a8a24e9 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -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);