@novnc/novnc 1.2.0 → 1.3.0-g0ef7582

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.
Files changed (75) hide show
  1. package/LICENSE.txt +0 -6
  2. package/README.md +16 -6
  3. package/core/decoders/copyrect.js +5 -0
  4. package/core/decoders/hextile.js +57 -3
  5. package/core/decoders/jpeg.js +141 -0
  6. package/core/decoders/raw.js +12 -2
  7. package/core/decoders/tight.js +24 -8
  8. package/core/decoders/zrle.js +185 -0
  9. package/core/display.js +21 -151
  10. package/core/encodings.js +4 -0
  11. package/core/input/domkeytable.js +25 -21
  12. package/core/input/keyboard.js +22 -127
  13. package/core/input/util.js +18 -35
  14. package/core/input/vkeys.js +0 -1
  15. package/core/input/xtscancodes.js +5 -3
  16. package/core/ra2.js +567 -0
  17. package/core/rfb.js +487 -171
  18. package/core/util/browser.js +0 -17
  19. package/core/util/cursor.js +1 -11
  20. package/core/util/events.js +0 -4
  21. package/core/util/md5.js +79 -0
  22. package/core/websock.js +76 -17
  23. package/docs/API.md +107 -6
  24. package/docs/LIBRARY.md +3 -7
  25. package/lib/base64.js +24 -38
  26. package/lib/decoders/copyrect.js +6 -11
  27. package/lib/decoders/hextile.js +68 -44
  28. package/lib/decoders/jpeg.js +146 -0
  29. package/lib/decoders/raw.js +14 -21
  30. package/lib/decoders/rre.js +3 -17
  31. package/lib/decoders/tight.js +43 -93
  32. package/lib/decoders/tightpng.js +11 -33
  33. package/lib/decoders/zrle.js +185 -0
  34. package/lib/deflator.js +9 -26
  35. package/lib/des.js +22 -38
  36. package/lib/display.js +100 -315
  37. package/lib/encodings.js +7 -8
  38. package/lib/inflator.js +6 -22
  39. package/lib/input/domkeytable.js +240 -208
  40. package/lib/input/fixedkeys.js +10 -5
  41. package/lib/input/gesturehandler.js +84 -154
  42. package/lib/input/keyboard.js +87 -238
  43. package/lib/input/keysym.js +16 -272
  44. package/lib/input/keysymdef.js +7 -9
  45. package/lib/input/util.js +69 -156
  46. package/lib/input/vkeys.js +2 -7
  47. package/lib/input/xtscancodes.js +10 -171
  48. package/lib/ra2.js +1033 -0
  49. package/lib/rfb.js +947 -1149
  50. package/lib/util/browser.js +25 -52
  51. package/lib/util/cursor.js +25 -81
  52. package/lib/util/element.js +3 -5
  53. package/lib/util/events.js +26 -35
  54. package/lib/util/eventtarget.js +4 -16
  55. package/lib/util/int.js +2 -3
  56. package/lib/util/logging.js +3 -21
  57. package/lib/util/md5.js +83 -0
  58. package/lib/util/strings.js +3 -5
  59. package/lib/vendor/pako/lib/utils/common.js +10 -19
  60. package/lib/vendor/pako/lib/zlib/adler32.js +4 -8
  61. package/lib/vendor/pako/lib/zlib/constants.js +4 -7
  62. package/lib/vendor/pako/lib/zlib/crc32.js +6 -13
  63. package/lib/vendor/pako/lib/zlib/deflate.js +304 -708
  64. package/lib/vendor/pako/lib/zlib/gzheader.js +2 -14
  65. package/lib/vendor/pako/lib/zlib/inffast.js +61 -177
  66. package/lib/vendor/pako/lib/zlib/inflate.js +421 -909
  67. package/lib/vendor/pako/lib/zlib/inftrees.js +66 -172
  68. package/lib/vendor/pako/lib/zlib/messages.js +3 -13
  69. package/lib/vendor/pako/lib/zlib/trees.js +250 -592
  70. package/lib/vendor/pako/lib/zlib/zstream.js +3 -19
  71. package/lib/websock.js +119 -111
  72. package/package.json +2 -10
  73. package/core/util/polyfill.js +0 -61
  74. package/lib/util/polyfill.js +0 -72
  75. 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 "./util/polyfill.js";
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;
@@ -51,6 +54,21 @@ const GESTURE_SCRLSENS = 50;
51
54
  const DOUBLE_TAP_TIMEOUT = 1000;
52
55
  const DOUBLE_TAP_THRESHOLD = 50;
53
56
 
57
+ // Security types
58
+ const securityTypeNone = 1;
59
+ const securityTypeVNCAuth = 2;
60
+ const securityTypeRA2ne = 6;
61
+ const securityTypeTight = 16;
62
+ const securityTypeVeNCrypt = 19;
63
+ const securityTypeXVP = 22;
64
+ const securityTypeARD = 30;
65
+
66
+ // Special Tight security types
67
+ const securityTypeUnixLogon = 129;
68
+
69
+ // VeNCrypt security types
70
+ const securityTypePlain = 256;
71
+
54
72
  // Extended clipboard pseudo-encoding formats
55
73
  const extendedClipboardFormatText = 1;
56
74
  /*eslint-disable no-unused-vars */
@@ -67,20 +85,31 @@ const extendedClipboardActionPeek = 1 << 26;
67
85
  const extendedClipboardActionNotify = 1 << 27;
68
86
  const extendedClipboardActionProvide = 1 << 28;
69
87
 
