@novnc/novnc 1.3.0 → 1.4.0-beta

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 (64) hide show
  1. package/AUTHORS +2 -2
  2. package/LICENSE.txt +1 -1
  3. package/README.md +23 -7
  4. package/core/decoders/jpeg.js +141 -0
  5. package/core/decoders/raw.js +1 -1
  6. package/core/decoders/zrle.js +185 -0
  7. package/core/des.js +1 -1
  8. package/core/display.js +12 -0
  9. package/core/encodings.js +4 -0
  10. package/core/input/keyboard.js +10 -0
  11. package/core/ra2.js +567 -0
  12. package/core/rfb.js +469 -84
  13. package/core/util/browser.js +56 -7
  14. package/core/util/cursor.js +4 -0
  15. package/core/util/md5.js +79 -0
  16. package/docs/API.md +318 -157
  17. package/lib/base64.js +20 -34
  18. package/lib/decoders/copyrect.js +5 -12
  19. package/lib/decoders/hextile.js +17 -47
  20. package/lib/decoders/jpeg.js +149 -0
  21. package/lib/decoders/raw.js +10 -23
  22. package/lib/decoders/rre.js +5 -16
  23. package/lib/decoders/tight.js +13 -79
  24. package/lib/decoders/tightpng.js +8 -28
  25. package/lib/decoders/zrle.js +188 -0
  26. package/lib/deflator.js +9 -23
  27. package/lib/des.js +24 -37
  28. package/lib/display.js +62 -108
  29. package/lib/encodings.js +7 -8
  30. package/lib/inflator.js +6 -19
  31. package/lib/input/domkeytable.js +77 -48
  32. package/lib/input/fixedkeys.js +8 -3
  33. package/lib/input/gesturehandler.js +86 -153
  34. package/lib/input/keyboard.js +62 -91
  35. package/lib/input/keysym.js +14 -270
  36. package/lib/input/keysymdef.js +5 -7
  37. package/lib/input/util.js +43 -85
  38. package/lib/input/vkeys.js +0 -3
  39. package/lib/input/xtscancodes.js +1 -168
  40. package/lib/ra2.js +1005 -0
  41. package/lib/rfb.js +795 -923
  42. package/lib/util/browser.js +66 -29
  43. package/lib/util/cursor.js +29 -66
  44. package/lib/util/element.js +3 -5
  45. package/lib/util/events.js +23 -30
  46. package/lib/util/eventtarget.js +5 -14
  47. package/lib/util/int.js +1 -2
  48. package/lib/util/logging.js +1 -19
  49. package/lib/util/md5.js +77 -0
  50. package/lib/util/strings.js +3 -5
  51. package/lib/vendor/pako/lib/utils/common.js +8 -17
  52. package/lib/vendor/pako/lib/zlib/adler32.js +3 -7
  53. package/lib/vendor/pako/lib/zlib/constants.js +2 -5
  54. package/lib/vendor/pako/lib/zlib/crc32.js +5 -12
  55. package/lib/vendor/pako/lib/zlib/deflate.js +213 -618
  56. package/lib/vendor/pako/lib/zlib/gzheader.js +1 -13
  57. package/lib/vendor/pako/lib/zlib/inffast.js +60 -176
  58. package/lib/vendor/pako/lib/zlib/inflate.js +398 -888
  59. package/lib/vendor/pako/lib/zlib/inftrees.js +63 -169
  60. package/lib/vendor/pako/lib/zlib/messages.js +1 -11
  61. package/lib/vendor/pako/lib/zlib/trees.js +246 -588
  62. package/lib/vendor/pako/lib/zlib/zstream.js +2 -18
  63. package/lib/websock.js +37 -88
  64. package/package.json +32 -35
package/core/rfb.js CHANGED
@@ -25,6 +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 RSAAESAuthenticationState from "./ra2.js";
29
+ import { MD5 } from "./util/md5.js";
28
30
 
29
31
  import RawDecoder from "./decoders/raw.js";
30
32
  import CopyRectDecoder from "./decoders/copyrect.js";
@@ -32,6 +34,8 @@ import RREDecoder from "./decoders/rre.js";
32
34
  import HextileDecoder from "./decoders/hextile.js";
33
35
  import TightDecoder from "./decoders/tight.js";
34
36
  import TightPNGDecoder from "./decoders/tightpng.js";
37
+ import ZRLEDecoder from "./decoders/zrle.js";
38
+ import JPEGDecoder from "./decoders/jpeg.js";
35
39
 
36
40
  // How many seconds to wait for a disconnect to finish
37
41
  const DISCONNECT_TIMEOUT = 3;
@@ -50,6 +54,22 @@ const GESTURE_SCRLSENS = 50;
50
54
  const DOUBLE_TAP_TIMEOUT = 1000;
51
55
  const DOUBLE_TAP_THRESHOLD = 50;
52
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
+ const securityTypeMSLogonII = 113;
66
+
67
+ // Special Tight security types
68
+ const securityTypeUnixLogon = 129;
69
+
70
+ // VeNCrypt security types
71
+ const securityTypePlain = 256;
72
+
53
73
  // Extended clipboard pseudo-encoding formats
