@novnc/novnc 1.3.0-beta → 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 (59) hide show
  1. package/README.md +13 -3
  2. package/core/decoders/jpeg.js +141 -0
  3. package/core/decoders/raw.js +1 -1
  4. package/core/decoders/zrle.js +185 -0
  5. package/core/display.js +12 -0
  6. package/core/encodings.js +4 -0
  7. package/core/input/keyboard.js +10 -0
  8. package/core/ra2.js +567 -0
  9. package/core/rfb.js +393 -84
  10. package/core/util/md5.js +79 -0
  11. package/docs/API.md +105 -4
  12. package/lib/base64.js +20 -34
  13. package/lib/decoders/copyrect.js +1 -11
  14. package/lib/decoders/hextile.js +14 -46
  15. package/lib/decoders/jpeg.js +146 -0
  16. package/lib/decoders/raw.js +6 -22
  17. package/lib/decoders/rre.js +1 -15
  18. package/lib/decoders/tight.js +10 -78
  19. package/lib/decoders/tightpng.js +5 -27
  20. package/lib/decoders/zrle.js +185 -0
  21. package/lib/deflator.js +5 -22
  22. package/lib/des.js +20 -36
  23. package/lib/display.js +59 -107
  24. package/lib/encodings.js +7 -8
  25. package/lib/inflator.js +2 -18
  26. package/lib/input/domkeytable.js +77 -48
  27. package/lib/input/fixedkeys.js +8 -3
  28. package/lib/input/gesturehandler.js +82 -152
  29. package/lib/input/keyboard.js +59 -90
  30. package/lib/input/keysym.js +14 -270
  31. package/lib/input/keysymdef.js +5 -7
  32. package/lib/input/util.js +44 -86
  33. package/lib/input/vkeys.js +0 -3
  34. package/lib/input/xtscancodes.js +1 -168
  35. package/lib/ra2.js +1033 -0
  36. package/lib/rfb.js +716 -922
  37. package/lib/util/browser.js +24 -29
  38. package/lib/util/cursor.js +21 -65
  39. package/lib/util/element.js +3 -5
  40. package/lib/util/events.js +25 -32
  41. package/lib/util/eventtarget.js +1 -13
  42. package/lib/util/int.js +2 -3
  43. package/lib/util/logging.js +3 -21
  44. package/lib/util/md5.js +83 -0
  45. package/lib/util/strings.js +3 -5
  46. package/lib/vendor/pako/lib/utils/common.js +10 -19
  47. package/lib/vendor/pako/lib/zlib/adler32.js +3 -7
  48. package/lib/vendor/pako/lib/zlib/constants.js +2 -5
  49. package/lib/vendor/pako/lib/zlib/crc32.js +5 -12
  50. package/lib/vendor/pako/lib/zlib/deflate.js +218 -622
  51. package/lib/vendor/pako/lib/zlib/gzheader.js +1 -13
  52. package/lib/vendor/pako/lib/zlib/inffast.js +60 -172
  53. package/lib/vendor/pako/lib/zlib/inflate.js +407 -874
  54. package/lib/vendor/pako/lib/zlib/inftrees.js +63 -169
  55. package/lib/vendor/pako/lib/zlib/messages.js +1 -11
  56. package/lib/vendor/pako/lib/zlib/trees.js +248 -590
  57. package/lib/vendor/pako/lib/zlib/zstream.js +2 -18
  58. package/lib/websock.js +33 -86
  59. package/package.json +2 -7
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,21 @@ 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
+
66
+ // Special Tight security types
67
+ const securityTypeUnixLogon = 129;
68
+
69
+ // VeNCrypt security types
70
+ const securityTypePlain = 256;
71
+
53
72
  // Extended clipboard pseudo-encoding formats
54
73
  const extendedClipboardFormatText = 1;
55
74
  /*eslint-disable no-unused-vars */
@@ -75,6 +94,12 @@ export default class RFB extends EventTargetMixin {
75
94
  throw new Error("Must specify URL, WebSocket or RTCDataChannel");
76
95
  }
77
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!");
101
+ }
102
+
78
103
  super();
79
104
 
80
105
  this._target = target;