70
-
71
88
  export default class RFB extends EventTargetMixin {
72
- constructor(target, url, options) {
89
+ constructor(target, urlOrChannel, options) {
73
90
  if (!target) {
74
91
  throw new Error("Must specify target");
75
92
  }
76
- if (!url) {
77
- throw new Error("Must specify URL");
93
+ if (!urlOrChannel) {
94
+ throw new Error("Must specify URL, WebSocket or RTCDataChannel");
95
+ }
96
+
97
+ // We rely on modern APIs which might not be available in an
98
+ // insecure context
99
+ if (!window.isSecureContext) {
100
+ Log.Error("noVNC requires a secure context (TLS). Expect crashes!");
78
101
  }
79
102
 
80
103
  super();
81
104
 
82
105
  this._target = target;
83
- this._url = url;
106
+
107
+ if (typeof urlOrChannel === "string") {
108
+ this._url = urlOrChannel;
109
+ } else {
110
+ this._url = null;
111
+ this._rawChannel = urlOrChannel;
112
+ }
84
113
 
85
114
  // Connection details
86
115
  options = options || {};
@@ -94,6 +123,7 @@ export default class RFB extends EventTargetMixin {
94
123
  this._rfbInitState = '';
95
124
  this._rfbAuthScheme = -1;
96
125
  this._rfbCleanDisconnect = true;
126
+ this._rfbRSAAESAuthenticationState = null;
97
127
 
98
128
  // Server capabilities
99
129
  this._rfbVersion = 0;
@@ -130,6 +160,7 @@ export default class RFB extends EventTargetMixin {
130
160
  this._flushing = false; // Display flushing state
131
161
  this._keyboard = null; // Keyboard input handler object
132
162
  this._gestures = null; // Gesture input handler object
163
+ this._resizeObserver = null; // Resize observer object
133
164
 
134
165
  // Timers
135
166
  this._disconnTimer = null; // disconnection timer
@@ -167,10 +198,12 @@ export default class RFB extends EventTargetMixin {
167
198
  // Bound event handlers
168
199
  this._eventHandlers = {
169
200
  focusCanvas: this._focusCanvas.bind(this),
170
- windowResize: this._windowResize.bind(this),
201
+ handleResize: this._handleResize.bind(this),
171
202
  handleMouse: this._handleMouse.bind(this),
172
203
  handleWheel: this._handleWheel.bind(this),
173
204
  handleGesture: this._handleGesture.bind(this),
205
+ handleRSAAESCredentialsRequired: this._handleRSAAESCredentialsRequired.bind(this),
206
+ handleRSAAESServerVerification: this._handleRSAAESServerVerification.bind(this),
174
207
  };
175
208
 
176
209
  // main setup
@@ -187,8 +220,6 @@ export default class RFB extends EventTargetMixin {
187
220
  this._canvas.style.margin = 'auto';
188
221
  // Some browsers add an outline on focus
189
222
  this._canvas.style.outline = 'none';
190
- // IE miscalculates width without this :(
191
- this._canvas.style.flexShrink = '0';
192
223
  this._canvas.width = 0;
193
224
  this._canvas.height = 0;
194
225
  this._canvas.tabIndex = -1;
@@ -215,6 +246,8 @@ export default class RFB extends EventTargetMixin {
215
246
  this._decoders[encodings.encodingHextile] = new HextileDecoder();
216
247
  this._decoders[encodings.encodingTight] = new TightDecoder();
217
248
  this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
249
+ this._decoders[encodings.encodingZRLE] = new ZRLEDecoder();
250
+ this._decoders[encodings.encodingJPEG] = new JPEGDecoder();
218
251
 
219
252
  // NB: nothing that needs explicit teardown should be done
220
253
  // before this point, since this can throw an exception
@@ -232,58 +265,17 @@ export default class RFB extends EventTargetMixin {
232
265
  this._gestures = new GestureHandler();
233
266
 
234
267
  this._sock = new Websock();
235
- this._sock.on('message', () => {
236
- this._handleMessage();
237
- });
238
- this._sock.on('open', () => {
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"));
268
+ this._sock.on('open', this._socketOpen.bind(this));
269
+ this._sock.on('close', this._socketClose.bind(this));
270
+ this._sock.on('message', this._handleMessage.bind(this));
271
+ this._sock.on('error', this._socketError.bind(this));
272
+
273
+ this._expectedClientWidth = null;
274
+ this._expectedClientHeight = null;
275
+ this._resizeObserver = new ResizeObserver(this._eventHandlers.handleResize);
283
276
 
284
- // Slight delay of the actual connection so that the caller has
285
- // time to set up callbacks
286
- setTimeout(this._updateConnectionState.bind(this, 'connecting'));
277
+ // All prepared, kick off the connection
278
+ this._updateConnectionState('connecting');
287
279
 
288
280
  Log.Debug("<< RFB.constructor");
289
281
 
@@ -412,11 +404,20 @@ export default class RFB extends EventTargetMixin {
412
404
  this._sock.off('error');
413
405
  this._sock.off('message');
414
406
  this._sock.off('open');
407
+ if (this._rfbRSAAESAuthenticationState !== null) {
408
+ this._rfbRSAAESAuthenticationState.disconnect();
409
+ }
410
+ }
411
+
412
+ approveServer() {
413
+ if (this._rfbRSAAESAuthenticationState !== null) {
414
+ this._rfbRSAAESAuthenticationState.approveServer();
415
+ }
415
416
  }
416
417
 
417
418
  sendCredentials(creds) {
418
419
  this._rfbCredentials = creds;
419
- setTimeout(this._initMsg.bind(this), 0);
420
+ this._resumeAuthentication();
420
421
  }
421
422
 
422
423
  sendCtrlAltDel() {
@@ -472,8 +473,8 @@ export default class RFB extends EventTargetMixin {
472
473
  }
473
474
  }
474
475
 
475
- focus() {
476
- this._canvas.focus();
476
+ focus(options) {
477
+ this._canvas.focus(options);
477
478
  }
478
479
 
479
480
  blur() {
@@ -489,31 +490,66 @@ export default class RFB extends EventTargetMixin {
489
490
  this._clipboardText = text;
490
491
  RFB.messages.extendedClipboardNotify(this._sock, [extendedClipboardFormatText]);
491
492
  } else {
492
- let data = new Uint8Array(text.length);
493
- for (let i = 0; i < text.length; i++) {
494
- // FIXME: text can have values outside of Latin1/Uint8
495
- data[i] = text.charCodeAt(i);
493
+ let length, i;
494
+ let data;
495
+
496
+ length = 0;
497
+ // eslint-disable-next-line no-unused-vars
498
+ for (let codePoint of text) {
499
+ length++;
500
+ }
501
+
502
+ data = new Uint8Array(length);
503
+
504
+ i = 0;
505
+ for (let codePoint of text) {
506
+ let code = codePoint.codePointAt(0);
507
+
508
+ /* Only ISO 8859-1 is supported */
509
+ if (code > 0xff) {
510
+ code = 0x3f; // '?'
511
+ }
512
+
513
+ data[i++] = code;
496
514
  }
497
515
 
498
516
  RFB.messages.clientCutText(this._sock, data);
499
517
  }
500
518
  }
501
519
 
520
+ getImageData() {
521
+ return this._display.getImageData();
522
+ }
523
+
524
+ toDataURL(type, encoderOptions) {
525
+ return this._display.toDataURL(type, encoderOptions);
526
+ }
527
+
528
+ toBlob(callback, type, quality) {
529
+ return this._display.toBlob(callback, type, quality);
530
+ }
531
+
502
532
  // ===== PRIVATE METHODS =====
503
533
 
504
534
  _connect() {
505
535
  Log.Debug(">> RFB.connect");
506
536
 
507
- Log.Info("connecting to " + this._url);
508
-
509
- try {
510
- // WebSocket.onopen transitions to the RFB init states
537
+ if (this._url) {
538
+ Log.Info(`connecting to ${this._url}`);
511
539
  this._sock.open(this._url, this._wsProtocols);
512
- } catch (e) {
513
- if (e.name === 'SyntaxError') {
514
- this._fail("Invalid host or port (" + e + ")");
515
- } else {
516
- this._fail("Error when opening socket (" + e + ")");
540
+ } else {
541
+ Log.Info(`attaching ${this._rawChannel} to Websock`);
542
+ this._sock.attach(this._rawChannel);
543
+
544
+ if (this._sock.readyState === 'closed') {
545
+ throw Error("Cannot use already closed WebSocket/RTCDataChannel");
546
+ }
547
+
548
+ if (this._sock.readyState === 'open') {
549
+ // FIXME: _socketOpen() can in theory call _fail(), which
550
+ // isn't allowed this early, but I'm not sure that can
551
+ // happen without a bug messing up our state variables
552
+ this._socketOpen();
517
553
  }
518
554
  }
519
555
 
@@ -525,9 +561,8 @@ export default class RFB extends EventTargetMixin {
525
561
  this._cursor.attach(this._canvas);
526
562
  this._refreshCursor();
527
563
 
528
- // Monitor size changes of the screen
529
- // FIXME: Use ResizeObserver, or hidden overflow
530
- window.addEventListener('resize', this._eventHandlers.windowResize);
564
+ // Monitor size changes of the screen element
565
+ this._resizeObserver.observe(this._screen);
531
566
 
532
567
  // Always grab focus on some kind of click event
533
568
  this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas);
@@ -568,7 +603,7 @@ export default class RFB extends EventTargetMixin {
568
603
  this._canvas.removeEventListener('contextmenu', this._eventHandlers.handleMouse);
569
604
  this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
570
605
  this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
571
- window.removeEventListener('resize', this._eventHandlers.windowResize);
606
+ this._resizeObserver.disconnect();
572
607
  this._keyboard.ungrab();
573
608
  this._gestures.detach();
574
609
  this._sock.close();
@@ -587,12 +622,64 @@ export default class RFB extends EventTargetMixin {
587
622
  Log.Debug("<< RFB.disconnect");
588
623
  }
589
624
 
625
+ _socketOpen() {
626
+ if ((this._rfbConnectionState === 'connecting') &&
627
+ (this._rfbInitState === '')) {
628
+ this._rfbInitState = 'ProtocolVersion';
629
+ Log.Debug("Starting VNC handshake");
630
+ } else {
631
+ this._fail("Unexpected server connection while " +
632
+ this._rfbConnectionState);
633
+ }
634
+ }
635
+
636
+ _socketClose(e) {
637
+ Log.Debug("WebSocket on-close event");
638
+ let msg = "";
639
+ if (e.code) {
640
+ msg = "(code: " + e.code;
641
+ if (e.reason) {
642
+ msg += ", reason: " + e.reason;
643
+ }
644
+ msg += ")";
645
+ }
646
+ switch (this._rfbConnectionState) {
647
+ case 'connecting':
648
+ this._fail("Connection closed " + msg);
649
+ break;
650
+ case 'connected':
651
+ // Handle disconnects that were initiated server-side
652
+ this._updateConnectionState('disconnecting');
653
+ this._updateConnectionState('disconnected');
654
+ break;
655
+ case 'disconnecting':
656
+ // Normal disconnection path
657
+ this._updateConnectionState('disconnected');
658
+ break;
659
+ case 'disconnected':
660
+ this._fail("Unexpected server disconnect " +
661
+ "when already disconnected " + msg);
662
+ break;
663
+ default:
664
+ this._fail("Unexpected server disconnect before connecting " +
665
+ msg);
666
+ break;
667
+ }
668
+ this._sock.off('close');
669
+ // Delete reference to raw channel to allow cleanup.
670
+ this._rawChannel = null;
671
+ }
672
+
673
+ _socketError(e) {
674
+ Log.Warn("WebSocket on-error event");
675
+ }
676
+
590
677
  _focusCanvas(event) {
591
678
  if (!this.focusOnClick) {
592
679
  return;
593
680
  }
594
681
 
595
- this.focus();
682
+ this.focus({ preventScroll: true });
596
683
  }
597
684
 
598
685
  _setDesktopName(name) {
@@ -602,7 +689,26 @@ export default class RFB extends EventTargetMixin {
602
689
  { detail: { name: this._fbName } }));
603
690
  }
604
691
 
605
- _windowResize(event) {
692
+ _saveExpectedClientSize() {
693
+ this._expectedClientWidth = this._screen.clientWidth;
694
+ this._expectedClientHeight = this._screen.clientHeight;
695
+ }
696
+
697
+ _currentClientSize() {
698
+ return [this._screen.clientWidth, this._screen.clientHeight];
699
+ }
700
+
701
+ _clientHasExpectedSize() {
702
+ const [currentWidth, currentHeight] = this._currentClientSize();
703
+ return currentWidth == this._expectedClientWidth &&
704
+ currentHeight == this._expectedClientHeight;
705
+ }
706
+
707
+ _handleResize() {
708
+ // Don't change anything if the client size is already as expected
709
+ if (this._clientHasExpectedSize()) {
710
+ return;
711
+ }
606
712
  // If the window resized then our screen element might have
607
713
  // as well. Update the viewport dimensions.
608
714
  window.requestAnimationFrame(() => {
@@ -643,6 +749,12 @@ export default class RFB extends EventTargetMixin {
643
749
  this._display.viewportChangeSize(size.w, size.h);
644
750
  this._fixScrollbars();
645
751
  }
752
+
753
+ // When changing clipping we might show or hide scrollbars.
754
+ // This causes the expected client dimensions to change.
755
+ if (curClip !== newClip) {
756
+ this._saveExpectedClientSize();
757
+ }
646
758
  }
647
759
 
648
760
  _updateScale() {
@@ -667,6 +779,7 @@ export default class RFB extends EventTargetMixin {
667
779
  }
668
780
 
669
781
  const size = this._screenSize();
782
+
670
783
  RFB.messages.setDesktopSize(this._sock,
671
784
  Math.floor(size.w), Math.floor(size.h),
672
785
  this._screenID, this._screenFlags);
@@ -682,12 +795,13 @@ export default class RFB extends EventTargetMixin {
682
795
  }
683
796
 
684
797
  _fixScrollbars() {
685
- // This is a hack because Chrome screws up the calculation
686
- // for when scrollbars are needed. So to fix it we temporarily
687
- // toggle them off and on.
798
+ // This is a hack because Safari on macOS screws up the calculation
799
+ // for when scrollbars are needed. We get scrollbars when making the
800
+ // browser smaller, despite remote resize being enabled. So to fix it
801
+ // we temporarily toggle them off and on.
688
802
  const orig = this._screen.style.overflow;
689
803
  this._screen.style.overflow = 'hidden';
690
- // Force Chrome to recalculate the layout by asking for
804
+ // Force Safari to recalculate the layout by asking for
691
805
  // an element's dimensions
692
806
  this._screen.getBoundingClientRect();
693
807
  this._screen.style.overflow = orig;
@@ -852,8 +966,15 @@ export default class RFB extends EventTargetMixin {
852
966
  }
853
967
  }
854
968
  break;
969
+ case 'connecting':
970
+ while (this._rfbConnectionState === 'connecting') {
971
+ if (!this._initMsg()) {
972
+ break;
973
+ }
974
+ }
975
+ break;
855
976
  default:
856
- this._initMsg();
977
+ Log.Error("Got data while in an invalid state");
857
978
  break;
858
979
  }
859
980
  }
@@ -1225,13 +1346,13 @@ export default class RFB extends EventTargetMixin {
1225
1346
  break;
1226
1347
  case "003.003":
1227
1348
  case "003.006": // UltraVNC
1228
- case "003.889": // Apple Remote Desktop
1229
1349
  this._rfbVersion = 3.3;
1230
1350
  break;
1231
1351
  case "003.007":
1232
1352
  this._rfbVersion = 3.7;
1233
1353
  break;
1234
1354
  case "003.008":
1355
+ case "003.889": // Apple Remote Desktop
1235
1356
  case "004.000": // Intel AMT KVM
1236
1357
  case "004.001": // RealVNC 4.6
1237
1358
  case "005.000": // RealVNC 5.3
@@ -1262,18 +1383,22 @@ export default class RFB extends EventTargetMixin {
1262
1383
  this._rfbInitState = 'Security';
1263
1384
  }
1264
1385
 
1265
- _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
- }
1386
+ _isSupportedSecurityType(type) {
1387
+ const clientTypes = [
1388
+ securityTypeNone,
1389
+ securityTypeVNCAuth,
1390
+ securityTypeRA2ne,
1391
+ securityTypeTight,
1392
+ securityTypeVeNCrypt,
1393
+ securityTypeXVP,
1394
+ securityTypeARD,
1395
+ securityTypePlain,
1396
+ ];
1397
+
1398
+ return clientTypes.includes(type);
1399
+ }
1276
1400
 
1401
+ _negotiateSecurity() {
1277
1402
  if (this._rfbVersion >= 3.7) {
1278
1403
  // Server sends supported list, client decides
1279
1404
  const numTypes = this._sock.rQshift8();
@@ -1283,24 +1408,23 @@ export default class RFB extends EventTargetMixin {
1283
1408
  this._rfbInitState = "SecurityReason";
1284
1409
  this._securityContext = "no security types";
1285
1410
  this._securityStatus = 1;
1286
- return this._initMsg();
1411
+ return true;
1287
1412
  }
1288
1413
 
1289
1414
  const types = this._sock.rQshiftBytes(numTypes);
1290
1415
  Log.Debug("Server security types: " + types);
1291
1416
 
1292
- // Look for each auth in preferred order
1293
- if (includes(1, types)) {
1294
- this._rfbAuthScheme = 1; // None
1295
- } else if (includes(22, types)) {
1296
- this._rfbAuthScheme = 22; // XVP
1297
- } else if (includes(16, types)) {
1298
- this._rfbAuthScheme = 16; // Tight
1299
- } else if (includes(2, types)) {
1300
- this._rfbAuthScheme = 2; // VNC Auth
1301
- } else if (includes(19, types)) {
1302
- this._rfbAuthScheme = 19; // VeNCrypt Auth
1303
- } else {
1417
+ // Look for a matching security type in the order that the
1418
+ // server prefers
1419
+ this._rfbAuthScheme = -1;
1420
+ for (let type of types) {
1421
+ if (this._isSupportedSecurityType(type)) {
1422
+ this._rfbAuthScheme = type;
1423
+ break;
1424
+ }
1425
+ }
1426
+
1427
+ if (this._rfbAuthScheme === -1) {
1304
1428
  return this._fail("Unsupported security types (types: " + types + ")");
1305
1429
  }
1306
1430
 
@@ -1314,14 +1438,14 @@ export default class RFB extends EventTargetMixin {
1314
1438
  this._rfbInitState = "SecurityReason";
1315
1439
  this._securityContext = "authentication scheme";
1316
1440
  this._securityStatus = 1;
1317
- return this._initMsg();
1441
+ return true;
1318
1442
  }
1319
1443
  }
1320
1444
 
1321
1445
  this._rfbInitState = 'Authentication';
1322
1446
  Log.Debug('Authenticating using scheme: ' + this._rfbAuthScheme);
1323
1447
 
1324
- return this._initMsg(); // jump to authentication
1448
+ return true;
1325
1449
  }
1326
1450
 
1327
1451
  _handleSecurityReason() {
@@ -1371,7 +1495,7 @@ export default class RFB extends EventTargetMixin {
1371
1495
  this._rfbCredentials.username +
1372
1496
  this._rfbCredentials.target;
1373
1497
  this._sock.sendString(xvpAuthStr);
1374
- this._rfbAuthScheme = 2;
1498
+ this._rfbAuthScheme = securityTypeVNCAuth;
1375
1499
  return this._negotiateAuthentication();
1376
1500
  }
1377
1501
 
@@ -1429,40 +1553,66 @@ export default class RFB extends EventTargetMixin {
1429
1553
  subtypes.push(this._sock.rQshift32());
1430
1554
  }
1431
1555
 
1432
- // 256 = Plain subtype
1433
- if (subtypes.indexOf(256) != -1) {
1434
- // 0x100 = 256
1435
- this._sock.send([0, 0, 1, 0]);
1436
- this._rfbVeNCryptState = 4;
1437
- } else {
1438
- return this._fail("VeNCrypt Plain subtype not offered by server");
1439
- }
1440
- }
1556
+ // Look for a matching security type in the order that the
1557
+ // server prefers
1558
+ this._rfbAuthScheme = -1;
1559
+ for (let type of subtypes) {
1560
+ // Avoid getting in to a loop
1561
+ if (type === securityTypeVeNCrypt) {
1562
+ continue;
1563
+ }
1441
1564
 
1442
- // negotiated Plain subtype, server waits for password
1443
- if (this._rfbVeNCryptState == 4) {
1444
- if (!this._rfbCredentials.username ||
1445
- !this._rfbCredentials.password) {
1446
- this.dispatchEvent(new CustomEvent(
1447
- "credentialsrequired",
1448
- { detail: { types: ["username", "password"] } }));
1449
- return false;
1565
+ if (this._isSupportedSecurityType(type)) {
1566
+ this._rfbAuthScheme = type;
1567
+ break;
1568
+ }
1450
1569
  }
1451
1570
 
1452
- const user = encodeUTF8(this._rfbCredentials.username);
1453
- const pass = encodeUTF8(this._rfbCredentials.password);
1571
+ if (this._rfbAuthScheme === -1) {
1572
+ return this._fail("Unsupported security types (types: " + subtypes + ")");
1573
+ }
1454
1574
 
1455
- // XXX we assume lengths are <= 255 (should not be an issue in the real world)
1456
- this._sock.send([0, 0, 0, user.length]);
1457
- this._sock.send([0, 0, 0, pass.length]);
1458
- this._sock.sendString(user);
1459
- this._sock.sendString(pass);
1575
+ this._sock.send([this._rfbAuthScheme >> 24,
1576
+ this._rfbAuthScheme >> 16,
1577
+ this._rfbAuthScheme >> 8,
1578
+ this._rfbAuthScheme]);
1460
1579
 
1461
- this._rfbInitState = "SecurityResult";
1580
+ this._rfbVeNCryptState == 4;
1462
1581
  return true;
1463
1582
  }
1464
1583
  }
1465
1584
 
1585
+ _negotiatePlainAuth() {
1586
+ if (this._rfbCredentials.username === undefined ||
1587
+ this._rfbCredentials.password === undefined) {
1588
+ this.dispatchEvent(new CustomEvent(
1589
+ "credentialsrequired",
1590
+ { detail: { types: ["username", "password"] } }));
1591
+ return false;
1592
+ }
1593
+
1594
+ const user = encodeUTF8(this._rfbCredentials.username);
1595
+ const pass = encodeUTF8(this._rfbCredentials.password);
1596
+
1597
+ this._sock.send([
1598
+ (user.length >> 24) & 0xFF,
1599
+ (user.length >> 16) & 0xFF,
1600
+ (user.length >> 8) & 0xFF,
1601
+ user.length & 0xFF
1602
+ ]);
1603
+ this._sock.send([
1604
+ (pass.length >> 24) & 0xFF,
1605
+ (pass.length >> 16) & 0xFF,
1606
+ (pass.length >> 8) & 0xFF,
1607
+ pass.length & 0xFF
1608
+ ]);
1609
+ this._sock.sendString(user);
1610
+ this._sock.sendString(pass);
1611
+
1612
+ this._rfbInitState = "SecurityResult";
1613
+ return true;
1614
+ }
1615
+
1466
1616
  _negotiateStdVNCAuth() {
1467
1617
  if (this._sock.rQwait("auth challenge", 16)) { return false; }
1468
1618
 
@@ -1481,6 +1631,117 @@ export default class RFB extends EventTargetMixin {
1481
1631
  return true;
1482
1632
  }
1483
1633
 
1634
+ _negotiateARDAuth() {
1635
+
1636
+ if (this._rfbCredentials.username === undefined ||
1637
+ this._rfbCredentials.password === undefined) {
1638
+ this.dispatchEvent(new CustomEvent(
1639
+ "credentialsrequired",
1640
+ { detail: { types: ["username", "password"] } }));
1641
+ return false;
1642
+ }
1643
+
1644
+ if (this._rfbCredentials.ardPublicKey != undefined &&
1645
+ this._rfbCredentials.ardCredentials != undefined) {
1646
+ // if the async web crypto is done return the results
1647
+ this._sock.send(this._rfbCredentials.ardCredentials);
1648
+ this._sock.send(this._rfbCredentials.ardPublicKey);
1649
+ this._rfbCredentials.ardCredentials = null;
1650
+ this._rfbCredentials.ardPublicKey = null;
1651
+ this._rfbInitState = "SecurityResult";
1652
+ return true;
1653
+ }
1654
+
1655
+ if (this._sock.rQwait("read ard", 4)) { return false; }
1656
+
1657
+ let generator = this._sock.rQshiftBytes(2); // DH base generator value
1658
+
1659
+ let keyLength = this._sock.rQshift16();
1660
+
1661
+ if (this._sock.rQwait("read ard keylength", keyLength*2, 4)) { return false; }
1662
+
1663
+ // read the server values
1664
+ let prime = this._sock.rQshiftBytes(keyLength); // predetermined prime modulus
1665
+ let serverPublicKey = this._sock.rQshiftBytes(keyLength); // other party's public key
1666
+
1667
+ let clientPrivateKey = window.crypto.getRandomValues(new Uint8Array(keyLength));
1668
+ let padding = Array.from(window.crypto.getRandomValues(new Uint8Array(64)), byte => String.fromCharCode(65+byte%26)).join('');
1669
+
1670
+ this._negotiateARDAuthAsync(generator, keyLength, prime, serverPublicKey, clientPrivateKey, padding);
1671
+
1672
+ return false;
1673
+ }
1674
+
1675
+ _modPow(base, exponent, modulus) {
1676
+
1677
+ let baseHex = "0x"+Array.from(base, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
1678
+ let exponentHex = "0x"+Array.from(exponent, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
1679
+ let modulusHex = "0x"+Array.from(modulus, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
1680
+
1681
+ let b = BigInt(baseHex);
1682
+ let e = BigInt(exponentHex);
1683
+ let m = BigInt(modulusHex);
1684
+ let r = 1n;
1685
+ b = b % m;
1686
+ while (e > 0) {
1687
+ if (e % 2n === 1n) {
1688
+ r = (r * b) % m;
1689
+ }
1690
+ e = e / 2n;
1691
+ b = (b * b) % m;
1692
+ }
1693
+ let hexResult = r.toString(16);
1694
+
1695
+ while (hexResult.length/2<exponent.length || (hexResult.length%2 != 0)) {
1696
+ hexResult = "0"+hexResult;
1697
+ }
1698
+
1699
+ let bytesResult = [];
1700
+ for (let c = 0; c < hexResult.length; c += 2) {
1701
+ bytesResult.push(parseInt(hexResult.substr(c, 2), 16));
1702
+ }
1703
+ return bytesResult;
1704
+ }
1705
+
1706
+ async _aesEcbEncrypt(string, key) {
1707
+ // perform AES-ECB blocks
1708
+ let keyString = Array.from(key, byte => String.fromCharCode(byte)).join('');
1709
+ let aesKey = await window.crypto.subtle.importKey("raw", MD5(keyString), {name: "AES-CBC"}, false, ["encrypt"]);
1710
+ let data = new Uint8Array(string.length);
1711
+ for (let i = 0; i < string.length; ++i) {
1712
+ data[i] = string.charCodeAt(i);
1713
+ }
1714
+ let encrypted = new Uint8Array(data.length);
1715
+ for (let i=0;i<data.length;i+=16) {
1716
+ let block = data.slice(i, i+16);
1717
+ let encryptedBlock = await window.crypto.subtle.encrypt({name: "AES-CBC", iv: block},
1718
+ aesKey, new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
1719
+ );
1720
+ encrypted.set((new Uint8Array(encryptedBlock)).slice(0, 16), i);
1721
+ }
1722
+ return encrypted;
1723
+ }
1724
+
1725
+ async _negotiateARDAuthAsync(generator, keyLength, prime, serverPublicKey, clientPrivateKey, padding) {
1726
+ // calculate the DH keys
1727
+ let clientPublicKey = this._modPow(generator, clientPrivateKey, prime);
1728
+ let sharedKey = this._modPow(serverPublicKey, clientPrivateKey, prime);
1729
+
1730
+ let username = encodeUTF8(this._rfbCredentials.username).substring(0, 63);
1731
+ let password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
1732
+
1733
+ let paddedUsername = username + '\0' + padding.substring(0, 63);
1734
+ let paddedPassword = password + '\0' + padding.substring(0, 63);
1735
+ let credentials = paddedUsername.substring(0, 64) + paddedPassword.substring(0, 64);
1736
+
1737
+ let encrypted = await this._aesEcbEncrypt(credentials, sharedKey);
1738
+
1739
+ this._rfbCredentials.ardCredentials = encrypted;
1740
+ this._rfbCredentials.ardPublicKey = clientPublicKey;
1741
+
1742
+ this._resumeAuthentication();
1743
+ }
1744
+
1484
1745
  _negotiateTightUnixAuth() {
1485
1746
  if (this._rfbCredentials.username === undefined ||
1486
1747
  this._rfbCredentials.password === undefined) {
@@ -1588,12 +1849,12 @@ export default class RFB extends EventTargetMixin {
1588
1849
  case 'STDVNOAUTH__': // no auth
1589
1850
  this._rfbInitState = 'SecurityResult';
1590
1851
  return true;
1591
- case 'STDVVNCAUTH_': // VNC auth
1592
- this._rfbAuthScheme = 2;
1593
- return this._initMsg();
1594
- case 'TGHTULGNAUTH': // UNIX auth
1595
- this._rfbAuthScheme = 129;
1596
- return this._initMsg();
1852
+ case 'STDVVNCAUTH_':
1853
+ this._rfbAuthScheme = securityTypeVNCAuth;
1854
+ return true;
1855
+ case 'TGHTULGNAUTH':
1856
+ this._rfbAuthScheme = securityTypeUnixLogon;
1857
+ return true;
1597
1858
  default:
1598
1859
  return this._fail("Unsupported tiny auth scheme " +
1599
1860
  "(scheme: " + authType + ")");
@@ -1604,31 +1865,74 @@ export default class RFB extends EventTargetMixin {
1604
1865
  return this._fail("No supported sub-auth types!");
1605
1866
  }
1606
1867
 
1868
+ _handleRSAAESCredentialsRequired(event) {
1869
+ this.dispatchEvent(event);
1870
+ }
1871
+
1872
+ _handleRSAAESServerVerification(event) {
1873
+ this.dispatchEvent(event);
1874
+ }
1875
+
1876
+ _negotiateRA2neAuth() {
1877
+ if (this._rfbRSAAESAuthenticationState === null) {
1878
+ this._rfbRSAAESAuthenticationState = new RSAAESAuthenticationState(this._sock, () => this._rfbCredentials);
1879
+ this._rfbRSAAESAuthenticationState.addEventListener(
1880
+ "serververification", this._eventHandlers.handleRSAAESServerVerification);
1881
+ this._rfbRSAAESAuthenticationState.addEventListener(
1882
+ "credentialsrequired", this._eventHandlers.handleRSAAESCredentialsRequired);
1883
+ }
1884
+ this._rfbRSAAESAuthenticationState.checkInternalEvents();
1885
+ if (!this._rfbRSAAESAuthenticationState.hasStarted) {
1886
+ this._rfbRSAAESAuthenticationState.negotiateRA2neAuthAsync()
1887
+ .catch((e) => {
1888
+ if (e.message !== "disconnect normally") {
1889
+ this._fail(e.message);
1890
+ }
1891
+ }).then(() => {
1892
+ this.dispatchEvent(new CustomEvent('securityresult'));
1893
+ this._rfbInitState = "SecurityResult";
1894
+ return true;
1895
+ }).finally(() => {
1896
+ this._rfbRSAAESAuthenticationState.removeEventListener(
1897
+ "serververification", this._eventHandlers.handleRSAAESServerVerification);
1898
+ this._rfbRSAAESAuthenticationState.removeEventListener(
1899
+ "credentialsrequired", this._eventHandlers.handleRSAAESCredentialsRequired);
1900
+ this._rfbRSAAESAuthenticationState = null;
1901
+ });
1902
+ }
1903
+ return false;
1904
+ }
1905
+
1607
1906
  _negotiateAuthentication() {
1608
1907
  switch (this._rfbAuthScheme) {
1609
- case 1: // no auth
1610
- if (this._rfbVersion >= 3.8) {
1611
- this._rfbInitState = 'SecurityResult';
1612
- return true;
1613
- }
1614
- this._rfbInitState = 'ClientInitialisation';
1615
- return this._initMsg();
1908
+ case securityTypeNone:
1909
+ this._rfbInitState = 'SecurityResult';
1910
+ return true;
1616
1911
 
1617
- case 22: // XVP auth
1912
+ case securityTypeXVP:
1618
1913
  return this._negotiateXvpAuth();
1619
1914
 
1620
- case 2: // VNC authentication
1915
+ case securityTypeARD:
1916
+ return this._negotiateARDAuth();
1917
+
1918
+ case securityTypeVNCAuth:
1621
1919
  return this._negotiateStdVNCAuth();
1622
1920
 
1623
- case 16: // TightVNC Security Type
1921
+ case securityTypeTight:
1624
1922
  return this._negotiateTightAuth();
1625
1923
 
1626
- case 19: // VeNCrypt Security Type
1924
+ case securityTypeVeNCrypt:
1627
1925
  return this._negotiateVeNCryptAuth();
1628
1926
 
1629
- case 129: // TightVNC UNIX Security Type
1927
+ case securityTypePlain:
1928
+ return this._negotiatePlainAuth();
1929
+
1930
+ case securityTypeUnixLogon:
1630
1931
  return this._negotiateTightUnixAuth();
1631
1932
 
1933
+ case securityTypeRA2ne:
1934
+ return this._negotiateRA2neAuth();
1935
+
1632
1936
  default:
1633
1937
  return this._fail("Unsupported auth scheme (scheme: " +
1634
1938
  this._rfbAuthScheme + ")");
@@ -1636,6 +1940,13 @@ export default class RFB extends EventTargetMixin {
1636
1940
  }
1637
1941
 
1638
1942
  _handleSecurityResult() {
1943
+ // There is no security choice, and hence no security result
1944
+ // until RFB 3.7
1945
+ if (this._rfbVersion < 3.7) {
1946
+ this._rfbInitState = 'ClientInitialisation';
1947
+ return true;
1948
+ }
1949
+
1639
1950
  if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
1640
1951
 
1641
1952
  const status = this._sock.rQshift32();
@@ -1643,13 +1954,13 @@ export default class RFB extends EventTargetMixin {
1643
1954
  if (status === 0) { // OK
1644
1955
  this._rfbInitState = 'ClientInitialisation';
1645
1956
  Log.Debug('Authentication OK');
1646
- return this._initMsg();
1957
+ return true;
1647
1958
  } else {
1648
1959
  if (this._rfbVersion >= 3.8) {
1649
1960
  this._rfbInitState = "SecurityReason";
1650
1961
  this._securityContext = "security result";
1651
1962
  this._securityStatus = status;
1652
- return this._initMsg();
1963
+ return true;
1653
1964
  } else {
1654
1965
  this.dispatchEvent(new CustomEvent(
1655
1966
  "securityfailure",
@@ -1757,6 +2068,8 @@ export default class RFB extends EventTargetMixin {
1757
2068
  if (this._fbDepth == 24) {
1758
2069
  encs.push(encodings.encodingTight);
1759
2070
  encs.push(encodings.encodingTightPNG);
2071
+ encs.push(encodings.encodingZRLE);
2072
+ encs.push(encodings.encodingJPEG);
1760
2073
  encs.push(encodings.encodingHextile);
1761
2074
  encs.push(encodings.encodingRRE);
1762
2075
  }
@@ -1823,6 +2136,14 @@ export default class RFB extends EventTargetMixin {
1823
2136
  }
1824
2137
  }
1825
2138
 
2139
+ // Resume authentication handshake after it was paused for some
2140
+ // reason, e.g. waiting for a password from the user
2141
+ _resumeAuthentication() {
2142
+ // We use setTimeout() so it's run in its own context, just like
2143
+ // it originally did via the WebSocket's event handler
2144
+ setTimeout(this._initMsg.bind(this), 0);
2145
+ }
2146
+
1826
2147
  _handleSetColourMapMsg() {
1827
2148
  Log.Debug("SetColorMapEntries");
1828
2149
 
@@ -2183,15 +2504,7 @@ export default class RFB extends EventTargetMixin {
2183
2504
  return this._handleCursor();
2184
2505
 
2185
2506
  case encodings.pseudoEncodingQEMUExtendedKeyEvent:
2186
- // Old Safari doesn't support creating keyboard events
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
- }
2507
+ this._qemuExtKeyEventSupported = true;
2195
2508
  return true;
2196
2509
 
2197
2510
  case encodings.pseudoEncodingDesktopName:
@@ -2493,6 +2806,9 @@ export default class RFB extends EventTargetMixin {
2493
2806
  this._updateScale();
2494
2807
 
2495
2808
  this._updateContinuousUpdates();
2809
+
2810
+ // Keep this size until browser client size changes
2811
+ this._saveExpectedClientSize();
2496
2812
  }
2497
2813
 
2498
2814
  _xvpOp(ver, op) {
@@ -2882,9 +3198,9 @@ RFB.messages = {
2882
3198
  buff[offset + 12] = 0; // blue-max
2883
3199
  buff[offset + 13] = (1 << bits) - 1; // blue-max
2884
3200
 
2885
- buff[offset + 14] = bits * 2; // red-shift
3201
+ buff[offset + 14] = bits * 0; // red-shift
2886
3202
  buff[offset + 15] = bits * 1; // green-shift
2887
- buff[offset + 16] = bits * 0; // blue-shift
3203
+ buff[offset + 16] = bits * 2; // blue-shift
2888
3204
 
2889
3205
  buff[offset + 17] = 0; // padding
2890
3206
  buff[offset + 18] = 0; // padding