54
74
  const extendedClipboardFormatText = 1;
55
75
  /*eslint-disable no-unused-vars */
@@ -75,6 +95,12 @@ export default class RFB extends EventTargetMixin {
75
95
  throw new Error("Must specify URL, WebSocket or RTCDataChannel");
76
96
  }
77
97
 
98
+ // We rely on modern APIs which might not be available in an
99
+ // insecure context
100
+ if (!window.isSecureContext) {
101
+ Log.Error("noVNC requires a secure context (TLS). Expect crashes!");
102
+ }
103
+
78
104
  super();
79
105
 
80
106
  this._target = target;
@@ -98,6 +124,7 @@ export default class RFB extends EventTargetMixin {
98
124
  this._rfbInitState = '';
99
125
  this._rfbAuthScheme = -1;
100
126
  this._rfbCleanDisconnect = true;
127
+ this._rfbRSAAESAuthenticationState = null;
101
128
 
102
129
  // Server capabilities
103
130
  this._rfbVersion = 0;
@@ -176,6 +203,8 @@ export default class RFB extends EventTargetMixin {
176
203
  handleMouse: this._handleMouse.bind(this),
177
204
  handleWheel: this._handleWheel.bind(this),
178
205
  handleGesture: this._handleGesture.bind(this),
206
+ handleRSAAESCredentialsRequired: this._handleRSAAESCredentialsRequired.bind(this),
207
+ handleRSAAESServerVerification: this._handleRSAAESServerVerification.bind(this),
179
208
  };
180
209
 
181
210
  // main setup
@@ -218,6 +247,8 @@ export default class RFB extends EventTargetMixin {
218
247
  this._decoders[encodings.encodingHextile] = new HextileDecoder();
219
248
  this._decoders[encodings.encodingTight] = new TightDecoder();
220
249
  this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
250
+ this._decoders[encodings.encodingZRLE] = new ZRLEDecoder();
251
+ this._decoders[encodings.encodingJPEG] = new JPEGDecoder();
221
252
 
222
253
  // NB: nothing that needs explicit teardown should be done
223
254
  // before this point, since this can throw an exception
@@ -240,6 +271,8 @@ export default class RFB extends EventTargetMixin {
240
271
  this._sock.on('message', this._handleMessage.bind(this));
241
272
  this._sock.on('error', this._socketError.bind(this));
242
273
 
274
+ this._expectedClientWidth = null;
275
+ this._expectedClientHeight = null;
243
276
  this._resizeObserver = new ResizeObserver(this._eventHandlers.handleResize);
244
277
 
245
278
  // All prepared, kick off the connection
@@ -254,6 +287,7 @@ export default class RFB extends EventTargetMixin {
254
287
 
255
288
  this._viewOnly = false;
256
289
  this._clipViewport = false;
290
+ this._clippingViewport = false;
257
291
  this._scaleViewport = false;
258
292
  this._resizeSession = false;
259
293
 
@@ -285,6 +319,16 @@ export default class RFB extends EventTargetMixin {
285
319
 
286
320
  get capabilities() { return this._capabilities; }
287
321
 
322
+ get clippingViewport() { return this._clippingViewport; }
323
+ _setClippingViewport(on) {
324
+ if (on === this._clippingViewport) {
325
+ return;
326
+ }
327
+ this._clippingViewport = on;
328
+ this.dispatchEvent(new CustomEvent("clippingviewport",
329
+ { detail: this._clippingViewport }));
330
+ }
331
+
288
332
  get touchButton() { return 0; }
289
333
  set touchButton(button) { Log.Warn("Using old API!"); }
290
334
 
@@ -372,11 +416,20 @@ export default class RFB extends EventTargetMixin {
372
416
  this._sock.off('error');
373
417
  this._sock.off('message');
374
418
  this._sock.off('open');
419
+ if (this._rfbRSAAESAuthenticationState !== null) {
420
+ this._rfbRSAAESAuthenticationState.disconnect();
421
+ }
422
+ }
423
+
424
+ approveServer() {
425
+ if (this._rfbRSAAESAuthenticationState !== null) {
426
+ this._rfbRSAAESAuthenticationState.approveServer();
427
+ }
375
428
  }
376
429
 
377
430
  sendCredentials(creds) {
378
431
  this._rfbCredentials = creds;
379
- setTimeout(this._initMsg.bind(this), 0);
432
+ this._resumeAuthentication();
380
433
  }
381
434
 
382
435
  sendCtrlAltDel() {
@@ -432,8 +485,8 @@ export default class RFB extends EventTargetMixin {
432
485
  }
433
486
  }
434
487
 
435
- focus() {
436
- this._canvas.focus();
488
+ focus(options) {
489
+ this._canvas.focus(options);
437
490
  }
438
491
 
439
492
  blur() {
@@ -449,16 +502,45 @@ export default class RFB extends EventTargetMixin {
449
502
  this._clipboardText = text;
450
503
  RFB.messages.extendedClipboardNotify(this._sock, [extendedClipboardFormatText]);
451
504
  } else {
452
- let data = new Uint8Array(text.length);
453
- for (let i = 0; i < text.length; i++) {
454
- // FIXME: text can have values outside of Latin1/Uint8
455
- data[i] = text.charCodeAt(i);
505
+ let length, i;
506
+ let data;
507
+
508
+ length = 0;
509
+ // eslint-disable-next-line no-unused-vars
510
+ for (let codePoint of text) {
511
+ length++;
512
+ }
513
+
514
+ data = new Uint8Array(length);
515
+
516
+ i = 0;
517
+ for (let codePoint of text) {
518
+ let code = codePoint.codePointAt(0);
519
+
520
+ /* Only ISO 8859-1 is supported */
521
+ if (code > 0xff) {
522
+ code = 0x3f; // '?'
523
+ }
524
+
525
+ data[i++] = code;
456
526
  }
457
527
 
458
528
  RFB.messages.clientCutText(this._sock, data);
459
529
  }
460
530
  }
461
531
 
532
+ getImageData() {
533
+ return this._display.getImageData();
534
+ }
535
+
536
+ toDataURL(type, encoderOptions) {
537
+ return this._display.toDataURL(type, encoderOptions);
538
+ }
539
+
540
+ toBlob(callback, type, quality) {
541
+ return this._display.toBlob(callback, type, quality);
542
+ }
543
+
462
544
  // ===== PRIVATE METHODS =====
463
545
 
464
546
  _connect() {
@@ -609,7 +691,7 @@ export default class RFB extends EventTargetMixin {
609
691
  return;
610
692
  }
611
693
 
612
- this.focus();
694
+ this.focus({ preventScroll: true });
613
695
  }
614
696
 
615
697
  _setDesktopName(name) {
@@ -619,7 +701,26 @@ export default class RFB extends EventTargetMixin {
619
701
  { detail: { name: this._fbName } }));
620
702
  }
621
703
 
704
+ _saveExpectedClientSize() {
705
+ this._expectedClientWidth = this._screen.clientWidth;
706
+ this._expectedClientHeight = this._screen.clientHeight;
707
+ }
708
+
709
+ _currentClientSize() {
710
+ return [this._screen.clientWidth, this._screen.clientHeight];
711
+ }
712
+
713
+ _clientHasExpectedSize() {
714
+ const [currentWidth, currentHeight] = this._currentClientSize();
715
+ return currentWidth == this._expectedClientWidth &&
716
+ currentHeight == this._expectedClientHeight;
717
+ }
718
+
622
719
  _handleResize() {
720
+ // Don't change anything if the client size is already as expected
721
+ if (this._clientHasExpectedSize()) {
722
+ return;
723
+ }
623
724
  // If the window resized then our screen element might have
624
725
  // as well. Update the viewport dimensions.
625
726
  window.requestAnimationFrame(() => {
@@ -659,6 +760,16 @@ export default class RFB extends EventTargetMixin {
659
760
  const size = this._screenSize();
660
761
  this._display.viewportChangeSize(size.w, size.h);
661
762
  this._fixScrollbars();
763
+ this._setClippingViewport(size.w < this._display.width ||
764
+ size.h < this._display.height);
765
+ } else {
766
+ this._setClippingViewport(false);
767
+ }
768
+
769
+ // When changing clipping we might show or hide scrollbars.
770
+ // This causes the expected client dimensions to change.
771
+ if (curClip !== newClip) {
772
+ this._saveExpectedClientSize();
662
773
  }
663
774
  }
664
775
 
@@ -684,6 +795,7 @@ export default class RFB extends EventTargetMixin {
684
795
  }
685
796
 
686
797
  const size = this._screenSize();
798
+
687
799
  RFB.messages.setDesktopSize(this._sock,
688
800
  Math.floor(size.w), Math.floor(size.h),
689
801
  this._screenID, this._screenFlags);
@@ -699,12 +811,13 @@ export default class RFB extends EventTargetMixin {
699
811
  }
700
812
 
701
813
  _fixScrollbars() {
702
- // This is a hack because Chrome screws up the calculation
703
- // for when scrollbars are needed. So to fix it we temporarily
704
- // toggle them off and on.
814
+ // This is a hack because Safari on macOS screws up the calculation
815
+ // for when scrollbars are needed. We get scrollbars when making the
816
+ // browser smaller, despite remote resize being enabled. So to fix it
817
+ // we temporarily toggle them off and on.
705
818
  const orig = this._screen.style.overflow;
706
819
  this._screen.style.overflow = 'hidden';
707
- // Force Chrome to recalculate the layout by asking for
820
+ // Force Safari to recalculate the layout by asking for
708
821
  // an element's dimensions
709
822
  this._screen.getBoundingClientRect();
710
823
  this._screen.style.overflow = orig;
@@ -869,8 +982,15 @@ export default class RFB extends EventTargetMixin {
869
982
  }
870
983
  }
871
984
  break;
985
+ case 'connecting':
986
+ while (this._rfbConnectionState === 'connecting') {
987
+ if (!this._initMsg()) {
988
+ break;
989
+ }
990
+ }
991
+ break;
872
992
  default:
873
- this._initMsg();
993
+ Log.Error("Got data while in an invalid state");
874
994
  break;
875
995
  }
876
996
  }
@@ -1242,13 +1362,13 @@ export default class RFB extends EventTargetMixin {
1242
1362
  break;
1243
1363
  case "003.003":
1244
1364
  case "003.006": // UltraVNC
1245
- case "003.889": // Apple Remote Desktop
1246
1365
  this._rfbVersion = 3.3;
1247
1366
  break;
1248
1367
  case "003.007":
1249
1368
  this._rfbVersion = 3.7;
1250
1369
  break;
1251
1370
  case "003.008":
1371
+ case "003.889": // Apple Remote Desktop
1252
1372
  case "004.000": // Intel AMT KVM
1253
1373
  case "004.001": // RealVNC 4.6
1254
1374
  case "005.000": // RealVNC 5.3
@@ -1279,6 +1399,22 @@ export default class RFB extends EventTargetMixin {
1279
1399
  this._rfbInitState = 'Security';
1280
1400
  }
1281
1401
 
1402
+ _isSupportedSecurityType(type) {
1403
+ const clientTypes = [
1404
+ securityTypeNone,
1405
+ securityTypeVNCAuth,
1406
+ securityTypeRA2ne,
1407
+ securityTypeTight,
1408
+ securityTypeVeNCrypt,
1409
+ securityTypeXVP,
1410
+ securityTypeARD,
1411
+ securityTypeMSLogonII,
1412
+ securityTypePlain,
1413
+ ];
1414
+
1415
+ return clientTypes.includes(type);
1416
+ }
1417
+
1282
1418
  _negotiateSecurity() {
1283
1419
  if (this._rfbVersion >= 3.7) {
1284
1420
  // Server sends supported list, client decides
@@ -1289,24 +1425,23 @@ export default class RFB extends EventTargetMixin {
1289
1425
  this._rfbInitState = "SecurityReason";
1290
1426
  this._securityContext = "no security types";
1291
1427
  this._securityStatus = 1;
1292
- return this._initMsg();
1428
+ return true;
1293
1429
  }
1294
1430
 
1295
1431
  const types = this._sock.rQshiftBytes(numTypes);
1296
1432
  Log.Debug("Server security types: " + types);
1297
1433
 
1298
- // Look for each auth in preferred order
1299
- if (types.includes(1)) {
1300
- this._rfbAuthScheme = 1; // None
1301
- } else if (types.includes(22)) {
1302
- this._rfbAuthScheme = 22; // XVP
1303
- } else if (types.includes(16)) {
1304
- this._rfbAuthScheme = 16; // Tight
1305
- } else if (types.includes(2)) {
1306
- this._rfbAuthScheme = 2; // VNC Auth
1307
- } else if (types.includes(19)) {
1308
- this._rfbAuthScheme = 19; // VeNCrypt Auth
1309
- } else {
1434
+ // Look for a matching security type in the order that the
1435
+ // server prefers
1436
+ this._rfbAuthScheme = -1;
1437
+ for (let type of types) {
1438
+ if (this._isSupportedSecurityType(type)) {
1439
+ this._rfbAuthScheme = type;
1440
+ break;
1441
+ }
1442
+ }
1443
+
1444
+ if (this._rfbAuthScheme === -1) {
1310
1445
  return this._fail("Unsupported security types (types: " + types + ")");
1311
1446
  }
1312
1447
 
@@ -1320,14 +1455,14 @@ export default class RFB extends EventTargetMixin {
1320
1455
  this._rfbInitState = "SecurityReason";
1321
1456
  this._securityContext = "authentication scheme";
1322
1457
  this._securityStatus = 1;
1323
- return this._initMsg();
1458
+ return true;
1324
1459
  }
1325
1460
  }
1326
1461
 
1327
1462
  this._rfbInitState = 'Authentication';
1328
1463
  Log.Debug('Authenticating using scheme: ' + this._rfbAuthScheme);
1329
1464
 
1330
- return this._initMsg(); // jump to authentication
1465
+ return true;
1331
1466
  }
1332
1467
 
1333
1468
  _handleSecurityReason() {
@@ -1377,7 +1512,7 @@ export default class RFB extends EventTargetMixin {
1377
1512
  this._rfbCredentials.username +
1378
1513
  this._rfbCredentials.target;
1379
1514
  this._sock.sendString(xvpAuthStr);
1380
- this._rfbAuthScheme = 2;
1515
+ this._rfbAuthScheme = securityTypeVNCAuth;
1381
1516
  return this._negotiateAuthentication();
1382
1517
  }
1383
1518
 
@@ -1435,49 +1570,66 @@ export default class RFB extends EventTargetMixin {
1435
1570
  subtypes.push(this._sock.rQshift32());
1436
1571
  }
1437
1572
 
1438
- // 256 = Plain subtype
1439
- if (subtypes.indexOf(256) != -1) {
1440
- // 0x100 = 256
1441
- this._sock.send([0, 0, 1, 0]);
1442
- this._rfbVeNCryptState = 4;
1443
- } else {
1444
- return this._fail("VeNCrypt Plain subtype not offered by server");
1573
+ // Look for a matching security type in the order that the
1574
+ // server prefers
1575
+ this._rfbAuthScheme = -1;
1576
+ for (let type of subtypes) {
1577
+ // Avoid getting in to a loop
1578
+ if (type === securityTypeVeNCrypt) {
1579
+ continue;
1580
+ }
1581
+
1582
+ if (this._isSupportedSecurityType(type)) {
1583
+ this._rfbAuthScheme = type;
1584
+ break;
1585
+ }
1445
1586
  }
1446
- }
1447
1587
 
1448
- // negotiated Plain subtype, server waits for password
1449
- if (this._rfbVeNCryptState == 4) {
1450
- if (this._rfbCredentials.username === undefined ||
1451
- this._rfbCredentials.password === undefined) {
1452
- this.dispatchEvent(new CustomEvent(
1453
- "credentialsrequired",
1454
- { detail: { types: ["username", "password"] } }));
1455
- return false;
1588
+ if (this._rfbAuthScheme === -1) {
1589
+ return this._fail("Unsupported security types (types: " + subtypes + ")");
1456
1590
  }
1457
1591
 
1458
- const user = encodeUTF8(this._rfbCredentials.username);
1459
- const pass = encodeUTF8(this._rfbCredentials.password);
1460
-
1461
- this._sock.send([
1462
- (user.length >> 24) & 0xFF,
1463
- (user.length >> 16) & 0xFF,
1464
- (user.length >> 8) & 0xFF,
1465
- user.length & 0xFF
1466
- ]);
1467
- this._sock.send([
1468
- (pass.length >> 24) & 0xFF,
1469
- (pass.length >> 16) & 0xFF,
1470
- (pass.length >> 8) & 0xFF,
1471
- pass.length & 0xFF
1472
- ]);
1473
- this._sock.sendString(user);
1474
- this._sock.sendString(pass);
1592
+ this._sock.send([this._rfbAuthScheme >> 24,
1593
+ this._rfbAuthScheme >> 16,
1594
+ this._rfbAuthScheme >> 8,
1595
+ this._rfbAuthScheme]);
1475
1596
 
1476
- this._rfbInitState = "SecurityResult";
1597
+ this._rfbVeNCryptState == 4;
1477
1598
  return true;
1478
1599
  }
1479
1600
  }
1480
1601
 
1602
+ _negotiatePlainAuth() {
1603
+ if (this._rfbCredentials.username === undefined ||
1604
+ this._rfbCredentials.password === undefined) {
1605
+ this.dispatchEvent(new CustomEvent(
1606
+ "credentialsrequired",
1607
+ { detail: { types: ["username", "password"] } }));
1608
+ return false;
1609
+ }
1610
+
1611
+ const user = encodeUTF8(this._rfbCredentials.username);
1612
+ const pass = encodeUTF8(this._rfbCredentials.password);
1613
+
1614
+ this._sock.send([
1615
+ (user.length >> 24) & 0xFF,
1616
+ (user.length >> 16) & 0xFF,
1617
+ (user.length >> 8) & 0xFF,
1618
+ user.length & 0xFF
1619
+ ]);
1620
+ this._sock.send([
1621
+ (pass.length >> 24) & 0xFF,
1622
+ (pass.length >> 16) & 0xFF,
1623
+ (pass.length >> 8) & 0xFF,
1624
+ pass.length & 0xFF
1625
+ ]);
1626
+ this._sock.sendString(user);
1627
+ this._sock.sendString(pass);
1628
+
1629
+ this._rfbInitState = "SecurityResult";
1630
+ return true;
1631
+ }
1632
+
1481
1633
  _negotiateStdVNCAuth() {
1482
1634
  if (this._sock.rQwait("auth challenge", 16)) { return false; }
1483
1635
 
@@ -1496,6 +1648,117 @@ export default class RFB extends EventTargetMixin {
1496
1648
  return true;
1497
1649
  }
1498
1650
 
1651
+ _negotiateARDAuth() {
1652
+
1653
+ if (this._rfbCredentials.username === undefined ||
1654
+ this._rfbCredentials.password === undefined) {
1655
+ this.dispatchEvent(new CustomEvent(
1656
+ "credentialsrequired",
1657
+ { detail: { types: ["username", "password"] } }));
1658
+ return false;
1659
+ }
1660
+
1661
+ if (this._rfbCredentials.ardPublicKey != undefined &&
1662
+ this._rfbCredentials.ardCredentials != undefined) {
1663
+ // if the async web crypto is done return the results
1664
+ this._sock.send(this._rfbCredentials.ardCredentials);
1665
+ this._sock.send(this._rfbCredentials.ardPublicKey);
1666
+ this._rfbCredentials.ardCredentials = null;
1667
+ this._rfbCredentials.ardPublicKey = null;
1668
+ this._rfbInitState = "SecurityResult";
1669
+ return true;
1670
+ }
1671
+
1672
+ if (this._sock.rQwait("read ard", 4)) { return false; }
1673
+
1674
+ let generator = this._sock.rQshiftBytes(2); // DH base generator value
1675
+
1676
+ let keyLength = this._sock.rQshift16();
1677
+
1678
+ if (this._sock.rQwait("read ard keylength", keyLength*2, 4)) { return false; }
1679
+
1680
+ // read the server values
1681
+ let prime = this._sock.rQshiftBytes(keyLength); // predetermined prime modulus
1682
+ let serverPublicKey = this._sock.rQshiftBytes(keyLength); // other party's public key
1683
+
1684
+ let clientPrivateKey = window.crypto.getRandomValues(new Uint8Array(keyLength));
1685
+ let padding = Array.from(window.crypto.getRandomValues(new Uint8Array(64)), byte => String.fromCharCode(65+byte%26)).join('');
1686
+
1687
+ this._negotiateARDAuthAsync(generator, keyLength, prime, serverPublicKey, clientPrivateKey, padding);
1688
+
1689
+ return false;
1690
+ }
1691
+
1692
+ _modPow(base, exponent, modulus) {
1693
+
1694
+ let baseHex = "0x"+Array.from(base, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
1695
+ let exponentHex = "0x"+Array.from(exponent, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
1696
+ let modulusHex = "0x"+Array.from(modulus, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
1697
+
1698
+ let b = BigInt(baseHex);
1699
+ let e = BigInt(exponentHex);
1700
+ let m = BigInt(modulusHex);
1701
+ let r = 1n;
1702
+ b = b % m;
1703
+ while (e > 0) {
1704
+ if (e % 2n === 1n) {
1705
+ r = (r * b) % m;
1706
+ }
1707
+ e = e / 2n;
1708
+ b = (b * b) % m;
1709
+ }
1710
+ let hexResult = r.toString(16);
1711
+
1712
+ while (hexResult.length/2<exponent.length || (hexResult.length%2 != 0)) {
1713
+ hexResult = "0"+hexResult;
1714
+ }
1715
+
1716
+ let bytesResult = [];
1717
+ for (let c = 0; c < hexResult.length; c += 2) {
1718
+ bytesResult.push(parseInt(hexResult.substr(c, 2), 16));
1719
+ }
1720
+ return bytesResult;
1721
+ }
1722
+
1723
+ async _aesEcbEncrypt(string, key) {
1724
+ // perform AES-ECB blocks
1725
+ let keyString = Array.from(key, byte => String.fromCharCode(byte)).join('');
1726
+ let aesKey = await window.crypto.subtle.importKey("raw", MD5(keyString), {name: "AES-CBC"}, false, ["encrypt"]);
1727
+ let data = new Uint8Array(string.length);
1728
+ for (let i = 0; i < string.length; ++i) {
1729
+ data[i] = string.charCodeAt(i);
1730
+ }
1731
+ let encrypted = new Uint8Array(data.length);
1732
+ for (let i=0;i<data.length;i+=16) {
1733
+ let block = data.slice(i, i+16);
1734
+ let encryptedBlock = await window.crypto.subtle.encrypt({name: "AES-CBC", iv: block},
1735
+ aesKey, new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
1736
+ );
1737
+ encrypted.set((new Uint8Array(encryptedBlock)).slice(0, 16), i);
1738
+ }
1739
+ return encrypted;
1740
+ }
1741
+
1742
+ async _negotiateARDAuthAsync(generator, keyLength, prime, serverPublicKey, clientPrivateKey, padding) {
1743
+ // calculate the DH keys
1744
+ let clientPublicKey = this._modPow(generator, clientPrivateKey, prime);
1745
+ let sharedKey = this._modPow(serverPublicKey, clientPrivateKey, prime);
1746
+
1747
+ let username = encodeUTF8(this._rfbCredentials.username).substring(0, 63);
1748
+ let password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
1749
+
1750
+ let paddedUsername = username + '\0' + padding.substring(0, 63);
1751
+ let paddedPassword = password + '\0' + padding.substring(0, 63);
1752
+ let credentials = paddedUsername.substring(0, 64) + paddedPassword.substring(0, 64);
1753
+
1754
+ let encrypted = await this._aesEcbEncrypt(credentials, sharedKey);
1755
+
1756
+ this._rfbCredentials.ardCredentials = encrypted;
1757
+ this._rfbCredentials.ardPublicKey = clientPublicKey;
1758
+
1759
+ this._resumeAuthentication();
1760
+ }
1761
+
1499
1762
  _negotiateTightUnixAuth() {
1500
1763
  if (this._rfbCredentials.username === undefined ||
1501
1764
  this._rfbCredentials.password === undefined) {
@@ -1603,12 +1866,12 @@ export default class RFB extends EventTargetMixin {
1603
1866
  case 'STDVNOAUTH__': // no auth
1604
1867
  this._rfbInitState = 'SecurityResult';
1605
1868
  return true;
1606
- case 'STDVVNCAUTH_': // VNC auth
1607
- this._rfbAuthScheme = 2;
1608
- return this._initMsg();
1609
- case 'TGHTULGNAUTH': // UNIX auth
1610
- this._rfbAuthScheme = 129;
1611
- return this._initMsg();
1869
+ case 'STDVVNCAUTH_':
1870
+ this._rfbAuthScheme = securityTypeVNCAuth;
1871
+ return true;
1872
+ case 'TGHTULGNAUTH':
1873
+ this._rfbAuthScheme = securityTypeUnixLogon;
1874
+ return true;
1612
1875
  default:
1613
1876
  return this._fail("Unsupported tiny auth scheme " +
1614
1877
  "(scheme: " + authType + ")");
@@ -1619,31 +1882,133 @@ export default class RFB extends EventTargetMixin {
1619
1882
  return this._fail("No supported sub-auth types!");
1620
1883
  }
1621
1884
 
1885
+ _handleRSAAESCredentialsRequired(event) {
1886
+ this.dispatchEvent(event);
1887
+ }
1888
+
1889
+ _handleRSAAESServerVerification(event) {
1890
+ this.dispatchEvent(event);
1891
+ }
1892
+
1893
+ _negotiateRA2neAuth() {
1894
+ if (this._rfbRSAAESAuthenticationState === null) {
1895
+ this._rfbRSAAESAuthenticationState = new RSAAESAuthenticationState(this._sock, () => this._rfbCredentials);
1896
+ this._rfbRSAAESAuthenticationState.addEventListener(
1897
+ "serververification", this._eventHandlers.handleRSAAESServerVerification);
1898
+ this._rfbRSAAESAuthenticationState.addEventListener(
1899
+ "credentialsrequired", this._eventHandlers.handleRSAAESCredentialsRequired);
1900
+ }
1901
+ this._rfbRSAAESAuthenticationState.checkInternalEvents();
1902
+ if (!this._rfbRSAAESAuthenticationState.hasStarted) {
1903
+ this._rfbRSAAESAuthenticationState.negotiateRA2neAuthAsync()
1904
+ .catch((e) => {
1905
+ if (e.message !== "disconnect normally") {
1906
+ this._fail(e.message);
1907
+ }
1908
+ }).then(() => {
1909
+ this.dispatchEvent(new CustomEvent('securityresult'));
1910
+ this._rfbInitState = "SecurityResult";
1911
+ return true;
1912
+ }).finally(() => {
1913
+ this._rfbRSAAESAuthenticationState.removeEventListener(
1914
+ "serververification", this._eventHandlers.handleRSAAESServerVerification);
1915
+ this._rfbRSAAESAuthenticationState.removeEventListener(
1916
+ "credentialsrequired", this._eventHandlers.handleRSAAESCredentialsRequired);
1917
+ this._rfbRSAAESAuthenticationState = null;
1918
+ });
1919
+ }
1920
+ return false;
1921
+ }
1922
+
1923
+ _negotiateMSLogonIIAuth() {
1924
+ if (this._sock.rQwait("mslogonii dh param", 24)) { return false; }
1925
+
1926
+ if (this._rfbCredentials.username === undefined ||
1927
+ this._rfbCredentials.password === undefined) {
1928
+ this.dispatchEvent(new CustomEvent(
1929
+ "credentialsrequired",
1930
+ { detail: { types: ["username", "password"] } }));
1931
+ return false;
1932
+ }
1933
+
1934
+ const g = this._sock.rQshiftBytes(8);
1935
+ const p = this._sock.rQshiftBytes(8);
1936
+ const A = this._sock.rQshiftBytes(8);
1937
+ const b = window.crypto.getRandomValues(new Uint8Array(8));
1938
+ const B = new Uint8Array(this._modPow(g, b, p));
1939
+ const secret = new Uint8Array(this._modPow(A, b, p));
1940
+
1941
+ const des = new DES(secret);
1942
+ const username = encodeUTF8(this._rfbCredentials.username).substring(0, 255);
1943
+ const password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
1944
+ const usernameBytes = new Uint8Array(256);
1945
+ const passwordBytes = new Uint8Array(64);
1946
+ window.crypto.getRandomValues(usernameBytes);
1947
+ window.crypto.getRandomValues(passwordBytes);
1948
+ for (let i = 0; i < username.length; i++) {
1949
+ usernameBytes[i] = username.charCodeAt(i);
1950
+ }
1951
+ usernameBytes[username.length] = 0;
1952
+ for (let i = 0; i < password.length; i++) {
1953
+ passwordBytes[i] = password.charCodeAt(i);
1954
+ }
1955
+ passwordBytes[password.length] = 0;
1956
+ let x = new Uint8Array(secret);
1957
+ for (let i = 0; i < 32; i++) {
1958
+ for (let j = 0; j < 8; j++) {
1959
+ x[j] ^= usernameBytes[i * 8 + j];
1960
+ }
1961
+ x = des.enc8(x);
1962
+ usernameBytes.set(x, i * 8);
1963
+ }
1964
+ x = new Uint8Array(secret);
1965
+ for (let i = 0; i < 8; i++) {
1966
+ for (let j = 0; j < 8; j++) {
1967
+ x[j] ^= passwordBytes[i * 8 + j];
1968
+ }
1969
+ x = des.enc8(x);
1970
+ passwordBytes.set(x, i * 8);
1971
+ }
1972
+ this._sock.send(B);
1973
+ this._sock.send(usernameBytes);
1974
+ this._sock.send(passwordBytes);
1975
+ this._rfbInitState = "SecurityResult";
1976
+ return true;
1977
+ }
1978
+
1622
1979
  _negotiateAuthentication() {
1623
1980
  switch (this._rfbAuthScheme) {
1624
- case 1: // no auth
1625
- if (this._rfbVersion >= 3.8) {
1626
- this._rfbInitState = 'SecurityResult';
1627
- return true;
1628
- }
1629
- this._rfbInitState = 'ClientInitialisation';
1630
- return this._initMsg();
1981
+ case securityTypeNone:
1982
+ this._rfbInitState = 'SecurityResult';
1983
+ return true;
1631
1984
 
1632
- case 22: // XVP auth
1985
+ case securityTypeXVP:
1633
1986
  return this._negotiateXvpAuth();
1634
1987
 
1635
- case 2: // VNC authentication
1988
+ case securityTypeARD:
1989
+ return this._negotiateARDAuth();
1990
+
1991
+ case securityTypeVNCAuth:
1636
1992
  return this._negotiateStdVNCAuth();
1637
1993
 
1638
- case 16: // TightVNC Security Type
1994
+ case securityTypeTight:
1639
1995
  return this._negotiateTightAuth();
1640
1996
 
1641
- case 19: // VeNCrypt Security Type
1997
+ case securityTypeVeNCrypt:
1642
1998
  return this._negotiateVeNCryptAuth();
1643
1999
 
1644
- case 129: // TightVNC UNIX Security Type
2000
+ case securityTypePlain:
2001
+ return this._negotiatePlainAuth();
2002
+
2003
+ case securityTypeUnixLogon:
1645
2004
  return this._negotiateTightUnixAuth();
1646
2005
 
2006
+ case securityTypeRA2ne:
2007
+ return this._negotiateRA2neAuth();
2008
+
2009
+ case securityTypeMSLogonII:
2010
+ return this._negotiateMSLogonIIAuth();
2011
+
1647
2012
  default:
1648
2013
  return this._fail("Unsupported auth scheme (scheme: " +
1649
2014
  this._rfbAuthScheme + ")");
@@ -1651,6 +2016,13 @@ export default class RFB extends EventTargetMixin {
1651
2016
  }
1652
2017
 
1653
2018
  _handleSecurityResult() {
2019
+ // There is no security choice, and hence no security result
2020
+ // until RFB 3.7
2021
+ if (this._rfbVersion < 3.7) {
2022
+ this._rfbInitState = 'ClientInitialisation';
2023
+ return true;
2024
+ }
2025
+
1654
2026
  if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
1655
2027
 
1656
2028
  const status = this._sock.rQshift32();
@@ -1658,13 +2030,13 @@ export default class RFB extends EventTargetMixin {
1658
2030
  if (status === 0) { // OK
1659
2031
  this._rfbInitState = 'ClientInitialisation';
1660
2032
  Log.Debug('Authentication OK');
1661
- return this._initMsg();
2033
+ return true;
1662
2034
  } else {
1663
2035
  if (this._rfbVersion >= 3.8) {
1664
2036
  this._rfbInitState = "SecurityReason";
1665
2037
  this._securityContext = "security result";
1666
2038
  this._securityStatus = status;
1667
- return this._initMsg();
2039
+ return true;
1668
2040
  } else {
1669
2041
  this.dispatchEvent(new CustomEvent(
1670
2042
  "securityfailure",
@@ -1772,6 +2144,8 @@ export default class RFB extends EventTargetMixin {
1772
2144
  if (this._fbDepth == 24) {
1773
2145
  encs.push(encodings.encodingTight);
1774
2146
  encs.push(encodings.encodingTightPNG);
2147
+ encs.push(encodings.encodingZRLE);
2148
+ encs.push(encodings.encodingJPEG);
1775
2149
  encs.push(encodings.encodingHextile);
1776
2150
  encs.push(encodings.encodingRRE);
1777
2151
  }
@@ -1838,6 +2212,14 @@ export default class RFB extends EventTargetMixin {
1838
2212
  }
1839
2213
  }
1840
2214
 
2215
+ // Resume authentication handshake after it was paused for some
2216
+ // reason, e.g. waiting for a password from the user
2217
+ _resumeAuthentication() {
2218
+ // We use setTimeout() so it's run in its own context, just like
2219
+ // it originally did via the WebSocket's event handler
2220
+ setTimeout(this._initMsg.bind(this), 0);
2221
+ }
2222
+
1841
2223
  _handleSetColourMapMsg() {
1842
2224
  Log.Debug("SetColorMapEntries");
1843
2225
 
@@ -2500,6 +2882,9 @@ export default class RFB extends EventTargetMixin {
2500
2882
  this._updateScale();
2501
2883
 
2502
2884
  this._updateContinuousUpdates();
2885
+
2886
+ // Keep this size until browser client size changes
2887
+ this._saveExpectedClientSize();
2503
2888
  }
2504
2889
 
2505
2890
  _xvpOp(ver, op) {