@@ -98,6 +123,7 @@ export default class RFB extends EventTargetMixin {
98
123
  this._rfbInitState = '';
99
124
  this._rfbAuthScheme = -1;
100
125
  this._rfbCleanDisconnect = true;
126
+ this._rfbRSAAESAuthenticationState = null;
101
127
 
102
128
  // Server capabilities
103
129
  this._rfbVersion = 0;
@@ -176,6 +202,8 @@ export default class RFB extends EventTargetMixin {
176
202
  handleMouse: this._handleMouse.bind(this),
177
203
  handleWheel: this._handleWheel.bind(this),
178
204
  handleGesture: this._handleGesture.bind(this),
205
+ handleRSAAESCredentialsRequired: this._handleRSAAESCredentialsRequired.bind(this),
206
+ handleRSAAESServerVerification: this._handleRSAAESServerVerification.bind(this),
179
207
  };
180
208
 
181
209
  // main setup
@@ -218,6 +246,8 @@ export default class RFB extends EventTargetMixin {
218
246
  this._decoders[encodings.encodingHextile] = new HextileDecoder();
219
247
  this._decoders[encodings.encodingTight] = new TightDecoder();
220
248
  this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
249
+ this._decoders[encodings.encodingZRLE] = new ZRLEDecoder();
250
+ this._decoders[encodings.encodingJPEG] = new JPEGDecoder();
221
251
 
222
252
  // NB: nothing that needs explicit teardown should be done
223
253
  // before this point, since this can throw an exception
@@ -240,6 +270,8 @@ export default class RFB extends EventTargetMixin {
240
270
  this._sock.on('message', this._handleMessage.bind(this));
241
271
  this._sock.on('error', this._socketError.bind(this));
242
272
 
273
+ this._expectedClientWidth = null;
274
+ this._expectedClientHeight = null;
243
275
  this._resizeObserver = new ResizeObserver(this._eventHandlers.handleResize);
244
276
 
245
277
  // All prepared, kick off the connection
@@ -372,11 +404,20 @@ export default class RFB extends EventTargetMixin {
372
404
  this._sock.off('error');
373
405
  this._sock.off('message');
374
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
+ }
375
416
  }
376
417
 
377
418
  sendCredentials(creds) {
378
419
  this._rfbCredentials = creds;
379
- setTimeout(this._initMsg.bind(this), 0);
420
+ this._resumeAuthentication();
380
421
  }
381
422
 
382
423
  sendCtrlAltDel() {
@@ -432,8 +473,8 @@ export default class RFB extends EventTargetMixin {
432
473
  }
433
474
  }
434
475
 
435
- focus() {
436
- this._canvas.focus();
476
+ focus(options) {
477
+ this._canvas.focus(options);
437
478
  }
438
479
 
439
480
  blur() {
@@ -449,16 +490,45 @@ export default class RFB extends EventTargetMixin {
449
490
  this._clipboardText = text;
450
491
  RFB.messages.extendedClipboardNotify(this._sock, [extendedClipboardFormatText]);
451
492
  } 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);
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;
456
514
  }
457
515
 
458
516
  RFB.messages.clientCutText(this._sock, data);
459
517
  }
460
518
  }
461
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
+
462
532
  // ===== PRIVATE METHODS =====
463
533
 
464
534
  _connect() {
@@ -609,7 +679,7 @@ export default class RFB extends EventTargetMixin {
609
679
  return;
610
680
  }
611
681
 
612
- this.focus();
682
+ this.focus({ preventScroll: true });
613
683
  }
614
684
 
615
685
  _setDesktopName(name) {
@@ -619,7 +689,26 @@ export default class RFB extends EventTargetMixin {
619
689
  { detail: { name: this._fbName } }));
620
690
  }
621
691
 
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
+
622
707
  _handleResize() {
708
+ // Don't change anything if the client size is already as expected
709
+ if (this._clientHasExpectedSize()) {
710
+ return;
711
+ }
623
712
  // If the window resized then our screen element might have
624
713
  // as well. Update the viewport dimensions.
625
714
  window.requestAnimationFrame(() => {
@@ -660,6 +749,12 @@ export default class RFB extends EventTargetMixin {
660
749
  this._display.viewportChangeSize(size.w, size.h);
661
750
  this._fixScrollbars();
662
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
+ }
663
758
  }
664
759
 
665
760
  _updateScale() {
@@ -684,6 +779,7 @@ export default class RFB extends EventTargetMixin {
684
779
  }
685
780
 
686
781
  const size = this._screenSize();
782
+
687
783
  RFB.messages.setDesktopSize(this._sock,
688
784
  Math.floor(size.w), Math.floor(size.h),
689
785
  this._screenID, this._screenFlags);
@@ -699,12 +795,13 @@ export default class RFB extends EventTargetMixin {
699
795
  }
700
796
 
701
797
  _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.
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.
705
802
  const orig = this._screen.style.overflow;
706
803
  this._screen.style.overflow = 'hidden';
707
- // Force Chrome to recalculate the layout by asking for
804
+ // Force Safari to recalculate the layout by asking for
708
805
  // an element's dimensions
709
806
  this._screen.getBoundingClientRect();
710
807
  this._screen.style.overflow = orig;
@@ -869,8 +966,15 @@ export default class RFB extends EventTargetMixin {
869
966
  }
870
967
  }
871
968
  break;
969
+ case 'connecting':
970
+ while (this._rfbConnectionState === 'connecting') {
971
+ if (!this._initMsg()) {
972
+ break;
973
+ }
974
+ }
975
+ break;
872
976
  default:
873
- this._initMsg();
977
+ Log.Error("Got data while in an invalid state");
874
978
  break;
875
979
  }
876
980
  }
