Merge branch 'add-ra2ne-security-type' of https://github.com/pdlan/noVNC
This commit is contained in:
commit
eac11d5799
|
@ -1,10 +1,11 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true
|
||||
"es2020": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
"sourceType": "module",
|
||||
"ecmaVersion": 11
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"rules": {
|
||||
|
|
|
@ -841,6 +841,25 @@ select:active {
|
|||
height: 1.3em;
|
||||
}
|
||||
|
||||
/* ----------------------------------------
|
||||
* Key verification Dialog
|
||||
* ----------------------------------------
|
||||
*/
|
||||
|
||||
#noVNC_verify_server_dlg {
|
||||
position: relative;
|
||||
|
||||
transform: translateY(-50px);
|
||||
}
|
||||
#noVNC_verify_server_dlg.noVNC_open {
|
||||
transform: translateY(0);
|
||||
}
|
||||
#noVNC_verify_server_dlg ul {
|
||||
list-style: none;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
/* ----------------------------------------
|
||||
* Password Dialog
|
||||
* ----------------------------------------
|
||||
|
|
36
app/ui.js
36
app/ui.js
|
@ -316,6 +316,10 @@ const UI = {
|
|||
document.getElementById("noVNC_cancel_reconnect_button")
|
||||
.addEventListener('click', UI.cancelReconnect);
|
||||
|
||||
document.getElementById("noVNC_approve_server_button")
|
||||
.addEventListener('click', UI.approveServer);
|
||||
document.getElementById("noVNC_reject_server_button")
|
||||
.addEventListener('click', UI.rejectServer);
|
||||
document.getElementById("noVNC_credentials_button")
|
||||
.addEventListener('click', UI.setCredentials);
|
||||
},
|
||||
|
@ -1030,6 +1034,7 @@ const UI = {
|
|||
credentials: { password: password } });
|
||||
UI.rfb.addEventListener("connect", UI.connectFinished);
|
||||
UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
|
||||
UI.rfb.addEventListener("serververification", UI.serverVerify);
|
||||
UI.rfb.addEventListener("credentialsrequired", UI.credentials);
|
||||
UI.rfb.addEventListener("securityfailure", UI.securityFailed);
|
||||
UI.rfb.addEventListener("capabilities", UI.updatePowerButton);
|
||||
|
@ -1152,6 +1157,37 @@ const UI = {
|
|||
/* ------^-------
|
||||
* /CONNECTION
|
||||
* ==============
|
||||
* SERVER VERIFY
|
||||
* ------v------*/
|
||||
|
||||
async serverVerify(e) {
|
||||
const type = e.detail.type;
|
||||
if (type === 'RSA') {
|
||||
const publickey = e.detail.publickey;
|
||||
let fingerprint = await window.crypto.subtle.digest("SHA-1", publickey);
|
||||
// The same fingerprint format as RealVNC
|
||||
fingerprint = Array.from(new Uint8Array(fingerprint).slice(0, 8)).map(
|
||||
x => x.toString(16).padStart(2, '0')).join('-');
|
||||
document.getElementById('noVNC_verify_server_dlg').classList.add('noVNC_open');
|
||||
document.getElementById('noVNC_fingerprint').innerHTML = fingerprint;
|
||||
}
|
||||
},
|
||||
|
||||
approveServer(e) {
|
||||
e.preventDefault();
|
||||
document.getElementById('noVNC_verify_server_dlg').classList.remove('noVNC_open');
|
||||
UI.rfb.approveServer();
|
||||
},
|
||||
|
||||
rejectServer(e) {
|
||||
e.preventDefault();
|
||||
document.getElementById('noVNC_verify_server_dlg').classList.remove('noVNC_open');
|
||||
UI.disconnect();
|
||||
},
|
||||
|
||||
/* ------^-------
|
||||
* /SERVER VERIFY
|
||||
* ==============
|
||||
* PASSWORD
|
||||
* ------v------*/
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
56
core/rfb.js
56
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 + ")");
|
||||
|
|
|
@ -0,0 +1,357 @@
|
|||
const expect = chai.expect;
|
||||
|
||||
import RFB from '../core/rfb.js';
|
||||
|
||||
import FakeWebSocket from './fake.websocket.js';
|
||||
|
||||
function fakeGetRandomValues(arr) {
|
||||
if (arr.length === 16) {
|
||||
arr.set(new Uint8Array([
|
||||
0x1c, 0x08, 0xfe, 0x21, 0x78, 0xef, 0x4e, 0xf9,
|
||||
0x3f, 0x05, 0xec, 0xea, 0xd4, 0x6b, 0xa5, 0xd5,
|
||||
]));
|
||||
} else {
|
||||
arr.set(new Uint8Array([
|
||||
0xee, 0xe2, 0xf1, 0x5a, 0x3c, 0xa7, 0xbe, 0x95,
|
||||
0x6f, 0x2a, 0x75, 0xfd, 0x62, 0x01, 0xcb, 0xbf,
|
||||
0x43, 0x74, 0xca, 0x47, 0x4d, 0xfb, 0x0f, 0xcf,
|
||||
0x3a, 0x6d, 0x55, 0x6b, 0x59, 0x3a, 0xf6, 0x87,
|
||||
0xcb, 0x03, 0xb7, 0x28, 0x35, 0x7b, 0x15, 0x8e,
|
||||
0xb6, 0xc8, 0x8f, 0x2d, 0x5e, 0x7b, 0x1c, 0x9a,
|
||||
0x32, 0x55, 0xe7, 0x64, 0x36, 0x25, 0x7b, 0xa3,
|
||||
0xe9, 0x4f, 0x6f, 0x97, 0xdc, 0xa4, 0xd4, 0x62,
|
||||
0x6d, 0x7f, 0xab, 0x02, 0x6b, 0x13, 0x56, 0x69,
|
||||
0xfb, 0xd0, 0xd4, 0x13, 0x76, 0xcd, 0x0d, 0xd0,
|
||||
0x1f, 0xd1, 0x0c, 0x63, 0x3a, 0x34, 0x20, 0x6c,
|
||||
0xbb, 0x60, 0x45, 0x82, 0x23, 0xfd, 0x7c, 0x77,
|
||||
0x6d, 0xcc, 0x5e, 0xaa, 0xc3, 0x0c, 0x43, 0xb7,
|
||||
0x8d, 0xc0, 0x27, 0x6e, 0xeb, 0x1d, 0x6c, 0x5f,
|
||||
0xd8, 0x1c, 0x3c, 0x1c, 0x60, 0x2e, 0x82, 0x15,
|
||||
0xfd, 0x2e, 0x5f, 0x3a, 0x15, 0x53, 0x14, 0x70,
|
||||
0x4f, 0xe1, 0x65, 0x68, 0x35, 0x6d, 0xc7, 0x64,
|
||||
0xdb, 0xdd, 0x09, 0x31, 0x4f, 0x7b, 0x6d, 0x6c,
|
||||
0x77, 0x59, 0x5e, 0x1e, 0xfa, 0x4b, 0x06, 0x14,
|
||||
0xbe, 0xdc, 0x9c, 0x3d, 0x7b, 0xed, 0xf3, 0x2b,
|
||||
0x19, 0x26, 0x11, 0x8e, 0x3f, 0xab, 0x73, 0x9a,
|
||||
0x0a, 0x3a, 0xaa, 0x85, 0x06, 0xd5, 0xca, 0x3f,
|
||||
0xc3, 0xe2, 0x33, 0x7f, 0x97, 0x74, 0x98, 0x8f,
|
||||
0x2f, 0xa5, 0xfc, 0x7e, 0xb1, 0x77, 0x71, 0x58,
|
||||
0xf0, 0xbc, 0x04, 0x59, 0xbb, 0xb4, 0xc6, 0xcc,
|
||||
0x0f, 0x06, 0xcd, 0xa2, 0xd5, 0x01, 0x2f, 0xb2,
|
||||
0x22, 0x0b, 0xfc, 0x1e, 0x59, 0x9f, 0xd3, 0x4f,
|
||||
0x30, 0x95, 0xc6, 0x80, 0x0f, 0x69, 0xf3, 0x4a,
|
||||
0xd4, 0x36, 0xb6, 0x5a, 0x0b, 0x16, 0x0d, 0x81,
|
||||
0x31, 0xb0, 0x69, 0xd4, 0x4e,
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
async function fakeGeneratekey() {
|
||||
let key = JSON.parse('{"alg":"RSA-OAEP-256","d":"B7QR2yI8sXjo8vQhJpX9odqqR\
|
||||
6wIuPrTM1B1JJEKVeSrr7OYcc1FRJ52Vap9LIAU-ezigs9QDvWMxknB8motLnG69Wck37nt9_z4s8l\
|
||||
FQp0nROA-oaR92HW34KNL1b2fEVWGI0N86h730MvTJC5O2cmKeMezIG-oNqbbfFyP8AW-WLdDlgZm1\
|
||||
1-FjzhbVpb0Bc7nRSgBPSV-EY6Sl-LuglxDx4LaTdQW7QE_WXoRUt-GYGfTseuFQQK5WeoyX3yBtQy\
|
||||
dpauW6rrgyWdtP4hDFIoZsX6w1i-UMWMMwlIB5FdnUSi26igVGADGpV_vGMP36bv-EHp0bY-Qp0gpI\
|
||||
fLfgQ","dp":"Z1v5UceFfV2bhmbG19eGYb30jFxqoRBq36PKNY7IunMs1keYy0FpLbyGhtgMZ1Ymm\
|
||||
c8wEzGYsCPEP-ykcun_rlyu7YxmcnyC9YQqTqLyqvO-7rUqDvk9TMfdqWFP6heADRhKZmEbmcau6_m\
|
||||
2MwwK9kOkMKWvpqp8_TpJMnAH7zE","dq":"OBacRE15aY3NtCR4cvP5os3sT70JbDdDLHT3IHZM6r\
|
||||
E35CYNpLDia2chm_wnMcYvKFW9zC2ajRZ15i9c_VXQzS7ZlTaQYBFyMt7kVhxMEMFsPv1crD6t3uEI\
|
||||
j0LNuNYyy0jkon_LPZKQFK654CiL-L2YaNXOH4HbHP02dWeVQIE","e":"AQAB","ext":true,"ke\
|
||||
y_ops":["decrypt"],"kty":"RSA","n":"m1c92ZFk9ZI6l_O4YFiNxbv0Ng94SB3yThy1P_mcqr\
|
||||
GDQkRiGVdcTxAk38T9PgLztmspF-6U5TAHO-gSmmW88AC9m6f1Mspps6r7zl-M_OG-TwvGzf3BDz8z\
|
||||
Eg1FPbZV7whO1M4TCAZ0PqwG7qCc6nK1WiAhaKrSpzuPdL1igfNBsX7qu5wgw4ZTTGSLbVC_LfULQ5\
|
||||
FADgFTRXUSaxm1F8C_Lwy6a2e4nTcXilmtN2IHUjHegzm-Tq2HizmR3ARdWJpESYIW5-AXoiqj29tD\
|
||||
rqCmu2WPkB2psVp83IzZfaQNQzjNfvA8GpimkcDCkP5VMRrtKCcG4ZAFnO-A3NBX_Q","p":"2Q_lN\
|
||||
L7vCOBzAppYzCZo3WSh0hX-MOZyPUznks5U2TjmfdNZoL6_FJRiGyyLvwSiZFdEAAvpAyESFfFigng\
|
||||
AqMLSf448nPg15VUGj533CotsEM0WpoEr1JCgqdUbgDAfJQIBcwOmegBqd7lWm7uzEnRCvouB70ybk\
|
||||
JfpdprhkVE","q":"tzTt-F3g2u_3Ctj26Ho9iN_wC_W0lXGzslLt5nLmss8JqdLoDDrijjU-gjeRh\
|
||||
7lgiuHdUc3dorfFKbaMNOjoW3QKqt9oZ1JM0HKeRw0X2PnWW_0WK6DK5ASWDTXbMq2sUZqJvYEyL74\
|
||||
H2Zrt0RPAux7XQLEVgND6ROdXnMJ70O0","qi":"qfl4cXQkz4BNqa2De0-PfdU-8d1w3onnaGqx1D\
|
||||
s2fHzD_SJ4cNghn2TksoT9Qo64b3pUjH9igi2pyEjomk6D12N6FG0e10u7vFKv3W5YqUOgTpYdbcWH\
|
||||
dZ2qZWJU0XQZIrF8jLGTOO4GYP6_9sJ5R7Wk_0MdqQy8qvixWD4zLcY"}');
|
||||
key = await window.crypto.subtle.importKey("jwk", key, {
|
||||
name: "RSA-OAEP",
|
||||
hash: {name: "SHA-256"}
|
||||
}, true, ["decrypt"]);
|
||||
return {privateKey: key};
|
||||
}
|
||||
|
||||
const receiveData = new Uint8Array([
|
||||
// server public key
|
||||
0x00, 0x00, 0x08, 0x00, 0xac, 0x1a, 0xbc, 0x42,
|
||||
0x8a, 0x2a, 0x69, 0x65, 0x54, 0xf8, 0x9a, 0xe6,
|
||||
0x43, 0xaa, 0xf7, 0x27, 0xf6, 0x2a, 0xf8, 0x8f,
|
||||
0x36, 0xd4, 0xae, 0x54, 0x0f, 0x16, 0x28, 0x08,
|
||||
0xc2, 0x5b, 0xca, 0x23, 0xdc, 0x27, 0x88, 0x1a,
|
||||
0x12, 0x82, 0xa8, 0x54, 0xea, 0x00, 0x99, 0x8d,
|
||||
0x02, 0x1d, 0x77, 0x4a, 0xeb, 0xd0, 0x93, 0x40,
|
||||
0x79, 0x86, 0xcb, 0x37, 0xd4, 0xb2, 0xc7, 0xcd,
|
||||
0x93, 0xe1, 0x00, 0x4d, 0x86, 0xff, 0x97, 0x33,
|
||||
0x0c, 0xad, 0x51, 0x47, 0x45, 0x85, 0x56, 0x07,
|
||||
0x65, 0x21, 0x7c, 0x57, 0x6d, 0x68, 0x7d, 0xd7,
|
||||
0x00, 0x43, 0x0c, 0x9d, 0x3b, 0xa1, 0x5a, 0x11,
|
||||
0xed, 0x51, 0x77, 0xf9, 0xd1, 0x5b, 0x33, 0xd7,
|
||||
0x1a, 0xeb, 0x65, 0x57, 0xc0, 0x01, 0x51, 0xff,
|
||||
0x9b, 0x82, 0xb3, 0xeb, 0x82, 0xc2, 0x1f, 0xca,
|
||||
0x47, 0xc0, 0x6a, 0x09, 0xe0, 0xf7, 0xda, 0x39,
|
||||
0x85, 0x12, 0xe7, 0x45, 0x8d, 0xb4, 0x1a, 0xda,
|
||||
0xcb, 0x86, 0x58, 0x52, 0x37, 0x66, 0x9d, 0x8a,
|
||||
0xce, 0xf2, 0x18, 0x78, 0x7d, 0x7f, 0xf0, 0x07,
|
||||
0x94, 0x8e, 0x6b, 0x17, 0xd9, 0x00, 0x2a, 0x3a,
|
||||
0xb9, 0xd4, 0x77, 0xde, 0x70, 0x85, 0xc4, 0x3a,
|
||||
0x62, 0x10, 0x02, 0xee, 0xba, 0xd8, 0xc0, 0x62,
|
||||
0xd0, 0x8e, 0xc1, 0x98, 0x19, 0x8e, 0x39, 0x0f,
|
||||
0x3e, 0x1d, 0x61, 0xb1, 0x93, 0x13, 0x59, 0x39,
|
||||
0xcb, 0x96, 0xf2, 0x17, 0xc9, 0xe1, 0x41, 0xd3,
|
||||
0x20, 0xdd, 0x62, 0x5e, 0x7d, 0x53, 0xd6, 0xb7,
|
||||
0x1d, 0xfe, 0x02, 0x18, 0x1f, 0xe0, 0xef, 0x3d,
|
||||
0x94, 0xe3, 0x0a, 0x9c, 0x59, 0x54, 0xd8, 0x98,
|
||||
0x16, 0x9c, 0x31, 0xda, 0x41, 0x0f, 0x2e, 0x71,
|
||||
0x68, 0xe0, 0xa2, 0x62, 0x3e, 0xe5, 0x25, 0x31,
|
||||
0xcf, 0xfc, 0x67, 0x63, 0xc3, 0xb0, 0xda, 0x3f,
|
||||
0x7b, 0x59, 0xbe, 0x7e, 0x9e, 0xa8, 0xd0, 0x01,
|
||||
0x4f, 0x43, 0x7f, 0x8d, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x00, 0x01,
|
||||
// server random
|
||||
0x01, 0x00, 0x5b, 0x58, 0x2a, 0x96, 0x2d, 0xbb,
|
||||
0x88, 0xec, 0xc3, 0x54, 0x00, 0xf3, 0xbb, 0xbe,
|
||||
0x17, 0xa3, 0x84, 0xd3, 0xef, 0xd8, 0x4a, 0x31,
|
||||
0x09, 0x20, 0xdd, 0xbc, 0x16, 0x9d, 0xc9, 0x5b,
|
||||
0x99, 0x62, 0x86, 0xfe, 0x0b, 0x28, 0x4b, 0xfe,
|
||||
0x5b, 0x56, 0x2d, 0xcb, 0x6e, 0x6f, 0xec, 0xf0,
|
||||
0x53, 0x0c, 0x33, 0x84, 0x93, 0xc9, 0xbf, 0x79,
|
||||
0xde, 0xb3, 0xb9, 0x29, 0x60, 0x78, 0xde, 0xe6,
|
||||
0x1d, 0xa7, 0x89, 0x48, 0x3f, 0xd1, 0x58, 0x66,
|
||||
0x27, 0x9c, 0xd4, 0x6e, 0x72, 0x9c, 0x6e, 0x4a,
|
||||
0xc0, 0x69, 0x79, 0x6f, 0x79, 0x0f, 0x13, 0xc4,
|
||||
0x20, 0xcf, 0xa6, 0xbb, 0xce, 0x18, 0x6d, 0xd5,
|
||||
0x9e, 0xd9, 0x67, 0xbe, 0x61, 0x43, 0x67, 0x11,
|
||||
0x76, 0x2f, 0xfd, 0x78, 0x75, 0x2b, 0x89, 0x35,
|
||||
0xdd, 0x0f, 0x13, 0x7f, 0xee, 0x78, 0xad, 0x32,
|
||||
0x56, 0x21, 0x81, 0x08, 0x1f, 0xcf, 0x4c, 0x29,
|
||||
0xa3, 0xeb, 0x89, 0x2d, 0xbe, 0xba, 0x8d, 0xe4,
|
||||
0x69, 0x28, 0xba, 0x53, 0x82, 0xce, 0x5c, 0xf6,
|
||||
0x5e, 0x5e, 0xa5, 0xb3, 0x88, 0xd8, 0x3d, 0xab,
|
||||
0xf4, 0x24, 0x9e, 0x3f, 0x04, 0xaf, 0xdc, 0x48,
|
||||
0x90, 0x53, 0x37, 0xe6, 0x82, 0x1d, 0xe0, 0x15,
|
||||
0x91, 0xa1, 0xc6, 0xa9, 0x54, 0xe5, 0x2a, 0xb5,
|
||||
0x64, 0x2d, 0x93, 0xc0, 0xc0, 0xe1, 0x0f, 0x6a,
|
||||
0x4b, 0xdb, 0x77, 0xf8, 0x4a, 0x0f, 0x83, 0x36,
|
||||
0xdd, 0x5e, 0x1e, 0xdd, 0x39, 0x65, 0xa2, 0x11,
|
||||
0xc2, 0xcf, 0x56, 0x1e, 0xa1, 0x29, 0xae, 0x11,
|
||||
0x9f, 0x3a, 0x82, 0xc7, 0xbd, 0x89, 0x6e, 0x59,
|
||||
0xb8, 0x59, 0x17, 0xcb, 0x65, 0xa0, 0x4b, 0x4d,
|
||||
0xbe, 0x33, 0x32, 0x85, 0x9c, 0xca, 0x5e, 0x95,
|
||||
0xc2, 0x5a, 0xd0, 0xc9, 0x8b, 0xf1, 0xf5, 0x14,
|
||||
0xcf, 0x76, 0x80, 0xc2, 0x24, 0x0a, 0x39, 0x7e,
|
||||
0x60, 0x64, 0xce, 0xd9, 0xb8, 0xad, 0x24, 0xa8,
|
||||
0xdf, 0xcb,
|
||||
// server hash
|
||||
0x00, 0x14, 0x39, 0x30, 0x66, 0xb5, 0x66, 0x8a,
|
||||
0xcd, 0xb9, 0xda, 0xe0, 0xde, 0xcb, 0xf6, 0x47,
|
||||
0x5f, 0x54, 0x66, 0xe0, 0xbc, 0x49, 0x37, 0x01,
|
||||
0xf2, 0x9e, 0xef, 0xcc, 0xcd, 0x4d, 0x6c, 0x0e,
|
||||
0xc6, 0xab, 0x28, 0xd4, 0x7b, 0x13,
|
||||
// subtype
|
||||
0x00, 0x01, 0x30, 0x2a, 0xc3, 0x0b, 0xc2, 0x1c,
|
||||
0xeb, 0x02, 0x44, 0x92, 0x5d, 0xfd, 0xf9, 0xa7,
|
||||
0x94, 0xd0, 0x19,
|
||||
]);
|
||||
|
||||
const sendData = new Uint8Array([
|
||||
// client public key
|
||||
0x00, 0x00, 0x08, 0x00, 0x9b, 0x57, 0x3d, 0xd9,
|
||||
0x91, 0x64, 0xf5, 0x92, 0x3a, 0x97, 0xf3, 0xb8,
|
||||
0x60, 0x58, 0x8d, 0xc5, 0xbb, 0xf4, 0x36, 0x0f,
|
||||
0x78, 0x48, 0x1d, 0xf2, 0x4e, 0x1c, 0xb5, 0x3f,
|
||||
0xf9, 0x9c, 0xaa, 0xb1, 0x83, 0x42, 0x44, 0x62,
|
||||
0x19, 0x57, 0x5c, 0x4f, 0x10, 0x24, 0xdf, 0xc4,
|
||||
0xfd, 0x3e, 0x02, 0xf3, 0xb6, 0x6b, 0x29, 0x17,
|
||||
0xee, 0x94, 0xe5, 0x30, 0x07, 0x3b, 0xe8, 0x12,
|
||||
0x9a, 0x65, 0xbc, 0xf0, 0x00, 0xbd, 0x9b, 0xa7,
|
||||
0xf5, 0x32, 0xca, 0x69, 0xb3, 0xaa, 0xfb, 0xce,
|
||||
0x5f, 0x8c, 0xfc, 0xe1, 0xbe, 0x4f, 0x0b, 0xc6,
|
||||
0xcd, 0xfd, 0xc1, 0x0f, 0x3f, 0x33, 0x12, 0x0d,
|
||||
0x45, 0x3d, 0xb6, 0x55, 0xef, 0x08, 0x4e, 0xd4,
|
||||
0xce, 0x13, 0x08, 0x06, 0x74, 0x3e, 0xac, 0x06,
|
||||
0xee, 0xa0, 0x9c, 0xea, 0x72, 0xb5, 0x5a, 0x20,
|
||||
0x21, 0x68, 0xaa, 0xd2, 0xa7, 0x3b, 0x8f, 0x74,
|
||||
0xbd, 0x62, 0x81, 0xf3, 0x41, 0xb1, 0x7e, 0xea,
|
||||
0xbb, 0x9c, 0x20, 0xc3, 0x86, 0x53, 0x4c, 0x64,
|
||||
0x8b, 0x6d, 0x50, 0xbf, 0x2d, 0xf5, 0x0b, 0x43,
|
||||
0x91, 0x40, 0x0e, 0x01, 0x53, 0x45, 0x75, 0x12,
|
||||
0x6b, 0x19, 0xb5, 0x17, 0xc0, 0xbf, 0x2f, 0x0c,
|
||||
0xba, 0x6b, 0x67, 0xb8, 0x9d, 0x37, 0x17, 0x8a,
|
||||
0x59, 0xad, 0x37, 0x62, 0x07, 0x52, 0x31, 0xde,
|
||||
0x83, 0x39, 0xbe, 0x4e, 0xad, 0x87, 0x8b, 0x39,
|
||||
0x91, 0xdc, 0x04, 0x5d, 0x58, 0x9a, 0x44, 0x49,
|
||||
0x82, 0x16, 0xe7, 0xe0, 0x17, 0xa2, 0x2a, 0xa3,
|
||||
0xdb, 0xdb, 0x43, 0xae, 0xa0, 0xa6, 0xbb, 0x65,
|
||||
0x8f, 0x90, 0x1d, 0xa9, 0xb1, 0x5a, 0x7c, 0xdc,
|
||||
0x8c, 0xd9, 0x7d, 0xa4, 0x0d, 0x43, 0x38, 0xcd,
|
||||
0x7e, 0xf0, 0x3c, 0x1a, 0x98, 0xa6, 0x91, 0xc0,
|
||||
0xc2, 0x90, 0xfe, 0x55, 0x31, 0x1a, 0xed, 0x28,
|
||||
0x27, 0x06, 0xe1, 0x90, 0x05, 0x9c, 0xef, 0x80,
|
||||
0xdc, 0xd0, 0x57, 0xfd, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x00, 0x01,
|
||||
// client random
|
||||
0x01, 0x00, 0x84, 0x7f, 0x26, 0x54, 0x74, 0xf6,
|
||||
0x47, 0xaf, 0x33, 0x64, 0x0d, 0xa6, 0xe5, 0x30,
|
||||
0xba, 0xe6, 0xe4, 0x8e, 0x50, 0x40, 0x71, 0x1c,
|
||||
0x0e, 0x06, 0x63, 0xf5, 0x07, 0x2a, 0x26, 0x68,
|
||||
0xd6, 0xcf, 0xa6, 0x80, 0x84, 0x5e, 0x64, 0xd4,
|
||||
0x5e, 0x62, 0x31, 0xfe, 0x44, 0x51, 0x0b, 0x7c,
|
||||
0x4d, 0x55, 0xc5, 0x4a, 0x7e, 0x0d, 0x4d, 0x9b,
|
||||
0x84, 0xb4, 0x32, 0x2b, 0x4d, 0x8a, 0x34, 0x8d,
|
||||
0xc8, 0xcf, 0x19, 0x3b, 0x64, 0x82, 0x27, 0x9e,
|
||||
0xa7, 0x70, 0x2a, 0xc1, 0xb8, 0xf3, 0x6a, 0x3a,
|
||||
0xf2, 0x75, 0x6e, 0x1d, 0xeb, 0xb6, 0x70, 0x7a,
|
||||
0x15, 0x18, 0x38, 0x00, 0xb4, 0x4f, 0x55, 0xb5,
|
||||
0xd8, 0x03, 0x4e, 0xb8, 0x53, 0xff, 0x80, 0x62,
|
||||
0xf1, 0x9d, 0x27, 0xe8, 0x2a, 0x3d, 0x98, 0x19,
|
||||
0x32, 0x09, 0x7e, 0x9a, 0xb0, 0xc7, 0x46, 0x23,
|
||||
0x10, 0x85, 0x35, 0x00, 0x96, 0xce, 0xb3, 0x2c,
|
||||
0x84, 0x8d, 0xf4, 0x9e, 0xa8, 0x42, 0x67, 0xed,
|
||||
0x09, 0xa6, 0x09, 0x97, 0xb3, 0x64, 0x26, 0xfb,
|
||||
0x71, 0x11, 0x9b, 0x3f, 0xbb, 0x57, 0xb8, 0x5b,
|
||||
0x2e, 0xc5, 0x2d, 0x8c, 0x5c, 0xf7, 0xef, 0x27,
|
||||
0x25, 0x88, 0x42, 0x45, 0x43, 0xa4, 0xe7, 0xde,
|
||||
0xea, 0xf9, 0x15, 0x7b, 0x5d, 0x66, 0x24, 0xce,
|
||||
0xf7, 0xc8, 0x2f, 0xc5, 0xc0, 0x3d, 0xcd, 0xf2,
|
||||
0x62, 0xfc, 0x1a, 0x5e, 0xec, 0xff, 0xf1, 0x1b,
|
||||
0xc8, 0xdb, 0xc1, 0x0f, 0x54, 0x66, 0x9e, 0xfd,
|
||||
0x99, 0x9b, 0x23, 0x70, 0x62, 0x37, 0x80, 0xad,
|
||||
0x91, 0x6b, 0x84, 0x85, 0x6a, 0x4c, 0x80, 0x9e,
|
||||
0x60, 0x8a, 0x93, 0xa3, 0xc8, 0x8e, 0xc4, 0x4b,
|
||||
0x4d, 0xb4, 0x8e, 0x3e, 0xaf, 0xce, 0xcd, 0x83,
|
||||
0xe5, 0x21, 0x90, 0x95, 0x20, 0x3c, 0x82, 0xb4,
|
||||
0x7c, 0xab, 0x63, 0x9c, 0xae, 0xc3, 0xc9, 0x71,
|
||||
0x1a, 0xec, 0x34, 0x18, 0x47, 0xec, 0x5c, 0x4d,
|
||||
0xed, 0x84,
|
||||
// client hash
|
||||
0x00, 0x14, 0x9c, 0x91, 0x9e, 0x76, 0xcf, 0x1e,
|
||||
0x66, 0x87, 0x5e, 0x29, 0xf1, 0x13, 0x80, 0xea,
|
||||
0x7d, 0xec, 0xae, 0xf9, 0x60, 0x01, 0xd3, 0x6f,
|
||||
0xb7, 0x9e, 0xb2, 0xcd, 0x2d, 0xc8, 0xf8, 0x84,
|
||||
0xb2, 0x9f, 0xc3, 0x7e, 0xb4, 0xbe,
|
||||
// credentials
|
||||
0x00, 0x08, 0x9d, 0xc8, 0x3a, 0xb8, 0x80, 0x4f,
|
||||
0xe3, 0x52, 0xdb, 0x62, 0x9e, 0x97, 0x64, 0x82,
|
||||
0xa8, 0xa1, 0x6b, 0x7e, 0x4d, 0x68, 0x8c, 0x29,
|
||||
0x91, 0x38,
|
||||
]);
|
||||
|
||||
describe('RA2 handshake', function () {
|
||||
let sock;
|
||||
let rfb;
|
||||
let sentData;
|
||||
|
||||
before(() => {
|
||||
FakeWebSocket.replace();
|
||||
sinon.stub(window.crypto, "getRandomValues").callsFake(fakeGetRandomValues);
|
||||
sinon.stub(window.crypto.subtle, "generateKey").callsFake(fakeGeneratekey);
|
||||
});
|
||||
after(() => {
|
||||
FakeWebSocket.restore();
|
||||
window.crypto.getRandomValues.restore();
|
||||
window.crypto.subtle.generateKey.restore();
|
||||
});
|
||||
|
||||
it('should fire the serververification event', function (done) {
|
||||
sentData = new Uint8Array();
|
||||
rfb = new RFB(document.createElement('div'), "ws://example.com");
|
||||
sock = rfb._sock;
|
||||
sock.send = (data) => {
|
||||
let res = new Uint8Array(sentData.length + data.length);
|
||||
res.set(sentData);
|
||||
res.set(data, sentData.length);
|
||||
sentData = res;
|
||||
};
|
||||
rfb._rfbInitState = "Security";
|
||||
rfb._rfbVersion = 3.8;
|
||||
sock._websocket._receiveData(new Uint8Array([1, 6]));
|
||||
rfb.addEventListener("serververification", (e) => {
|
||||
expect(e.detail.publickey).to.eql(receiveData.slice(0, 516));
|
||||
done();
|
||||
});
|
||||
sock._websocket._receiveData(receiveData);
|
||||
});
|
||||
|
||||
it('should handle approveServer and fire the credentialsrequired event', function (done) {
|
||||
rfb.addEventListener("credentialsrequired", (e) => {
|
||||
expect(e.detail.types).to.eql(["password"]);
|
||||
done();
|
||||
});
|
||||
rfb.approveServer();
|
||||
});
|
||||
|
||||
it('should match sendData after sending credentials', function (done) {
|
||||
rfb.addEventListener("securityresult", (event) => {
|
||||
expect(sentData.slice(1)).to.eql(sendData);
|
||||
done();
|
||||
});
|
||||
rfb.sendCredentials({ "password": "123456" });
|
||||
});
|
||||
});
|
19
vnc.html
19
vnc.html
|
@ -274,6 +274,25 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Server Key Verification Dialog -->
|
||||
<div class="noVNC_center noVNC_connect_layer">
|
||||
<div id="noVNC_verify_server_dlg" class="noVNC_panel"><form>
|
||||
<ul>
|
||||
<li id="noVNC_fingerprint_block">
|
||||
<label>Fingerprint:</label>
|
||||
<div id="noVNC_fingerprint"></div>
|
||||
</li>
|
||||
<li>
|
||||
Please verify the identification of the server by its fingerprint before you approve it.
|
||||
</li>
|
||||
<li>
|
||||
<input id="noVNC_approve_server_button" type="submit" value="Approve" class="noVNC_submit">
|
||||
<input id="noVNC_reject_server_button" type="button" value="Reject" class="noVNC_submit">
|
||||
</li>
|
||||
</ul>
|
||||
</form></div>
|
||||
</div>
|
||||
|
||||
<!-- Password Dialog -->
|
||||
<div class="noVNC_center noVNC_connect_layer">
|
||||
<div id="noVNC_credentials_dlg" class="noVNC_panel"><form>
|
||||
|
|
Loading…
Reference in New Issue