@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.
- package/README.md +13 -3
- package/core/decoders/jpeg.js +141 -0
- package/core/decoders/raw.js +1 -1
- package/core/decoders/zrle.js +185 -0
- package/core/display.js +12 -0
- package/core/encodings.js +4 -0
- package/core/input/keyboard.js +10 -0
- package/core/ra2.js +567 -0
- package/core/rfb.js +393 -84
- package/core/util/md5.js +79 -0
- package/docs/API.md +105 -4
- package/lib/base64.js +20 -34
- package/lib/decoders/copyrect.js +1 -11
- package/lib/decoders/hextile.js +14 -46
- package/lib/decoders/jpeg.js +146 -0
- package/lib/decoders/raw.js +6 -22
- package/lib/decoders/rre.js +1 -15
- package/lib/decoders/tight.js +10 -78
- package/lib/decoders/tightpng.js +5 -27
- package/lib/decoders/zrle.js +185 -0
- package/lib/deflator.js +5 -22
- package/lib/des.js +20 -36
- package/lib/display.js +59 -107
- package/lib/encodings.js +7 -8
- package/lib/inflator.js +2 -18
- package/lib/input/domkeytable.js +77 -48
- package/lib/input/fixedkeys.js +8 -3
- package/lib/input/gesturehandler.js +82 -152
- package/lib/input/keyboard.js +59 -90
- package/lib/input/keysym.js +14 -270
- package/lib/input/keysymdef.js +5 -7
- package/lib/input/util.js +44 -86
- package/lib/input/vkeys.js +0 -3
- package/lib/input/xtscancodes.js +1 -168
- package/lib/ra2.js +1033 -0
- package/lib/rfb.js +716 -922
- package/lib/util/browser.js +24 -29
- package/lib/util/cursor.js +21 -65
- package/lib/util/element.js +3 -5
- package/lib/util/events.js +25 -32
- package/lib/util/eventtarget.js +1 -13
- package/lib/util/int.js +2 -3
- package/lib/util/logging.js +3 -21
- package/lib/util/md5.js +83 -0
- package/lib/util/strings.js +3 -5
- package/lib/vendor/pako/lib/utils/common.js +10 -19
- package/lib/vendor/pako/lib/zlib/adler32.js +3 -7
- package/lib/vendor/pako/lib/zlib/constants.js +2 -5
- package/lib/vendor/pako/lib/zlib/crc32.js +5 -12
- package/lib/vendor/pako/lib/zlib/deflate.js +218 -622
- package/lib/vendor/pako/lib/zlib/gzheader.js +1 -13
- package/lib/vendor/pako/lib/zlib/inffast.js +60 -172
- package/lib/vendor/pako/lib/zlib/inflate.js +407 -874
- package/lib/vendor/pako/lib/zlib/inftrees.js +63 -169
- package/lib/vendor/pako/lib/zlib/messages.js +1 -11
- package/lib/vendor/pako/lib/zlib/trees.js +248 -590
- package/lib/vendor/pako/lib/zlib/zstream.js +2 -18
- package/lib/websock.js +33 -86
- 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
|
-
|
|
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
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
|
703
|
-
// for when scrollbars are needed.
|
|
704
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
this.
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
//
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
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
|
-
|
|
1449
|
-
|
|
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
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
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.
|
|
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_':
|
|
1607
|
-
this._rfbAuthScheme =
|
|
1608
|
-
return
|
|
1609
|
-
case 'TGHTULGNAUTH':
|
|
1610
|
-
this._rfbAuthScheme =
|
|
1611
|
-
return
|
|
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
|
|
1625
|
-
|
|
1626
|
-
|
|
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
|
|
1912
|
+
case securityTypeXVP:
|
|
1633
1913
|
return this._negotiateXvpAuth();
|
|
1634
1914
|
|
|
1635
|
-
case
|
|
1915
|
+
case securityTypeARD:
|
|
1916
|
+
return this._negotiateARDAuth();
|
|
1917
|
+
|
|
1918
|
+
case securityTypeVNCAuth:
|
|
1636
1919
|
return this._negotiateStdVNCAuth();
|
|
1637
1920
|
|
|
1638
|
-
case
|
|
1921
|
+
case securityTypeTight:
|
|
1639
1922
|
return this._negotiateTightAuth();
|
|
1640
1923
|
|
|
1641
|
-
case
|
|
1924
|
+
case securityTypeVeNCrypt:
|
|
1642
1925
|
return this._negotiateVeNCryptAuth();
|
|
1643
1926
|
|
|
1644
|
-
case
|
|
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
|
|
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
|
|
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) {
|