@@ -1242,13 +1346,13 @@ export default class RFB extends EventTargetMixin {
1242
1346
  break;
1243
1347
  case "003.003":
1244
1348
  case "003.006": // UltraVNC
1245
- case "003.889": // Apple Remote Desktop
1246
1349
  this._rfbVersion = 3.3;
1247
1350
  break;
1248
1351
  case "003.007":
1249
1352
  this._rfbVersion = 3.7;
1250
1353
  break;
1251
1354
  case "003.008":
1355
+ case "003.889": // Apple Remote Desktop
1252
1356
  case "004.000": // Intel AMT KVM
1253
1357
  case "004.001": // RealVNC 4.6
1254
1358
  case "005.000": // RealVNC 5.3
@@ -1279,6 +1383,21 @@ export default class RFB extends EventTargetMixin {
1279
1383
  this._rfbInitState = 'Security';
1280
1384
  }
1281
1385
 
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
+ }
1400
+
1282
1401
  _negotiateSecurity() {
1283
1402
  if (this._rfbVersion >= 3.7) {
1284
1403
  // Server sends supported list, client decides
@@ -1289,24 +1408,23 @@ export default class RFB extends EventTargetMixin {
1289
1408
  this._rfbInitState = "SecurityReason";
1290
1409
  this._securityContext = "no security types";
1291
1410
  this._securityStatus = 1;
1292
- return this._initMsg();
1411
+ return true;
1293
1412
  }
1294
1413
 
1295
1414
  const types = this._sock.rQshiftBytes(numTypes);
1296
1415
  Log.Debug("Server security types: " + types);
1297
1416
 
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 {
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) {
1310
1428
  return this._fail("Unsupported security types (types: " + types + ")");
1311
1429
  }
1312
1430
 
@@ -1320,14 +1438,14 @@ export default class RFB extends EventTargetMixin {
1320
1438
  this._rfbInitState = "SecurityReason";
1321
1439
  this._securityContext = "authentication scheme";
1322
1440
  this._securityStatus = 1;
1323
- return this._initMsg();
1441
+ return true;
1324
1442
  }
1325
1443
  }
1326
1444
 
1327
1445
  this._rfbInitState = 'Authentication';
1328
1446
  Log.Debug('Authenticating using scheme: ' + this._rfbAuthScheme);
1329
1447
 
1330
- return this._initMsg(); // jump to authentication
1448
+ return true;
1331
1449
  }
1332
1450
 
1333
1451
  _handleSecurityReason() {
@@ -1377,7 +1495,7 @@ export default class RFB extends EventTargetMixin {
1377
1495
  this._rfbCredentials.username +
1378
1496
  this._rfbCredentials.target;
1379
1497
  this._sock.sendString(xvpAuthStr);
1380
- this._rfbAuthScheme = 2;
1498
+ this._rfbAuthScheme = securityTypeVNCAuth;
1381
1499
  return this._negotiateAuthentication();
1382
1500
  }
1383
1501
 
@@ -1435,49 +1553,66 @@ export default class RFB extends EventTargetMixin {
1435
1553
  subtypes.push(this._sock.rQshift32());
1436
1554
  }
1437
1555
 
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");
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
+ }
1564
+
1565
+ if (this._isSupportedSecurityType(type)) {
1566
+ this._rfbAuthScheme = type;
1567
+ break;
1568
+ }
1445
1569
  }
1446
- }
1447
1570
 
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;
1571
+ if (this._rfbAuthScheme === -1) {
1572
+ return this._fail("Unsupported security types (types: " + subtypes + ")");
1456
1573
  }
1457
1574
 
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);
1575
+ this._sock.send([this._rfbAuthScheme >> 24,
1576
+ this._rfbAuthScheme >> 16,
1577
+ this._rfbAuthScheme >> 8,
1578
+ this._rfbAuthScheme]);
1475
1579
 
1476
- this._rfbInitState = "SecurityResult";
1580
+ this._rfbVeNCryptState == 4;
1477
1581
  return true;
1478
1582
  }
1479
1583
  }
