summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authorPierre Ossman <ossman@cendio.se>2022-03-10 12:37:34 +0100
committerPierre Ossman <ossman@cendio.se>2022-03-10 12:37:34 +0100
commiteac11d57994397a9696290347b75a0f13cd69fe2 (patch)
tree2d9e9d08fe92a83ec886d04ff45ccc6cc7e033f7 /core
parent80a7c1dbf1c6eb70eb584b3c60adb385bbc0ff7e (diff)
parenta1709b999ee97f77f9ff5d658f8723f328af1686 (diff)
downloadnovnc-eac11d57994397a9696290347b75a0f13cd69fe2.tar.gz
Merge branch 'add-ra2ne-security-type' of https://github.com/pdlan/noVNC
Diffstat (limited to 'core')
-rw-r--r--core/ra2.js567
-rw-r--r--core/rfb.js56
2 files changed, 623 insertions, 0 deletions
diff --git a/core/ra2.js b/core/ra2.js
new file mode 100644
index 0000000..81a8a89
--- /dev/null
+++ b/core/ra2.js
@@ -0,0 +1,567 @@
+import Base64 from './base64.js';
+import { encodeUTF8 } from './util/strings.js';
+import EventTargetMixin from './util/eventtarget.js';
+
+export class AESEAXCipher {
+ constructor() {
+ this._rawKey = null;
+ this._ctrKey = null;
+ this._cbcKey = null;
+ this._zeroBlock = new Uint8Array(16);
+ this._prefixBlock0 = this._zeroBlock;
+ this._prefixBlock1 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
+ this._prefixBlock2 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
+ }
+
+ async _encryptBlock(block) {
+ const encrypted = await window.crypto.subtle.encrypt({
+ name: "AES-CBC",
+ iv: this._zeroBlock,
+ }, this._cbcKey, block);
+ return new Uint8Array(encrypted).slice(0, 16);
+ }
+
+ async _initCMAC() {
+ const k1 = await this._encryptBlock(this._zeroBlock);
+ const k2 = new Uint8Array(16);
+ const v = k1[0] >>> 6;
+ for (let i = 0; i < 15; i++) {
+ k2[i] = (k1[i + 1] >> 6) | (k1[i] << 2);
+ k1[i] = (k1[i + 1] >> 7) | (k1[i] << 1);
+ }
+ const lut = [0x0, 0x87, 0x0e, 0x89];
+ k2[14] ^= v >>> 1;
+ k2[15] = (k1[15] << 2) ^ lut[v];
+ k1[15] = (k1[15] << 1) ^ lut[v >> 1];
+ this._k1 = k1;
+ this._k2 = k2;
+ }
+
+ async _encryptCTR(data, counter) {
+ const encrypted = await window.crypto.subtle.encrypt({
+ "name": "AES-CTR",
+ counter: counter,
+ length: 128
+ }, this._ctrKey, data);
+ return new Uint8Array(encrypted);
+ }
+
+ async _decryptCTR(data, counter) {
+ const decrypted = await window.crypto.subtle.decrypt({
+ "name": "AES-CTR",
+ counter: counter,
+ length: 128
+ }, this._ctrKey, data);
+ return new Uint8Array(decrypted);
+ }
+
+ async _computeCMAC(data, prefixBlock) {
+ if (prefixBlock.length !== 16) {
+ return null;
+ }
+ const n = Math.floor(data.length / 16);
+ const m = Math.ceil(data.length / 16);
+ const r = data.length - n * 16;
+ const cbcData = new Uint8Array((m + 1) * 16);
+ cbcData.set(prefixBlock);
+ cbcData.set(data, 16);
+ if (r === 0) {
+ for (let i = 0; i < 16; i++) {
+ cbcData[n * 16 + i] ^= this._k1[i];
+ }
+ } else {
+ cbcData[(n + 1) * 16 + r] = 0x80;
+ for (let i = 0; i < 16; i++) {
+ cbcData[(n + 1) * 16 + i] ^= this._k2[i];
+ }
+ }
+ let cbcEncrypted = await window.crypto.subtle.encrypt({
+ name: "AES-CBC",
+ iv: this._zeroBlock,
+ }, this._cbcKey, cbcData);
+
+ cbcEncrypted = new Uint8Array(cbcEncrypted);
+ const mac = cbcEncrypted.slice(cbcEncrypted.length - 32, cbcEncrypted.length - 16);
+ return mac;
+ }
+
+ async setKey(key) {
+ this._rawKey = key;
+ this._ctrKey = await window.crypto.subtle.importKey(
+ "raw", key, {"name": "AES-CTR"}, false, ["encrypt", "decrypt"]);
+ this._cbcKey = await window.crypto.subtle.importKey(
+ "raw", key, {"name": "AES-CBC"}, false, ["encrypt", "decrypt"]);
+ await this._initCMAC();
+ }
+
+ async encrypt(message, associatedData, nonce) {
+ const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
+ const encrypted = await this._encryptCTR(message, nCMAC);
+ const adCMAC = await this._computeCMAC(associatedData, this._prefixBlock1);
+ const mac = await this._computeCMAC(encrypted, this._prefixBlock2);
+ for (let i = 0; i < 16; i++) {
+ mac[i] ^= nCMAC[i] ^ adCMAC[i];
+ }
+ const res = new Uint8Array(16 + encrypted.length);
+ res.set(encrypted);
+ res.set(mac, encrypted.length);
+ return res;
+ }
+
+ async decrypt(encrypted, associatedData, nonce, mac) {
+ const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
+ const adCMAC = await this._computeCMAC(associatedData, this._prefixBlock1);
+ const computedMac = await this._computeCMAC(encrypted, this._prefixBlock2);
+ for (let i = 0; i < 16; i++) {
+ computedMac[i] ^= nCMAC[i] ^ adCMAC[i];
+ }
+ if (computedMac.length !== mac.length) {
+ return null;
+ }
+ for (let i = 0; i < mac.length; i++) {
+ if (computedMac[i] !== mac[i]) {
+ return null;
+ }
+ }
+ const res = await this._decryptCTR(encrypted, nCMAC);
+ return res;
+ }
+}
+
+export class RA2Cipher {
+ constructor() {
+ this._cipher = new AESEAXCipher();
+ this._counter = new Uint8Array(16);
+ }
+
+ async setKey(key) {
+ await this._cipher.setKey(key);
+ }
+
+ async makeMessage(message) {
+ const ad = new Uint8Array([(message.length & 0xff00) >>> 8, message.length & 0xff]);
+ const encrypted = await this._cipher.encrypt(message, ad, this._counter);
+ for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
+ const res = new Uint8Array(message.length + 2 + 16);
+ res.set(ad);
+ res.set(encrypted, 2);
+ return res;
+ }
+
+ async receiveMessage(length, encrypted, mac) {
+ const ad = new Uint8Array([(length & 0xff00) >>> 8, length & 0xff]);
+ const res = await this._cipher.decrypt(encrypted, ad, this._counter, mac);
+ for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
+ return res;
+ }
+}
+
+export class RSACipher {
+ constructor(keyLength) {
+ this._key = null;
+ this._keyLength = keyLength;
+ this._keyBytes = Math.ceil(keyLength / 8);
+ this._n = null;
+ this._e = null;
+ this._d = null;
+ this._nBigInt = null;
+ this._eBigInt = null;
+ this._dBigInt = null;
+ }
+
+ _base64urlDecode(data) {
+ data = data.replace(/-/g, "+").replace(/_/g, "/");
+ data = data.padEnd(Math.ceil(data.length / 4) * 4, "=");
+ return Base64.decode(data);
+ }
+
+ _u8ArrayToBigInt(arr) {
+ let hex = '0x';
+ for (let i = 0; i < arr.length; i++) {
+ hex += arr[i].toString(16).padStart(2, '0');
+ }
+ return BigInt(hex);
+ }
+
+ _padArray(arr, length) {
+ const res = new Uint8Array(length);
+ res.set(arr, length - arr.length);
+ return res;
+ }
+
+ _bigIntToU8Array(bigint, padLength=0) {
+ let hex = bigint.toString(16);
+ if (padLength === 0) {
+ padLength = Math.ceil(hex.length / 2) * 2;
+ }
+ hex = hex.padStart(padLength * 2, '0');
+ const length = hex.length / 2;
+ const arr = new Uint8Array(length);
+ for (let i = 0; i < length; i++) {
+ arr[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
+ }
+ return arr;
+ }
+
+ _modPow(b, e, m) {
+ if (m === 1n) {
+ return 0;
+ }
+ let r = 1n;
+ b = b % m;
+ while (e > 0) {
+ if (e % 2n === 1n) {
+ r = (r * b) % m;
+ }
+ e = e / 2n;
+ b = (b * b) % m;
+ }
+ return r;
+ }
+
+ async generateKey() {
+ this._key = await window.crypto.subtle.generateKey(
+ {
+ name: "RSA-OAEP",
+ modulusLength: this._keyLength,
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
+ hash: {name: "SHA-256"},
+ },
+ true, ["encrypt", "decrypt"]);
+ const privateKey = await window.crypto.subtle.exportKey("jwk", this._key.privateKey);
+ this._n = this._padArray(this._base64urlDecode(privateKey.n), this._keyBytes);
+ this._nBigInt = this._u8ArrayToBigInt(this._n);
+ this._e = this._padArray(this._base64urlDecode(privateKey.e), this._keyBytes);
+ this._eBigInt = this._u8ArrayToBigInt(this._e);
+ this._d = this._padArray(this._base64urlDecode(privateKey.d), this._keyBytes);
+ this._dBigInt = this._u8ArrayToBigInt(this._d);
+ }
+
+ setPublicKey(n, e) {
+ if (n.length !== this._keyBytes || e.length !== this._keyBytes) {
+ return;
+ }
+ this._n = new Uint8Array(this._keyBytes);
+ this._e = new Uint8Array(this._keyBytes);
+ this._n.set(n);
+ this._e.set(e);
+ this._nBigInt = this._u8ArrayToBigInt(this._n);
+ this._eBigInt = this._u8ArrayToBigInt(this._e);
+ }
+
+ encrypt(message) {
+ if (message.length > this._keyBytes - 11) {
+ return null;
+ }
+ const ps = new Uint8Array(this._keyBytes - message.length - 3);
+ window.crypto.getRandomValues(ps);
+ for (let i = 0; i < ps.length; i++) {
+ ps[i] = Math.floor(ps[i] * 254 / 255 + 1);
+ }
+ const em = new Uint8Array(this._keyBytes);
+ em[1] = 0x02;
+ em.set(ps, 2);
+ em.set(message, ps.length + 3);
+ const emBigInt = this._u8ArrayToBigInt(em);
+ const c = this._modPow(emBigInt, this._eBigInt, this._nBigInt);
+ return this._bigIntToU8Array(c, this._keyBytes);
+ }
+
+ decrypt(message) {
+ if (message.length !== this._keyBytes) {
+ return null;
+ }
+ const msgBigInt = this._u8ArrayToBigInt(message);
+ const emBigInt = this._modPow(msgBigInt, this._dBigInt, this._nBigInt);
+ const em = this._bigIntToU8Array(emBigInt, this._keyBytes);
+ if (em[0] !== 0x00 || em[1] !== 0x02) {
+ return null;
+ }
+ let i = 2;
+ for (; i < em.length; i++) {
+ if (em[i] === 0x00) {
+ break;
+ }
+ }
+ if (i === em.length) {
+ return null;
+ }
+ return em.slice(i + 1, em.length);
+ }
+
+ get keyLength() {
+ return this._keyLength;
+ }
+
+ get n() {
+ return this._n;
+ }
+
+ get e() {
+ return this._e;
+ }
+
+ get d() {
+ return this._d;
+ }
+}
+
+export default class RSAAESAuthenticationState extends EventTargetMixin {
+ constructor(sock, getCredentials) {
+ super();
+ this._hasStarted = false;
+ this._checkSock = null;
+ this._checkCredentials = null;
+ this._approveServerResolve = null;
+ this._sockReject = null;
+ this._credentialsReject = null;
+ this._approveServerReject = null;
+ this._sock = sock;
+ this._getCredentials = getCredentials;
+ }
+
+ _waitSockAsync(len) {
+ return new Promise((resolve, reject) => {
+ const hasData = () => !this._sock.rQwait('RA2', len);
+ if (hasData()) {
+ resolve();
+ } else {
+ this._checkSock = () => {
+ if (hasData()) {
+ resolve();
+ this._checkSock = null;
+ this._sockReject = null;
+ }
+ };
+ this._sockReject = reject;
+ }
+ });
+ }
+
+ _waitApproveKeyAsync() {
+ return new Promise((resolve, reject) => {
+ this._approveServerResolve = resolve;
+ this._approveServerReject = reject;
+ });
+ }
+
+ _waitCredentialsAsync(subtype) {
+ const hasCredentials = () => {
+ if (subtype === 1 && this._getCredentials().username !== undefined &&
+ this._getCredentials().password !== undefined) {
+ return true;
+ } else if (subtype === 2 && this._getCredentials().password !== undefined) {
+ return true;
+ }
+ return false;
+ };
+ return new Promise((resolve, reject) => {
+ if (hasCredentials()) {
+ resolve();
+ } else {
+ this._checkCredentials = () => {
+ if (hasCredentials()) {
+ resolve();
+ this._checkCredentials = null;
+ this._credentialsReject = null;
+ }
+ };
+ this._credentialsReject = reject;
+ }
+ });
+ }
+
+ checkInternalEvents() {
+ if (this._checkSock !== null) {
+ this._checkSock();
+ }
+ if (this._checkCredentials !== null) {
+ this._checkCredentials();
+ }
+ }
+
+ approveServer() {
+ if (this._approveServerResolve !== null) {
+ this._approveServerResolve();
+ this._approveServerResolve = null;
+ }
+ }
+
+ disconnect() {
+ if (this._sockReject !== null) {
+ this._sockReject(new Error("disconnect normally"));
+ this._sockReject = null;
+ }
+ if (this._credentialsReject !== null) {
+ this._credentialsReject(new Error("disconnect normally"));
+ this._credentialsReject = null;
+ }
+ if (this._approveServerReject !== null) {
+ this._approveServerReject(new Error("disconnect normally"));
+ this._approveServerReject = null;
+ }
+ }
+
+ async negotiateRA2neAuthAsync() {
+ this._hasStarted = true;
+ // 1: Receive server public key
+ await this._waitSockAsync(4);
+ const serverKeyLengthBuffer = this._sock.rQslice(0, 4);
+ const serverKeyLength = this._sock.rQshift32();
+ if (serverKeyLength < 1024) {
+ throw new Error("RA2: server public key is too short: " + serverKeyLength);
+ } else if (serverKeyLength > 8192) {
+ throw new Error("RA2: server public key is too long: " + serverKeyLength);
+ }
+ const serverKeyBytes = Math.ceil(serverKeyLength / 8);
+ await this._waitSockAsync(serverKeyBytes * 2);
+ const serverN = this._sock.rQshiftBytes(serverKeyBytes);
+ const serverE = this._sock.rQshiftBytes(serverKeyBytes);
+ const serverRSACipher = new RSACipher(serverKeyLength);
+ serverRSACipher.setPublicKey(serverN, serverE);
+ const serverPublickey = new Uint8Array(4 + serverKeyBytes * 2);
+ serverPublickey.set(serverKeyLengthBuffer);
+ serverPublickey.set(serverN, 4);
+ serverPublickey.set(serverE, 4 + serverKeyBytes);
+
+ // verify server public key
+ this.dispatchEvent(new CustomEvent("serververification", {
+ detail: { type: "RSA", publickey: serverPublickey }
+ }));
+ await this._waitApproveKeyAsync();
+
+ // 2: Send client public key
+ const clientKeyLength = 2048;
+ const clientKeyBytes = Math.ceil(clientKeyLength / 8);
+ const clientRSACipher = new RSACipher(clientKeyLength);
+ await clientRSACipher.generateKey();
+ const clientN = clientRSACipher.n;
+ const clientE = clientRSACipher.e;
+ const clientPublicKey = new Uint8Array(4 + clientKeyBytes * 2);
+ clientPublicKey[0] = (clientKeyLength & 0xff000000) >>> 24;
+ clientPublicKey[1] = (clientKeyLength & 0xff0000) >>> 16;
+ clientPublicKey[2] = (clientKeyLength & 0xff00) >>> 8;
+ clientPublicKey[3] = clientKeyLength & 0xff;
+ clientPublicKey.set(clientN, 4);
+ clientPublicKey.set(clientE, 4 + clientKeyBytes);
+ this._sock.send(clientPublicKey);
+
+ // 3: Send client random
+ const clientRandom = new Uint8Array(16);
+ window.crypto.getRandomValues(clientRandom);
+ const clientEncryptedRandom = serverRSACipher.encrypt(clientRandom);
+ const clientRandomMessage = new Uint8Array(2 + serverKeyBytes);
+ clientRandomMessage[0] = (serverKeyBytes & 0xff00) >>> 8;
+ clientRandomMessage[1] = serverKeyBytes & 0xff;
+ clientRandomMessage.set(clientEncryptedRandom, 2);
+ this._sock.send(clientRandomMessage);
+
+ // 4: Receive server random
+ await this._waitSockAsync(2);
+ if (this._sock.rQshift16() !== clientKeyBytes) {
+ throw new Error("RA2: wrong encrypted message length");
+ }
+ const serverEncryptedRandom = this._sock.rQshiftBytes(clientKeyBytes);
+ const serverRandom = clientRSACipher.decrypt(serverEncryptedRandom);
+ if (serverRandom === null || serverRandom.length !== 16) {
+ throw new Error("RA2: corrupted server encrypted random");
+ }
+
+ // 5: Compute session keys and set ciphers
+ let clientSessionKey = new Uint8Array(32);
+ let serverSessionKey = new Uint8Array(32);
+ clientSessionKey.set(serverRandom);
+ clientSessionKey.set(clientRandom, 16);
+ serverSessionKey.set(clientRandom);
+ serverSessionKey.set(serverRandom, 16);
+ clientSessionKey = await window.crypto.subtle.digest("SHA-1", clientSessionKey);
+ clientSessionKey = new Uint8Array(clientSessionKey).slice(0, 16);
+ serverSessionKey = await window.crypto.subtle.digest("SHA-1", serverSessionKey);
+ serverSessionKey = new Uint8Array(serverSessionKey).slice(0, 16);
+ const clientCipher = new RA2Cipher();
+ await clientCipher.setKey(clientSessionKey);
+ const serverCipher = new RA2Cipher();
+ await serverCipher.setKey(serverSessionKey);
+
+ // 6: Compute and exchange hashes
+ let serverHash = new Uint8Array(8 + serverKeyBytes * 2 + clientKeyBytes * 2);
+ let clientHash = new Uint8Array(8 + serverKeyBytes * 2 + clientKeyBytes * 2);
+ serverHash.set(serverPublickey);
+ serverHash.set(clientPublicKey, 4 + serverKeyBytes * 2);
+ clientHash.set(clientPublicKey);
+ clientHash.set(serverPublickey, 4 + clientKeyBytes * 2);
+ serverHash = await window.crypto.subtle.digest("SHA-1", serverHash);
+ clientHash = await window.crypto.subtle.digest("SHA-1", clientHash);
+ serverHash = new Uint8Array(serverHash);
+ clientHash = new Uint8Array(clientHash);
+ this._sock.send(await clientCipher.makeMessage(clientHash));
+ await this._waitSockAsync(2 + 20 + 16);
+ if (this._sock.rQshift16() !== 20) {
+ throw new Error("RA2: wrong server hash");
+ }
+ const serverHashReceived = await serverCipher.receiveMessage(
+ 20, this._sock.rQshiftBytes(20), this._sock.rQshiftBytes(16));
+ if (serverHashReceived === null) {
+ throw new Error("RA2: failed to authenticate the message");
+ }
+ for (let i = 0; i < 20; i++) {
+ if (serverHashReceived[i] !== serverHash[i]) {
+ throw new Error("RA2: wrong server hash");
+ }
+ }
+
+ // 7: Receive subtype
+ await this._waitSockAsync(2 + 1 + 16);
+ if (this._sock.rQshift16() !== 1) {
+ throw new Error("RA2: wrong subtype");
+ }
+ let subtype = (await serverCipher.receiveMessage(
+ 1, this._sock.rQshiftBytes(1), this._sock.rQshiftBytes(16)));
+ if (subtype === null) {
+ throw new Error("RA2: failed to authenticate the message");
+ }
+ subtype = subtype[0];
+ if (subtype === 1) {
+ if (this._getCredentials().username === undefined ||
+ this._getCredentials().password === undefined) {
+ this.dispatchEvent(new CustomEvent(
+ "credentialsrequired",
+ { detail: { types: ["username", "password"] } }));
+ }
+ } else if (subtype === 2) {
+ if (this._getCredentials().password === undefined) {
+ this.dispatchEvent(new CustomEvent(
+ "credentialsrequired",
+ { detail: { types: ["password"] } }));
+ }
+ } else {
+ throw new Error("RA2: wrong subtype");
+ }
+ await this._waitCredentialsAsync(subtype);
+ let username;
+ if (subtype === 1) {
+ username = encodeUTF8(this._getCredentials().username).slice(0, 255);
+ } else {
+ username = "";
+ }
+ const password = encodeUTF8(this._getCredentials().password).slice(0, 255);
+ const credentials = new Uint8Array(username.length + password.length + 2);
+ credentials[0] = username.length;
+ credentials[username.length + 1] = password.length;
+ for (let i = 0; i < username.length; i++) {
+ credentials[i + 1] = username.charCodeAt(i);
+ }
+ for (let i = 0; i < password.length; i++) {
+ credentials[username.length + 2 + i] = password.charCodeAt(i);
+ }
+ this._sock.send(await clientCipher.makeMessage(credentials));
+ }
+
+ get hasStarted() {
+ return this._hasStarted;
+ }
+
+ set hasStarted(s) {
+ this._hasStarted = s;
+ }
+} \ No newline at end of file
diff --git a/core/rfb.js b/core/rfb.js
index 970e03f..860a85b 100644
--- a/core/rfb.js
+++ b/core/rfb.js
@@ -25,6 +25,7 @@ import DES from "./des.js";
import KeyTable from "./input/keysym.js";
import XtScancode from "./input/xtscancodes.js";
import { encodings } from "./encodings.js";
+import RSAAESAuthenticationState from "./ra2.js";
import RawDecoder from "./decoders/raw.js";
import CopyRectDecoder from "./decoders/copyrect.js";
@@ -100,6 +101,7 @@ export default class RFB extends EventTargetMixin {
this._rfbInitState = '';
this._rfbAuthScheme = -1;
this._rfbCleanDisconnect = true;
+ this._rfbRSAAESAuthenticationState = null;
// Server capabilities
this._rfbVersion = 0;
@@ -178,6 +180,8 @@ export default class RFB extends EventTargetMixin {
handleMouse: this._handleMouse.bind(this),
handleWheel: this._handleWheel.bind(this),
handleGesture: this._handleGesture.bind(this),
+ handleRSAAESCredentialsRequired: this._handleRSAAESCredentialsRequired.bind(this),
+ handleRSAAESServerVerification: this._handleRSAAESServerVerification.bind(this),
};
// main setup
@@ -378,6 +382,15 @@ export default class RFB extends EventTargetMixin {
this._sock.off('error');
this._sock.off('message');
this._sock.off('open');
+ if (this._rfbRSAAESAuthenticationState !== null) {
+ this._rfbRSAAESAuthenticationState.disconnect();
+ }
+ }
+
+ approveServer() {
+ if (this._rfbRSAAESAuthenticationState !== null) {
+ this._rfbRSAAESAuthenticationState.approveServer();
+ }
}
sendCredentials(creds) {
@@ -1335,6 +1348,8 @@ export default class RFB extends EventTargetMixin {
this._rfbAuthScheme = 22; // XVP
} else if (types.includes(16)) {
this._rfbAuthScheme = 16; // Tight
+ } else if (types.includes(6)) {
+ this._rfbAuthScheme = 6; // RA2ne Auth
} else if (types.includes(2)) {
this._rfbAuthScheme = 2; // VNC Auth
} else if (types.includes(19)) {
@@ -1652,6 +1667,44 @@ export default class RFB extends EventTargetMixin {
return this._fail("No supported sub-auth types!");
}
+ _handleRSAAESCredentialsRequired(event) {
+ this.dispatchEvent(event);
+ }
+
+ _handleRSAAESServerVerification(event) {
+ this.dispatchEvent(event);
+ }
+
+ _negotiateRA2neAuth() {
+ if (this._rfbRSAAESAuthenticationState === null) {
+ this._rfbRSAAESAuthenticationState = new RSAAESAuthenticationState(this._sock, () => this._rfbCredentials);
+ this._rfbRSAAESAuthenticationState.addEventListener(
+ "serververification", this._eventHandlers.handleRSAAESServerVerification);
+ this._rfbRSAAESAuthenticationState.addEventListener(
+ "credentialsrequired", this._eventHandlers.handleRSAAESCredentialsRequired);
+ }
+ this._rfbRSAAESAuthenticationState.checkInternalEvents();
+ if (!this._rfbRSAAESAuthenticationState.hasStarted) {
+ this._rfbRSAAESAuthenticationState.negotiateRA2neAuthAsync()
+ .catch((e) => {
+ if (e.message !== "disconnect normally") {
+ this._fail(e.message);
+ }
+ }).then(() => {
+ this.dispatchEvent(new CustomEvent('securityresult'));
+ this._rfbInitState = "SecurityResult";
+ this._initMsg();
+ }).finally(() => {
+ this._rfbRSAAESAuthenticationState.removeEventListener(
+ "serververification", this._eventHandlers.handleRSAAESServerVerification);
+ this._rfbRSAAESAuthenticationState.removeEventListener(
+ "credentialsrequired", this._eventHandlers.handleRSAAESCredentialsRequired);
+ this._rfbRSAAESAuthenticationState = null;
+ });
+ }
+ return false;
+ }
+
_negotiateAuthentication() {
switch (this._rfbAuthScheme) {
case 1: // no auth
@@ -1677,6 +1730,9 @@ export default class RFB extends EventTargetMixin {
case 129: // TightVNC UNIX Security Type
return this._negotiateTightUnixAuth();
+ case 6: // RA2ne Security Type
+ return this._negotiateRA2neAuth();
+
default:
return this._fail("Unsupported auth scheme (scheme: " +
this._rfbAuthScheme + ")");