@novnc/novnc 1.2.0-test → 1.3.0-g1075cd8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +0 -6
- package/README.md +16 -6
- package/core/decoders/copyrect.js +5 -0
- package/core/decoders/hextile.js +57 -3
- package/core/decoders/jpeg.js +141 -0
- package/core/decoders/raw.js +12 -2
- package/core/decoders/tight.js +24 -8
- package/core/decoders/zrle.js +185 -0
- package/core/display.js +9 -151
- package/core/encodings.js +4 -0
- package/core/input/domkeytable.js +25 -21
- package/core/input/keyboard.js +12 -127
- package/core/input/util.js +18 -35
- package/core/input/vkeys.js +0 -1
- package/core/input/xtscancodes.js +5 -3
- package/core/ra2.js +567 -0
- package/core/rfb.js +332 -114
- package/core/util/browser.js +0 -17
- package/core/util/cursor.js +1 -11
- package/core/util/events.js +0 -4
- package/core/util/md5.js +79 -0
- package/core/websock.js +76 -17
- package/docs/API.md +46 -6
- package/docs/LIBRARY.md +3 -7
- package/lib/base64.js +5 -5
- package/lib/decoders/copyrect.js +8 -3
- package/lib/decoders/hextile.js +65 -9
- package/lib/decoders/jpeg.js +188 -0
- package/lib/decoders/raw.js +14 -5
- package/lib/decoders/rre.js +3 -3
- package/lib/decoders/tight.js +40 -22
- package/lib/decoders/tightpng.js +10 -10
- package/lib/decoders/zrle.js +234 -0
- package/lib/deflator.js +5 -5
- package/lib/des.js +3 -3
- package/lib/display.js +47 -214
- package/lib/encodings.js +8 -0
- package/lib/inflator.js +5 -5
- package/lib/input/domkeytable.js +197 -194
- package/lib/input/fixedkeys.js +2 -2
- package/lib/input/gesturehandler.js +3 -3
- package/lib/input/keyboard.js +40 -160
- package/lib/input/keysym.js +2 -2
- package/lib/input/keysymdef.js +2 -2
- package/lib/input/util.js +35 -80
- package/lib/input/vkeys.js +2 -4
- package/lib/input/xtscancodes.js +11 -5
- package/lib/ra2.js +1257 -0
- package/lib/rfb.js +656 -306
- package/lib/util/browser.js +9 -27
- package/lib/util/cursor.js +5 -17
- package/lib/util/events.js +3 -5
- package/lib/util/eventtarget.js +4 -4
- package/lib/util/int.js +1 -1
- package/lib/util/logging.js +2 -2
- package/lib/util/md5.js +103 -0
- package/lib/vendor/pako/lib/utils/common.js +2 -2
- package/lib/vendor/pako/lib/zlib/adler32.js +1 -1
- package/lib/vendor/pako/lib/zlib/constants.js +2 -2
- package/lib/vendor/pako/lib/zlib/crc32.js +1 -1
- package/lib/vendor/pako/lib/zlib/deflate.js +114 -113
- package/lib/vendor/pako/lib/zlib/gzheader.js +1 -1
- package/lib/vendor/pako/lib/zlib/inffast.js +5 -5
- package/lib/vendor/pako/lib/zlib/inflate.js +51 -49
- package/lib/vendor/pako/lib/zlib/inftrees.js +4 -4
- package/lib/vendor/pako/lib/zlib/messages.js +2 -2
- package/lib/vendor/pako/lib/zlib/trees.js +5 -5
- package/lib/vendor/pako/lib/zlib/zstream.js +1 -1
- package/lib/websock.js +107 -46
- package/package.json +2 -10
- package/core/util/polyfill.js +0 -61
- package/lib/util/polyfill.js +0 -72
- package/lib/vendor/promise.js +0 -255
package/core/rfb.js
CHANGED
|
@@ -25,7 +25,8 @@ import DES from "./des.js";
|
|
|
25
25
|
import KeyTable from "./input/keysym.js";
|
|
26
26
|
import XtScancode from "./input/xtscancodes.js";
|
|
27
27
|
import { encodings } from "./encodings.js";
|
|
28
|
-
import "./
|
|
28
|
+
import RSAAESAuthenticationState from "./ra2.js";
|
|
29
|
+
import { MD5 } from "./util/md5.js";
|
|
29
30
|
|
|
30
31
|
import RawDecoder from "./decoders/raw.js";
|
|
31
32
|
import CopyRectDecoder from "./decoders/copyrect.js";
|
|
@@ -33,6 +34,8 @@ import RREDecoder from "./decoders/rre.js";
|
|
|
33
34
|
import HextileDecoder from "./decoders/hextile.js";
|
|
34
35
|
import TightDecoder from "./decoders/tight.js";
|
|
35
36
|
import TightPNGDecoder from "./decoders/tightpng.js";
|
|
37
|
+
import ZRLEDecoder from "./decoders/zrle.js";
|
|
38
|
+
import JPEGDecoder from "./decoders/jpeg.js";
|
|
36
39
|
|
|
37
40
|
// How many seconds to wait for a disconnect to finish
|
|
38
41
|
const DISCONNECT_TIMEOUT = 3;
|
|
@@ -67,20 +70,25 @@ const extendedClipboardActionPeek = 1 << 26;
|
|
|
67
70
|
const extendedClipboardActionNotify = 1 << 27;
|
|
68
71
|
const extendedClipboardActionProvide = 1 << 28;
|
|
69
72
|
|
|
70
|
-
|
|
71
73
|
export default class RFB extends EventTargetMixin {
|
|
72
|
-
constructor(target,
|
|
74
|
+
constructor(target, urlOrChannel, options) {
|
|
73
75
|
if (!target) {
|
|
74
76
|
throw new Error("Must specify target");
|
|
75
77
|
}
|
|
76
|
-
if (!
|
|
77
|
-
throw new Error("Must specify URL");
|
|
78
|
+
if (!urlOrChannel) {
|
|
79
|
+
throw new Error("Must specify URL, WebSocket or RTCDataChannel");
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
super();
|
|
81
83
|
|
|
82
84
|
this._target = target;
|
|
83
|
-
|
|
85
|
+
|
|
86
|
+
if (typeof urlOrChannel === "string") {
|
|
87
|
+
this._url = urlOrChannel;
|
|
88
|
+
} else {
|
|
89
|
+
this._url = null;
|
|
90
|
+
this._rawChannel = urlOrChannel;
|
|
91
|
+
}
|
|
84
92
|
|
|
85
93
|
// Connection details
|
|
86
94
|
options = options || {};
|
|
@@ -94,6 +102,7 @@ export default class RFB extends EventTargetMixin {
|
|
|
94
102
|
this._rfbInitState = '';
|
|
95
103
|
this._rfbAuthScheme = -1;
|
|
96
104
|
this._rfbCleanDisconnect = true;
|
|
105
|
+
this._rfbRSAAESAuthenticationState = null;
|
|
97
106
|
|
|
98
107
|
// Server capabilities
|
|
99
108
|
this._rfbVersion = 0;
|
|
@@ -130,6 +139,7 @@ export default class RFB extends EventTargetMixin {
|
|
|
130
139
|
this._flushing = false; // Display flushing state
|
|
131
140
|
this._keyboard = null; // Keyboard input handler object
|
|
132
141
|
this._gestures = null; // Gesture input handler object
|
|
142
|
+
this._resizeObserver = null; // Resize observer object
|
|
133
143
|
|
|
134
144
|
// Timers
|
|
135
145
|
this._disconnTimer = null; // disconnection timer
|
|
@@ -167,10 +177,12 @@ export default class RFB extends EventTargetMixin {
|
|
|
167
177
|
// Bound event handlers
|
|
168
178
|
this._eventHandlers = {
|
|
169
179
|
focusCanvas: this._focusCanvas.bind(this),
|
|
170
|
-
|
|
180
|
+
handleResize: this._handleResize.bind(this),
|
|
171
181
|
handleMouse: this._handleMouse.bind(this),
|
|
172
182
|
handleWheel: this._handleWheel.bind(this),
|
|
173
183
|
handleGesture: this._handleGesture.bind(this),
|
|
184
|
+
handleRSAAESCredentialsRequired: this._handleRSAAESCredentialsRequired.bind(this),
|
|
185
|
+
handleRSAAESServerVerification: this._handleRSAAESServerVerification.bind(this),
|
|
174
186
|
};
|
|
175
187
|
|
|
176
188
|
// main setup
|
|
@@ -187,8 +199,6 @@ export default class RFB extends EventTargetMixin {
|
|
|
187
199
|
this._canvas.style.margin = 'auto';
|
|
188
200
|
// Some browsers add an outline on focus
|
|
189
201
|
this._canvas.style.outline = 'none';
|
|
190
|
-
// IE miscalculates width without this :(
|
|
191
|
-
this._canvas.style.flexShrink = '0';
|
|
192
202
|
this._canvas.width = 0;
|
|
193
203
|
this._canvas.height = 0;
|
|
194
204
|
this._canvas.tabIndex = -1;
|
|
@@ -215,6 +225,8 @@ export default class RFB extends EventTargetMixin {
|
|
|
215
225
|
this._decoders[encodings.encodingHextile] = new HextileDecoder();
|
|
216
226
|
this._decoders[encodings.encodingTight] = new TightDecoder();
|
|
217
227
|
this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
|
|
228
|
+
this._decoders[encodings.encodingZRLE] = new ZRLEDecoder();
|
|
229
|
+
this._decoders[encodings.encodingJPEG] = new JPEGDecoder();
|
|
218
230
|
|
|
219
231
|
// NB: nothing that needs explicit teardown should be done
|
|
220
232
|
// before this point, since this can throw an exception
|
|
@@ -232,58 +244,17 @@ export default class RFB extends EventTargetMixin {
|
|
|
232
244
|
this._gestures = new GestureHandler();
|
|
233
245
|
|
|
234
246
|
this._sock = new Websock();
|
|
235
|
-
this._sock.on('
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
this._sock.on('
|
|
239
|
-
if ((this._rfbConnectionState === 'connecting') &&
|
|
240
|
-
(this._rfbInitState === '')) {
|
|
241
|
-
this._rfbInitState = 'ProtocolVersion';
|
|
242
|
-
Log.Debug("Starting VNC handshake");
|
|
243
|
-
} else {
|
|
244
|
-
this._fail("Unexpected server connection while " +
|
|
245
|
-
this._rfbConnectionState);
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
this._sock.on('close', (e) => {
|
|
249
|
-
Log.Debug("WebSocket on-close event");
|
|
250
|
-
let msg = "";
|
|
251
|
-
if (e.code) {
|
|
252
|
-
msg = "(code: " + e.code;
|
|
253
|
-
if (e.reason) {
|
|
254
|
-
msg += ", reason: " + e.reason;
|
|
255
|
-
}
|
|
256
|
-
msg += ")";
|
|
257
|
-
}
|
|
258
|
-
switch (this._rfbConnectionState) {
|
|
259
|
-
case 'connecting':
|
|
260
|
-
this._fail("Connection closed " + msg);
|
|
261
|
-
break;
|
|
262
|
-
case 'connected':
|
|
263
|
-
// Handle disconnects that were initiated server-side
|
|
264
|
-
this._updateConnectionState('disconnecting');
|
|
265
|
-
this._updateConnectionState('disconnected');
|
|
266
|
-
break;
|
|
267
|
-
case 'disconnecting':
|
|
268
|
-
// Normal disconnection path
|
|
269
|
-
this._updateConnectionState('disconnected');
|
|
270
|
-
break;
|
|
271
|
-
case 'disconnected':
|
|
272
|
-
this._fail("Unexpected server disconnect " +
|
|
273
|
-
"when already disconnected " + msg);
|
|
274
|
-
break;
|
|
275
|
-
default:
|
|
276
|
-
this._fail("Unexpected server disconnect before connecting " +
|
|
277
|
-
msg);
|
|
278
|
-
break;
|
|
279
|
-
}
|
|
280
|
-
this._sock.off('close');
|
|
281
|
-
});
|
|
282
|
-
this._sock.on('error', e => Log.Warn("WebSocket on-error event"));
|
|
247
|
+
this._sock.on('open', this._socketOpen.bind(this));
|
|
248
|
+
this._sock.on('close', this._socketClose.bind(this));
|
|
249
|
+
this._sock.on('message', this._handleMessage.bind(this));
|
|
250
|
+
this._sock.on('error', this._socketError.bind(this));
|
|
283
251
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
252
|
+
this._expectedClientWidth = null;
|
|
253
|
+
this._expectedClientHeight = null;
|
|
254
|
+
this._resizeObserver = new ResizeObserver(this._eventHandlers.handleResize);
|
|
255
|
+
|
|
256
|
+
// All prepared, kick off the connection
|
|
257
|
+
this._updateConnectionState('connecting');
|
|
287
258
|
|
|
288
259
|
Log.Debug("<< RFB.constructor");
|
|
289
260
|
|
|
@@ -412,6 +383,15 @@ export default class RFB extends EventTargetMixin {
|
|
|
412
383
|
this._sock.off('error');
|
|
413
384
|
this._sock.off('message');
|
|
414
385
|
this._sock.off('open');
|
|
386
|
+
if (this._rfbRSAAESAuthenticationState !== null) {
|
|
387
|
+
this._rfbRSAAESAuthenticationState.disconnect();
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
approveServer() {
|
|
392
|
+
if (this._rfbRSAAESAuthenticationState !== null) {
|
|
393
|
+
this._rfbRSAAESAuthenticationState.approveServer();
|
|
394
|
+
}
|
|
415
395
|
}
|
|
416
396
|
|
|
417
397
|
sendCredentials(creds) {
|
|
@@ -472,8 +452,8 @@ export default class RFB extends EventTargetMixin {
|
|
|
472
452
|
}
|
|
473
453
|
}
|
|
474
454
|
|
|
475
|
-
focus() {
|
|
476
|
-
this._canvas.focus();
|
|
455
|
+
focus(options) {
|
|
456
|
+
this._canvas.focus(options);
|
|
477
457
|
}
|
|
478
458
|
|
|
479
459
|
blur() {
|
|
@@ -504,16 +484,22 @@ export default class RFB extends EventTargetMixin {
|
|
|
504
484
|
_connect() {
|
|
505
485
|
Log.Debug(">> RFB.connect");
|
|
506
486
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
try {
|
|
510
|
-
// WebSocket.onopen transitions to the RFB init states
|
|
487
|
+
if (this._url) {
|
|
488
|
+
Log.Info(`connecting to ${this._url}`);
|
|
511
489
|
this._sock.open(this._url, this._wsProtocols);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
490
|
+
} else {
|
|
491
|
+
Log.Info(`attaching ${this._rawChannel} to Websock`);
|
|
492
|
+
this._sock.attach(this._rawChannel);
|
|
493
|
+
|
|
494
|
+
if (this._sock.readyState === 'closed') {
|
|
495
|
+
throw Error("Cannot use already closed WebSocket/RTCDataChannel");
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (this._sock.readyState === 'open') {
|
|
499
|
+
// FIXME: _socketOpen() can in theory call _fail(), which
|
|
500
|
+
// isn't allowed this early, but I'm not sure that can
|
|
501
|
+
// happen without a bug messing up our state variables
|
|
502
|
+
this._socketOpen();
|
|
517
503
|
}
|
|
518
504
|
}
|
|
519
505
|
|
|
@@ -525,9 +511,8 @@ export default class RFB extends EventTargetMixin {
|
|
|
525
511
|
this._cursor.attach(this._canvas);
|
|
526
512
|
this._refreshCursor();
|
|
527
513
|
|
|
528
|
-
// Monitor size changes of the screen
|
|
529
|
-
|
|
530
|
-
window.addEventListener('resize', this._eventHandlers.windowResize);
|
|
514
|
+
// Monitor size changes of the screen element
|
|
515
|
+
this._resizeObserver.observe(this._screen);
|
|
531
516
|
|
|
532
517
|
// Always grab focus on some kind of click event
|
|
533
518
|
this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas);
|
|
@@ -568,7 +553,7 @@ export default class RFB extends EventTargetMixin {
|
|
|
568
553
|
this._canvas.removeEventListener('contextmenu', this._eventHandlers.handleMouse);
|
|
569
554
|
this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
|
|
570
555
|
this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
|
|
571
|
-
|
|
556
|
+
this._resizeObserver.disconnect();
|
|
572
557
|
this._keyboard.ungrab();
|
|
573
558
|
this._gestures.detach();
|
|
574
559
|
this._sock.close();
|
|
@@ -587,12 +572,64 @@ export default class RFB extends EventTargetMixin {
|
|
|
587
572
|
Log.Debug("<< RFB.disconnect");
|
|
588
573
|
}
|
|
589
574
|
|
|
575
|
+
_socketOpen() {
|
|
576
|
+
if ((this._rfbConnectionState === 'connecting') &&
|
|
577
|
+
(this._rfbInitState === '')) {
|
|
578
|
+
this._rfbInitState = 'ProtocolVersion';
|
|
579
|
+
Log.Debug("Starting VNC handshake");
|
|
580
|
+
} else {
|
|
581
|
+
this._fail("Unexpected server connection while " +
|
|
582
|
+
this._rfbConnectionState);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
_socketClose(e) {
|
|
587
|
+
Log.Debug("WebSocket on-close event");
|
|
588
|
+
let msg = "";
|
|
589
|
+
if (e.code) {
|
|
590
|
+
msg = "(code: " + e.code;
|
|
591
|
+
if (e.reason) {
|
|
592
|
+
msg += ", reason: " + e.reason;
|
|
593
|
+
}
|
|
594
|
+
msg += ")";
|
|
595
|
+
}
|
|
596
|
+
switch (this._rfbConnectionState) {
|
|
597
|
+
case 'connecting':
|
|
598
|
+
this._fail("Connection closed " + msg);
|
|
599
|
+
break;
|
|
600
|
+
case 'connected':
|
|
601
|
+
// Handle disconnects that were initiated server-side
|
|
602
|
+
this._updateConnectionState('disconnecting');
|
|
603
|
+
this._updateConnectionState('disconnected');
|
|
604
|
+
break;
|
|
605
|
+
case 'disconnecting':
|
|
606
|
+
// Normal disconnection path
|
|
607
|
+
this._updateConnectionState('disconnected');
|
|
608
|
+
break;
|
|
609
|
+
case 'disconnected':
|
|
610
|
+
this._fail("Unexpected server disconnect " +
|
|
611
|
+
"when already disconnected " + msg);
|
|
612
|
+
break;
|
|
613
|
+
default:
|
|
614
|
+
this._fail("Unexpected server disconnect before connecting " +
|
|
615
|
+
msg);
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
this._sock.off('close');
|
|
619
|
+
// Delete reference to raw channel to allow cleanup.
|
|
620
|
+
this._rawChannel = null;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
_socketError(e) {
|
|
624
|
+
Log.Warn("WebSocket on-error event");
|
|
625
|
+
}
|
|
626
|
+
|
|
590
627
|
_focusCanvas(event) {
|
|
591
628
|
if (!this.focusOnClick) {
|
|
592
629
|
return;
|
|
593
630
|
}
|
|
594
631
|
|
|
595
|
-
this.focus();
|
|
632
|
+
this.focus({ preventScroll: true });
|
|
596
633
|
}
|
|
597
634
|
|
|
598
635
|
_setDesktopName(name) {
|
|
@@ -602,7 +639,26 @@ export default class RFB extends EventTargetMixin {
|
|
|
602
639
|
{ detail: { name: this._fbName } }));
|
|
603
640
|
}
|
|
604
641
|
|
|
605
|
-
|
|
642
|
+
_saveExpectedClientSize() {
|
|
643
|
+
this._expectedClientWidth = this._screen.clientWidth;
|
|
644
|
+
this._expectedClientHeight = this._screen.clientHeight;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
_currentClientSize() {
|
|
648
|
+
return [this._screen.clientWidth, this._screen.clientHeight];
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
_clientHasExpectedSize() {
|
|
652
|
+
const [currentWidth, currentHeight] = this._currentClientSize();
|
|
653
|
+
return currentWidth == this._expectedClientWidth &&
|
|
654
|
+
currentHeight == this._expectedClientHeight;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
_handleResize() {
|
|
658
|
+
// Don't change anything if the client size is already as expected
|
|
659
|
+
if (this._clientHasExpectedSize()) {
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
606
662
|
// If the window resized then our screen element might have
|
|
607
663
|
// as well. Update the viewport dimensions.
|
|
608
664
|
window.requestAnimationFrame(() => {
|
|
@@ -643,6 +699,12 @@ export default class RFB extends EventTargetMixin {
|
|
|
643
699
|
this._display.viewportChangeSize(size.w, size.h);
|
|
644
700
|
this._fixScrollbars();
|
|
645
701
|
}
|
|
702
|
+
|
|
703
|
+
// When changing clipping we might show or hide scrollbars.
|
|
704
|
+
// This causes the expected client dimensions to change.
|
|
705
|
+
if (curClip !== newClip) {
|
|
706
|
+
this._saveExpectedClientSize();
|
|
707
|
+
}
|
|
646
708
|
}
|
|
647
709
|
|
|
648
710
|
_updateScale() {
|
|
@@ -667,6 +729,7 @@ export default class RFB extends EventTargetMixin {
|
|
|
667
729
|
}
|
|
668
730
|
|
|
669
731
|
const size = this._screenSize();
|
|
732
|
+
|
|
670
733
|
RFB.messages.setDesktopSize(this._sock,
|
|
671
734
|
Math.floor(size.w), Math.floor(size.h),
|
|
672
735
|
this._screenID, this._screenFlags);
|
|
@@ -682,12 +745,13 @@ export default class RFB extends EventTargetMixin {
|
|
|
682
745
|
}
|
|
683
746
|
|
|
684
747
|
_fixScrollbars() {
|
|
685
|
-
// This is a hack because
|
|
686
|
-
// for when scrollbars are needed.
|
|
687
|
-
//
|
|
748
|
+
// This is a hack because Safari on macOS screws up the calculation
|
|
749
|
+
// for when scrollbars are needed. We get scrollbars when making the
|
|
750
|
+
// browser smaller, despite remote resize being enabled. So to fix it
|
|
751
|
+
// we temporarily toggle them off and on.
|
|
688
752
|
const orig = this._screen.style.overflow;
|
|
689
753
|
this._screen.style.overflow = 'hidden';
|
|
690
|
-
// Force
|
|
754
|
+
// Force Safari to recalculate the layout by asking for
|
|
691
755
|
// an element's dimensions
|
|
692
756
|
this._screen.getBoundingClientRect();
|
|
693
757
|
this._screen.style.overflow = orig;
|
|
@@ -1225,13 +1289,13 @@ export default class RFB extends EventTargetMixin {
|
|
|
1225
1289
|
break;
|
|
1226
1290
|
case "003.003":
|
|
1227
1291
|
case "003.006": // UltraVNC
|
|
1228
|
-
case "003.889": // Apple Remote Desktop
|
|
1229
1292
|
this._rfbVersion = 3.3;
|
|
1230
1293
|
break;
|
|
1231
1294
|
case "003.007":
|
|
1232
1295
|
this._rfbVersion = 3.7;
|
|
1233
1296
|
break;
|
|
1234
1297
|
case "003.008":
|
|
1298
|
+
case "003.889": // Apple Remote Desktop
|
|
1235
1299
|
case "004.000": // Intel AMT KVM
|
|
1236
1300
|
case "004.001": // RealVNC 4.6
|
|
1237
1301
|
case "005.000": // RealVNC 5.3
|
|
@@ -1263,17 +1327,6 @@ export default class RFB extends EventTargetMixin {
|
|
|
1263
1327
|
}
|
|
1264
1328
|
|
|
1265
1329
|
_negotiateSecurity() {
|
|
1266
|
-
// Polyfill since IE and PhantomJS doesn't have
|
|
1267
|
-
// TypedArray.includes()
|
|
1268
|
-
function includes(item, array) {
|
|
1269
|
-
for (let i = 0; i < array.length; i++) {
|
|
1270
|
-
if (array[i] === item) {
|
|
1271
|
-
return true;
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
return false;
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
1330
|
if (this._rfbVersion >= 3.7) {
|
|
1278
1331
|
// Server sends supported list, client decides
|
|
1279
1332
|
const numTypes = this._sock.rQshift8();
|
|
@@ -1290,15 +1343,19 @@ export default class RFB extends EventTargetMixin {
|
|
|
1290
1343
|
Log.Debug("Server security types: " + types);
|
|
1291
1344
|
|
|
1292
1345
|
// Look for each auth in preferred order
|
|
1293
|
-
if (includes(1
|
|
1346
|
+
if (types.includes(1)) {
|
|
1294
1347
|
this._rfbAuthScheme = 1; // None
|
|
1295
|
-
} else if (includes(22
|
|
1348
|
+
} else if (types.includes(22)) {
|
|
1296
1349
|
this._rfbAuthScheme = 22; // XVP
|
|
1297
|
-
} else if (includes(16
|
|
1350
|
+
} else if (types.includes(16)) {
|
|
1298
1351
|
this._rfbAuthScheme = 16; // Tight
|
|
1299
|
-
} else if (includes(
|
|
1352
|
+
} else if (types.includes(6)) {
|
|
1353
|
+
this._rfbAuthScheme = 6; // RA2ne Auth
|
|
1354
|
+
} else if (types.includes(2)) {
|
|
1300
1355
|
this._rfbAuthScheme = 2; // VNC Auth
|
|
1301
|
-
} else if (includes(
|
|
1356
|
+
} else if (types.includes(30)) {
|
|
1357
|
+
this._rfbAuthScheme = 30; // ARD Auth
|
|
1358
|
+
} else if (types.includes(19)) {
|
|
1302
1359
|
this._rfbAuthScheme = 19; // VeNCrypt Auth
|
|
1303
1360
|
} else {
|
|
1304
1361
|
return this._fail("Unsupported security types (types: " + types + ")");
|
|
@@ -1441,8 +1498,8 @@ export default class RFB extends EventTargetMixin {
|
|
|
1441
1498
|
|
|
1442
1499
|
// negotiated Plain subtype, server waits for password
|
|
1443
1500
|
if (this._rfbVeNCryptState == 4) {
|
|
1444
|
-
if (
|
|
1445
|
-
|
|
1501
|
+
if (this._rfbCredentials.username === undefined ||
|
|
1502
|
+
this._rfbCredentials.password === undefined) {
|
|
1446
1503
|
this.dispatchEvent(new CustomEvent(
|
|
1447
1504
|
"credentialsrequired",
|
|
1448
1505
|
{ detail: { types: ["username", "password"] } }));
|
|
@@ -1452,9 +1509,18 @@ export default class RFB extends EventTargetMixin {
|
|
|
1452
1509
|
const user = encodeUTF8(this._rfbCredentials.username);
|
|
1453
1510
|
const pass = encodeUTF8(this._rfbCredentials.password);
|
|
1454
1511
|
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1512
|
+
this._sock.send([
|
|
1513
|
+
(user.length >> 24) & 0xFF,
|
|
1514
|
+
(user.length >> 16) & 0xFF,
|
|
1515
|
+
(user.length >> 8) & 0xFF,
|
|
1516
|
+
user.length & 0xFF
|
|
1517
|
+
]);
|
|
1518
|
+
this._sock.send([
|
|
1519
|
+
(pass.length >> 24) & 0xFF,
|
|
1520
|
+
(pass.length >> 16) & 0xFF,
|
|
1521
|
+
(pass.length >> 8) & 0xFF,
|
|
1522
|
+
pass.length & 0xFF
|
|
1523
|
+
]);
|
|
1458
1524
|
this._sock.sendString(user);
|
|
1459
1525
|
this._sock.sendString(pass);
|
|
1460
1526
|
|
|
@@ -1481,6 +1547,117 @@ export default class RFB extends EventTargetMixin {
|
|
|
1481
1547
|
return true;
|
|
1482
1548
|
}
|
|
1483
1549
|
|
|
1550
|
+
_negotiateARDAuth() {
|
|
1551
|
+
|
|
1552
|
+
if (this._rfbCredentials.username === undefined ||
|
|
1553
|
+
this._rfbCredentials.password === undefined) {
|
|
1554
|
+
this.dispatchEvent(new CustomEvent(
|
|
1555
|
+
"credentialsrequired",
|
|
1556
|
+
{ detail: { types: ["username", "password"] } }));
|
|
1557
|
+
return false;
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
if (this._rfbCredentials.ardPublicKey != undefined &&
|
|
1561
|
+
this._rfbCredentials.ardCredentials != undefined) {
|
|
1562
|
+
// if the async web crypto is done return the results
|
|
1563
|
+
this._sock.send(this._rfbCredentials.ardCredentials);
|
|
1564
|
+
this._sock.send(this._rfbCredentials.ardPublicKey);
|
|
1565
|
+
this._rfbCredentials.ardCredentials = null;
|
|
1566
|
+
this._rfbCredentials.ardPublicKey = null;
|
|
1567
|
+
this._rfbInitState = "SecurityResult";
|
|
1568
|
+
return true;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
if (this._sock.rQwait("read ard", 4)) { return false; }
|
|
1572
|
+
|
|
1573
|
+
let generator = this._sock.rQshiftBytes(2); // DH base generator value
|
|
1574
|
+
|
|
1575
|
+
let keyLength = this._sock.rQshift16();
|
|
1576
|
+
|
|
1577
|
+
if (this._sock.rQwait("read ard keylength", keyLength*2, 4)) { return false; }
|
|
1578
|
+
|
|
1579
|
+
// read the server values
|
|
1580
|
+
let prime = this._sock.rQshiftBytes(keyLength); // predetermined prime modulus
|
|
1581
|
+
let serverPublicKey = this._sock.rQshiftBytes(keyLength); // other party's public key
|
|
1582
|
+
|
|
1583
|
+
let clientPrivateKey = window.crypto.getRandomValues(new Uint8Array(keyLength));
|
|
1584
|
+
let padding = Array.from(window.crypto.getRandomValues(new Uint8Array(64)), byte => String.fromCharCode(65+byte%26)).join('');
|
|
1585
|
+
|
|
1586
|
+
this._negotiateARDAuthAsync(generator, keyLength, prime, serverPublicKey, clientPrivateKey, padding);
|
|
1587
|
+
|
|
1588
|
+
return false;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
_modPow(base, exponent, modulus) {
|
|
1592
|
+
|
|
1593
|
+
let baseHex = "0x"+Array.from(base, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
|
|
1594
|
+
let exponentHex = "0x"+Array.from(exponent, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
|
|
1595
|
+
let modulusHex = "0x"+Array.from(modulus, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
|
|
1596
|
+
|
|
1597
|
+
let b = BigInt(baseHex);
|
|
1598
|
+
let e = BigInt(exponentHex);
|
|
1599
|
+
let m = BigInt(modulusHex);
|
|
1600
|
+
let r = 1n;
|
|
1601
|
+
b = b % m;
|
|
1602
|
+
while (e > 0) {
|
|
1603
|
+
if (e % 2n === 1n) {
|
|
1604
|
+
r = (r * b) % m;
|
|
1605
|
+
}
|
|
1606
|
+
e = e / 2n;
|
|
1607
|
+
b = (b * b) % m;
|
|
1608
|
+
}
|
|
1609
|
+
let hexResult = r.toString(16);
|
|
1610
|
+
|
|
1611
|
+
while (hexResult.length/2<exponent.length || (hexResult.length%2 != 0)) {
|
|
1612
|
+
hexResult = "0"+hexResult;
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
let bytesResult = [];
|
|
1616
|
+
for (let c = 0; c < hexResult.length; c += 2) {
|
|
1617
|
+
bytesResult.push(parseInt(hexResult.substr(c, 2), 16));
|
|
1618
|
+
}
|
|
1619
|
+
return bytesResult;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
async _aesEcbEncrypt(string, key) {
|
|
1623
|
+
// perform AES-ECB blocks
|
|
1624
|
+
let keyString = Array.from(key, byte => String.fromCharCode(byte)).join('');
|
|
1625
|
+
let aesKey = await window.crypto.subtle.importKey("raw", MD5(keyString), {name: "AES-CBC"}, false, ["encrypt"]);
|
|
1626
|
+
let data = new Uint8Array(string.length);
|
|
1627
|
+
for (let i = 0; i < string.length; ++i) {
|
|
1628
|
+
data[i] = string.charCodeAt(i);
|
|
1629
|
+
}
|
|
1630
|
+
let encrypted = new Uint8Array(data.length);
|
|
1631
|
+
for (let i=0;i<data.length;i+=16) {
|
|
1632
|
+
let block = data.slice(i, i+16);
|
|
1633
|
+
let encryptedBlock = await window.crypto.subtle.encrypt({name: "AES-CBC", iv: block},
|
|
1634
|
+
aesKey, new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
|
1635
|
+
);
|
|
1636
|
+
encrypted.set((new Uint8Array(encryptedBlock)).slice(0, 16), i);
|
|
1637
|
+
}
|
|
1638
|
+
return encrypted;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
async _negotiateARDAuthAsync(generator, keyLength, prime, serverPublicKey, clientPrivateKey, padding) {
|
|
1642
|
+
// calculate the DH keys
|
|
1643
|
+
let clientPublicKey = this._modPow(generator, clientPrivateKey, prime);
|
|
1644
|
+
let sharedKey = this._modPow(serverPublicKey, clientPrivateKey, prime);
|
|
1645
|
+
|
|
1646
|
+
let username = encodeUTF8(this._rfbCredentials.username).substring(0, 63);
|
|
1647
|
+
let password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
|
|
1648
|
+
|
|
1649
|
+
let paddedUsername = username + '\0' + padding.substring(0, 63);
|
|
1650
|
+
let paddedPassword = password + '\0' + padding.substring(0, 63);
|
|
1651
|
+
let credentials = paddedUsername.substring(0, 64) + paddedPassword.substring(0, 64);
|
|
1652
|
+
|
|
1653
|
+
let encrypted = await this._aesEcbEncrypt(credentials, sharedKey);
|
|
1654
|
+
|
|
1655
|
+
this._rfbCredentials.ardCredentials = encrypted;
|
|
1656
|
+
this._rfbCredentials.ardPublicKey = clientPublicKey;
|
|
1657
|
+
|
|
1658
|
+
setTimeout(this._initMsg.bind(this), 0);
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1484
1661
|
_negotiateTightUnixAuth() {
|
|
1485
1662
|
if (this._rfbCredentials.username === undefined ||
|
|
1486
1663
|
this._rfbCredentials.password === undefined) {
|
|
@@ -1604,6 +1781,44 @@ export default class RFB extends EventTargetMixin {
|
|
|
1604
1781
|
return this._fail("No supported sub-auth types!");
|
|
1605
1782
|
}
|
|
1606
1783
|
|
|
1784
|
+
_handleRSAAESCredentialsRequired(event) {
|
|
1785
|
+
this.dispatchEvent(event);
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
_handleRSAAESServerVerification(event) {
|
|
1789
|
+
this.dispatchEvent(event);
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
_negotiateRA2neAuth() {
|
|
1793
|
+
if (this._rfbRSAAESAuthenticationState === null) {
|
|
1794
|
+
this._rfbRSAAESAuthenticationState = new RSAAESAuthenticationState(this._sock, () => this._rfbCredentials);
|
|
1795
|
+
this._rfbRSAAESAuthenticationState.addEventListener(
|
|
1796
|
+
"serververification", this._eventHandlers.handleRSAAESServerVerification);
|
|
1797
|
+
this._rfbRSAAESAuthenticationState.addEventListener(
|
|
1798
|
+
"credentialsrequired", this._eventHandlers.handleRSAAESCredentialsRequired);
|
|
1799
|
+
}
|
|
1800
|
+
this._rfbRSAAESAuthenticationState.checkInternalEvents();
|
|
1801
|
+
if (!this._rfbRSAAESAuthenticationState.hasStarted) {
|
|
1802
|
+
this._rfbRSAAESAuthenticationState.negotiateRA2neAuthAsync()
|
|
1803
|
+
.catch((e) => {
|
|
1804
|
+
if (e.message !== "disconnect normally") {
|
|
1805
|
+
this._fail(e.message);
|
|
1806
|
+
}
|
|
1807
|
+
}).then(() => {
|
|
1808
|
+
this.dispatchEvent(new CustomEvent('securityresult'));
|
|
1809
|
+
this._rfbInitState = "SecurityResult";
|
|
1810
|
+
this._initMsg();
|
|
1811
|
+
}).finally(() => {
|
|
1812
|
+
this._rfbRSAAESAuthenticationState.removeEventListener(
|
|
1813
|
+
"serververification", this._eventHandlers.handleRSAAESServerVerification);
|
|
1814
|
+
this._rfbRSAAESAuthenticationState.removeEventListener(
|
|
1815
|
+
"credentialsrequired", this._eventHandlers.handleRSAAESCredentialsRequired);
|
|
1816
|
+
this._rfbRSAAESAuthenticationState = null;
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
return false;
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1607
1822
|
_negotiateAuthentication() {
|
|
1608
1823
|
switch (this._rfbAuthScheme) {
|
|
1609
1824
|
case 1: // no auth
|
|
@@ -1617,6 +1832,9 @@ export default class RFB extends EventTargetMixin {
|
|
|
1617
1832
|
case 22: // XVP auth
|
|
1618
1833
|
return this._negotiateXvpAuth();
|
|
1619
1834
|
|
|
1835
|
+
case 30: // ARD auth
|
|
1836
|
+
return this._negotiateARDAuth();
|
|
1837
|
+
|
|
1620
1838
|
case 2: // VNC authentication
|
|
1621
1839
|
return this._negotiateStdVNCAuth();
|
|
1622
1840
|
|
|
@@ -1629,6 +1847,9 @@ export default class RFB extends EventTargetMixin {
|
|
|
1629
1847
|
case 129: // TightVNC UNIX Security Type
|
|
1630
1848
|
return this._negotiateTightUnixAuth();
|
|
1631
1849
|
|
|
1850
|
+
case 6: // RA2ne Security Type
|
|
1851
|
+
return this._negotiateRA2neAuth();
|
|
1852
|
+
|
|
1632
1853
|
default:
|
|
1633
1854
|
return this._fail("Unsupported auth scheme (scheme: " +
|
|
1634
1855
|
this._rfbAuthScheme + ")");
|
|
@@ -1757,6 +1978,8 @@ export default class RFB extends EventTargetMixin {
|
|
|
1757
1978
|
if (this._fbDepth == 24) {
|
|
1758
1979
|
encs.push(encodings.encodingTight);
|
|
1759
1980
|
encs.push(encodings.encodingTightPNG);
|
|
1981
|
+
encs.push(encodings.encodingZRLE);
|
|
1982
|
+
encs.push(encodings.encodingJPEG);
|
|
1760
1983
|
encs.push(encodings.encodingHextile);
|
|
1761
1984
|
encs.push(encodings.encodingRRE);
|
|
1762
1985
|
}
|
|
@@ -2183,15 +2406,7 @@ export default class RFB extends EventTargetMixin {
|
|
|
2183
2406
|
return this._handleCursor();
|
|
2184
2407
|
|
|
2185
2408
|
case encodings.pseudoEncodingQEMUExtendedKeyEvent:
|
|
2186
|
-
|
|
2187
|
-
try {
|
|
2188
|
-
const keyboardEvent = document.createEvent("keyboardEvent");
|
|
2189
|
-
if (keyboardEvent.code !== undefined) {
|
|
2190
|
-
this._qemuExtKeyEventSupported = true;
|
|
2191
|
-
}
|
|
2192
|
-
} catch (err) {
|
|
2193
|
-
// Do nothing
|
|
2194
|
-
}
|
|
2409
|
+
this._qemuExtKeyEventSupported = true;
|
|
2195
2410
|
return true;
|
|
2196
2411
|
|
|
2197
2412
|
case encodings.pseudoEncodingDesktopName:
|
|
@@ -2493,6 +2708,9 @@ export default class RFB extends EventTargetMixin {
|
|
|
2493
2708
|
this._updateScale();
|
|
2494
2709
|
|
|
2495
2710
|
this._updateContinuousUpdates();
|
|
2711
|
+
|
|
2712
|
+
// Keep this size until browser client size changes
|
|
2713
|
+
this._saveExpectedClientSize();
|
|
2496
2714
|
}
|
|
2497
2715
|
|
|
2498
2716
|
_xvpOp(ver, op) {
|
|
@@ -2882,9 +3100,9 @@ RFB.messages = {
|
|
|
2882
3100
|
buff[offset + 12] = 0; // blue-max
|
|
2883
3101
|
buff[offset + 13] = (1 << bits) - 1; // blue-max
|
|
2884
3102
|
|
|
2885
|
-
buff[offset + 14] = bits *
|
|
3103
|
+
buff[offset + 14] = bits * 0; // red-shift
|
|
2886
3104
|
buff[offset + 15] = bits * 1; // green-shift
|
|
2887
|
-
buff[offset + 16] = bits *
|
|
3105
|
+
buff[offset + 16] = bits * 2; // blue-shift
|
|
2888
3106
|
|
|
2889
3107
|
buff[offset + 17] = 0; // padding
|
|
2890
3108
|
buff[offset + 18] = 0; // padding
|