1480
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
+
1481
1616
  _negotiateStdVNCAuth() {
1482
1617
  if (this._sock.rQwait("auth challenge", 16)) { return false; }
1483
1618
 
@@ -1496,6 +1631,117 @@ export default class RFB extends EventTargetMixin {
1496
1631
  return true;
1497
1632
  }
1498
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
+
1499
1745
  _negotiateTightUnixAuth() {
1500
1746
  if (this._rfbCredentials.username === undefined ||
1501
1747
  this._rfbCredentials.password === undefined) {
@@ -1603,12 +1849,12 @@ export default class RFB extends EventTargetMixin {
1603
1849
  case 'STDVNOAUTH__': // no auth
1604
1850
  this._rfbInitState = 'SecurityResult';
1605
1851
  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();
1852
+ case 'STDVVNCAUTH_':
1853
+ this._rfbAuthScheme = securityTypeVNCAuth;
1854
+ return true;
1855
+ case 'TGHTULGNAUTH':
1856
+ this._rfbAuthScheme = securityTypeUnixLogon;
1857
+ return true;
1612
1858
  default:
1613
1859
  return this._fail("Unsupported tiny auth scheme " +
1614
1860
  "(scheme: " + authType + ")");
@@ -1619,31 +1865,74 @@ export default class RFB extends EventTargetMixin {
1619
1865
  return this._fail("No supported sub-auth types!");
1620
1866
  }
1621
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
+
1622
1906
  _negotiateAuthentication() {
1623
1907
  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();
1908
+ case securityTypeNone:
1909
+ this._rfbInitState = 'SecurityResult';
1910
+ return true;
1631
1911
 
1632
- case 22: // XVP auth
1912
+ case securityTypeXVP:
1633
1913
  return this._negotiateXvpAuth();
1634
1914
 
1635
- case 2: // VNC authentication
1915
+ case securityTypeARD:
1916
+ return this._negotiateARDAuth();
1917
+
1918
+ case securityTypeVNCAuth:
1636
1919
  return this._negotiateStdVNCAuth();
1637
1920
 
1638
- case 16: // TightVNC Security Type
1921
+ case securityTypeTight:
1639
1922
  return this._negotiateTightAuth();
1640
1923
 
1641
- case 19: // VeNCrypt Security Type
1924
+ case securityTypeVeNCrypt:
1642
1925
  return this._negotiateVeNCryptAuth();
1643
1926
 
1644
- case 129: // TightVNC UNIX Security Type
1927
+ case securityTypePlain:
1928
+ return this._negotiatePlainAuth();
1929
+
1930
+ case securityTypeUnixLogon:
1645
1931
  return this._negotiateTightUnixAuth();
1646
1932
 
1933
+ case securityTypeRA2ne:
1934
+ return this._negotiateRA2neAuth();
1935
+
1647
1936
  default:
1648
1937
  return this._fail("Unsupported auth scheme (scheme: " +
1649
1938
  this._rfbAuthScheme + ")");
@@ -1651,6 +1940,13 @@ export default class RFB extends EventTargetMixin {
1651
1940
  }
1652
1941
 
1653
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
+
1654
1950
  if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
1655
1951
 
1656
1952
  const status = this._sock.rQshift32();
@@ -1658,13 +1954,13 @@ export default class RFB extends EventTargetMixin {
1658
1954
  if (status === 0) { // OK
1659
1955
  this._rfbInitState = 'ClientInitialisation';
1660
1956
  Log.Debug('Authentication OK');
1661
- return this._initMsg();
1957
+ return true;
1662
1958
  } else {
1663
1959
  if (this._rfbVersion >= 3.8) {
1664
1960
  this._rfbInitState = "SecurityReason";
1665
1961
  this._securityContext = "security result";
1666
1962
  this._securityStatus = status;
1667
- return this._initMsg();
1963
+ return true;
1668
1964
  } else {
1669
1965
  this.dispatchEvent(new CustomEvent(
1670
1966
  "securityfailure",
@@ -1772,6 +2068,8 @@ export default class RFB extends EventTargetMixin {
1772
2068
  if (this._fbDepth == 24) {
1773
2069
  encs.push(encodings.encodingTight);
1774
2070
  encs.push(encodings.encodingTightPNG);
2071
+ encs.push(encodings.encodingZRLE);
2072
+ encs.push(encodings.encodingJPEG);
1775
2073
  encs.push(encodings.encodingHextile);
1776
2074
  encs.push(encodings.encodingRRE);
1777
2075
  }
@@ -1838,6 +2136,14 @@ export default class RFB extends EventTargetMixin {
1838
2136
  }
1839
2137
  }
1840
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
+
1841
2147
  _handleSetColourMapMsg() {
1842
2148
  Log.Debug("SetColorMapEntries");
1843
2149
 
@@ -2500,6 +2806,9 @@ export default class RFB extends EventTargetMixin {
2500
2806
  this._updateScale();
2501
2807
 
2502
2808
  this._updateContinuousUpdates();
2809
+
2810
+ // Keep this size until browser client size changes
2811
+ this._saveExpectedClientSize();
2503
2812
  }
2504
2813
 
2505
2814
  _xvpOp(ver, op) {