Merge branch 'appleremotedesktop' of https://github.com/pauldumais/noVNC

This commit is contained in:
Pierre Ossman 2022-04-05 07:55:14 +02:00
commit 42ec5f3321
5 changed files with 545 additions and 4 deletions

View File

@ -5,7 +5,7 @@
},
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 11
"ecmaVersion": 2020
},
"extends": "eslint:recommended",
"rules": {

View File

@ -26,6 +26,8 @@ import KeyTable from "./input/keysym.js";
import XtScancode from "./input/xtscancodes.js";
import { encodings } from "./encodings.js";
import RSAAESAuthenticationState from "./ra2.js";
import { MD5 } from "./util/md5.js";
import { modPow } from "./util/bigint-mod-arith.js";
import RawDecoder from "./decoders/raw.js";
import CopyRectDecoder from "./decoders/copyrect.js";
@ -1288,13 +1290,13 @@ export default class RFB extends EventTargetMixin {
break;
case "003.003":
case "003.006": // UltraVNC
case "003.889": // Apple Remote Desktop
this._rfbVersion = 3.3;
break;
case "003.007":
this._rfbVersion = 3.7;
break;
case "003.008":
case "003.889": // Apple Remote Desktop
case "004.000": // Intel AMT KVM
case "004.001": // RealVNC 4.6
case "005.000": // RealVNC 5.3
@ -1352,6 +1354,8 @@ export default class RFB extends EventTargetMixin {
this._rfbAuthScheme = 6; // RA2ne Auth
} else if (types.includes(2)) {
this._rfbAuthScheme = 2; // VNC Auth
} else if (types.includes(30)) {
this._rfbAuthScheme = 30; // ARD Auth
} else if (types.includes(19)) {
this._rfbAuthScheme = 19; // VeNCrypt Auth
} else {
@ -1544,6 +1548,105 @@ export default class RFB extends EventTargetMixin {
return true;
}
_negotiateARDAuth() {
if (this._rfbCredentials.username === undefined ||
this._rfbCredentials.password === undefined) {
this.dispatchEvent(new CustomEvent(
"credentialsrequired",
{ detail: { types: ["username", "password"] } }));
return false;
}
if (this._rfbCredentials.ardPublicKey != undefined &&
this._rfbCredentials.ardCredentials != undefined) {
// if the async web crypto is done return the results
this._sock.send(this._rfbCredentials.ardCredentials);
this._sock.send(this._rfbCredentials.ardPublicKey);
this._rfbCredentials.ardCredentials = null;
this._rfbCredentials.ardPublicKey = null;
this._rfbInitState = "SecurityResult";
return true;
}
if (this._sock.rQwait("read ard", 4)) { return false; }
let generator = this._sock.rQshiftBytes(2); // DH base generator value
let keyLength = this._sock.rQshift16();
if (this._sock.rQwait("read ard keylength", keyLength*2, 4)) { return false; }
// read the server values
let prime = this._sock.rQshiftBytes(keyLength); // predetermined prime modulus
let serverPublicKey = this._sock.rQshiftBytes(keyLength); // other party's public key
let clientPrivateKey = window.crypto.getRandomValues(new Uint8Array(keyLength));
let padding = Array.from(window.crypto.getRandomValues(new Uint8Array(64)), byte => String.fromCharCode(65+byte%26)).join('');
this._negotiateARDAuthAsync(generator, keyLength, prime, serverPublicKey, clientPrivateKey, padding);
return false;
}
_modPow(base, exponent, modulus) {
let baseHex = "0x"+Array.from(base, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
let exponentHex = "0x"+Array.from(exponent, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
let modulusHex = "0x"+Array.from(modulus, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
let hexResult = modPow(BigInt(baseHex), BigInt(exponentHex), BigInt(modulusHex)).toString(16);
while (hexResult.length/2<exponent.length || (hexResult.length%2 != 0)) {
hexResult = "0"+hexResult;
}
let bytesResult = [];
for (let c = 0; c < hexResult.length; c += 2) {
bytesResult.push(parseInt(hexResult.substr(c, 2), 16));
}
return bytesResult;
}
async _aesEcbEncrypt(string, key) {
// perform AES-ECB blocks
let keyString = Array.from(key, byte => String.fromCharCode(byte)).join('');
let aesKey = await window.crypto.subtle.importKey("raw", MD5(keyString), {name: "AES-CBC"}, false, ["encrypt"]);
let data = new Uint8Array(string.length);
for (let i = 0; i < string.length; ++i) {
data[i] = string.charCodeAt(i);
}
let encrypted = new Uint8Array(data.length);
for (let i=0;i<data.length;i+=16) {
let block = data.slice(i, i+16);
let encryptedBlock = await window.crypto.subtle.encrypt({name: "AES-CBC", iv: block},
aesKey, new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
);
encrypted.set((new Uint8Array(encryptedBlock)).slice(0, 16), i);
}
return encrypted;
}
async _negotiateARDAuthAsync(generator, keyLength, prime, serverPublicKey, clientPrivateKey, padding) {
// calculate the DH keys
let clientPublicKey = this._modPow(generator, clientPrivateKey, prime);
let sharedKey = this._modPow(serverPublicKey, clientPrivateKey, prime);
let username = encodeUTF8(this._rfbCredentials.username).substring(0, 63);
let password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
let paddedUsername = username + '\0' + padding.substring(0, 63);
let paddedPassword = password + '\0' + padding.substring(0, 63);
let credentials = paddedUsername.substring(0, 64) + paddedPassword.substring(0, 64);
let encrypted = await this._aesEcbEncrypt(credentials, sharedKey);
this._rfbCredentials.ardCredentials = encrypted;
this._rfbCredentials.ardPublicKey = clientPublicKey;
setTimeout(this._initMsg.bind(this), 0);
}
_negotiateTightUnixAuth() {
if (this._rfbCredentials.username === undefined ||
this._rfbCredentials.password === undefined) {
@ -1718,6 +1821,9 @@ export default class RFB extends EventTargetMixin {
case 22: // XVP auth
return this._negotiateXvpAuth();
case 30: // ARD auth
return this._negotiateARDAuth();
case 2: // VNC authentication
return this._negotiateStdVNCAuth();

View File

@ -0,0 +1,283 @@
/*
* bigint-mod-arith implementation:
* https://github.com/juanelas/bigint-mod-arith
*
* Full attribution follows:
*
* -------------------------------------------------------------------------
*
* MIT License
*
* Copyright (c) 2018 Juan Hernández Serrano
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
/**
* Absolute value. abs(a)==a if a>=0. abs(a)==-a if a<0
*
* @param a
*
* @returns The absolute value of a
*/
function abs(a) {
return (a >= 0) ? a : -a;
}
/**
* Returns the bitlength of a number
*
* @param a
* @returns The bit length
*/
function bitLength(a) {
if (typeof a === 'number') {
a = BigInt(a);
}
if (a === 1n) {
return 1;
}
let bits = 1;
do {
bits++;
} while ((a >>= 1n) > 1n);
return bits;
}
/**
* An iterative implementation of the extended euclidean algorithm or extended greatest common divisor algorithm.
* Take positive integers a, b as input, and return a triple (g, x, y), such that ax + by = g = gcd(a, b).
*
* @param a
* @param b
*
* @throws {RangeError}
* This excepction is thrown if a or b are less than 0
*
* @returns A triple (g, x, y), such that ax + by = g = gcd(a, b).
*/
function eGcd(a, b) {
if (typeof a === 'number') {
a = BigInt(a);
}
if (typeof b === 'number') {
b = BigInt(b);
}
if (a <= 0n || b <= 0n) {
throw new RangeError('a and b MUST be > 0'); // a and b MUST be positive
}
let x = 0n;
let y = 1n;
let u = 1n;
let v = 0n;
while (a !== 0n) {
const q = b / a;
const r = b % a;
const m = x - (u * q);
const n = y - (v * q);
b = a;
a = r;
x = u;
y = v;
u = m;
v = n;
}
return {
g: b,
x: x,
y: y
};
}
/**
* Greatest-common divisor of two integers based on the iterative binary algorithm.
*
* @param a
* @param b
*
* @returns The greatest common divisor of a and b
*/
function gcd(a, b) {
let aAbs = (typeof a === 'number') ? BigInt(abs(a)) : abs(a);
let bAbs = (typeof b === 'number') ? BigInt(abs(b)) : abs(b);
if (aAbs === 0n) {
return bAbs;
} else if (bAbs === 0n) {
return aAbs;
}
let shift = 0n;
while (((aAbs | bAbs) & 1n) === 0n) {
aAbs >>= 1n;
bAbs >>= 1n;
shift++;
}
while ((aAbs & 1n) === 0n) {
aAbs >>= 1n;
}
do {
while ((bAbs & 1n) === 0n) {
bAbs >>= 1n;
}
if (aAbs > bAbs) {
const x = aAbs;
aAbs = bAbs;
bAbs = x;
}
bAbs -= aAbs;
} while (bAbs !== 0n);
// rescale
return aAbs << shift;
}
/**
* The least common multiple computed as abs(a*b)/gcd(a,b)
* @param a
* @param b
*
* @returns The least common multiple of a and b
*/
function lcm(a, b) {
if (typeof a === 'number') {
a = BigInt(a);
}
if (typeof b === 'number') {
b = BigInt(b);
}
if (a === 0n && b === 0n) {
return BigInt(0);
}
return abs(a * b) / gcd(a, b);
}
/**
* Maximum. max(a,b)==a if a>=b. max(a,b)==b if a<=b
*
* @param a
* @param b
*
* @returns Maximum of numbers a and b
*/
function max(a, b) {
return (a >= b) ? a : b;
}
/**
* Minimum. min(a,b)==b if a>=b. min(a,b)==a if a<=b
*
* @param a
* @param b
*
* @returns Minimum of numbers a and b
*/
function min(a, b) {
return (a >= b) ? b : a;
}
/**
* Finds the smallest positive element that is congruent to a in modulo n
*
* @remarks
* a and b must be the same type, either number or bigint
*
* @param a - An integer
* @param n - The modulo
*
* @throws {RangeError}
* Excpeption thrown when n is not > 0
*
* @returns A bigint with the smallest positive representation of a modulo n
*/
function toZn(a, n) {
if (typeof a === 'number') {
a = BigInt(a);
}
if (typeof n === 'number') {
n = BigInt(n);
}
if (n <= 0n) {
throw new RangeError('n must be > 0');
}
const aZn = a % n;
return (aZn < 0n) ? aZn + n : aZn;
}
/**
* Modular inverse.
*
* @param a The number to find an inverse for
* @param n The modulo
*
* @throws {RangeError}
* Excpeption thorwn when a does not have inverse modulo n
*
* @returns The inverse modulo n
*/
function modInv(a, n) {
const egcd = eGcd(toZn(a, n), n);
if (egcd.g !== 1n) {
throw new RangeError(`${a.toString()} does not have inverse modulo ${n.toString()}`); // modular inverse does not exist
} else {
return toZn(egcd.x, n);
}
}
/**
* Modular exponentiation b**e mod n. Currently using the right-to-left binary method
*
* @param b base
* @param e exponent
* @param n modulo
*
* @throws {RangeError}
* Excpeption thrown when n is not > 0
*
* @returns b**e mod n
*/
function modPow(b, e, n) {
if (typeof b === 'number') {
b = BigInt(b);
}
if (typeof e === 'number') {
e = BigInt(e);
}
if (typeof n === 'number') {
n = BigInt(n);
}
if (n <= 0n) {
throw new RangeError('n must be > 0');
} else if (n === 1n) {
return 0n;
}
b = toZn(b, n);
if (e < 0n) {
return modInv(modPow(b, abs(e), n), n);
}
let r = 1n;
while (e > 0) {
if ((e % 2n) === 1n) {
r = r * b % n;
}
e = e / 2n;
b = b ** 2n % n;
}
return r;
}
export { abs, bitLength, eGcd, gcd, lcm, max, min, modInv, modPow, toZn };

79
core/util/md5.js Normal file
View File

@ -0,0 +1,79 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2021 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
/*
* Performs MD5 hashing on a string of binary characters, returns an array of bytes
*/
export function MD5(d) {
let r = M(V(Y(X(d), 8 * d.length)));
return r;
}
function M(d) {
let f = new Uint8Array(d.length);
for (let i=0;i<d.length;i++) {
f[i] = d.charCodeAt(i);
}
return f;
}
function X(d) {
let r = Array(d.length >> 2);
for (let m = 0; m < r.length; m++) r[m] = 0;
for (let m = 0; m < 8 * d.length; m += 8) r[m >> 5] |= (255 & d.charCodeAt(m / 8)) << m % 32;
return r;
}
function V(d) {
let r = "";
for (let m = 0; m < 32 * d.length; m += 8) r += String.fromCharCode(d[m >> 5] >>> m % 32 & 255);
return r;
}
function Y(d, g) {
d[g >> 5] |= 128 << g % 32, d[14 + (g + 64 >>> 9 << 4)] = g;
let m = 1732584193, f = -271733879, r = -1732584194, i = 271733878;
for (let n = 0; n < d.length; n += 16) {
let h = m,
t = f,
g = r,
e = i;
f = ii(f = ii(f = ii(f = ii(f = hh(f = hh(f = hh(f = hh(f = gg(f = gg(f = gg(f = gg(f = ff(f = ff(f = ff(f = ff(f, r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 0], 7, -680876936), f, r, d[n + 1], 12, -389564586), m, f, d[n + 2], 17, 606105819), i, m, d[n + 3], 22, -1044525330), r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 4], 7, -176418897), f, r, d[n + 5], 12, 1200080426), m, f, d[n + 6], 17, -1473231341), i, m, d[n + 7], 22, -45705983), r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 8], 7, 1770035416), f, r, d[n + 9], 12, -1958414417), m, f, d[n + 10], 17, -42063), i, m, d[n + 11], 22, -1990404162), r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 12], 7, 1804603682), f, r, d[n + 13], 12, -40341101), m, f, d[n + 14], 17, -1502002290), i, m, d[n + 15], 22, 1236535329), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 1], 5, -165796510), f, r, d[n + 6], 9, -1069501632), m, f, d[n + 11], 14, 643717713), i, m, d[n + 0], 20, -373897302), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 5], 5, -701558691), f, r, d[n + 10], 9, 38016083), m, f, d[n + 15], 14, -660478335), i, m, d[n + 4], 20, -405537848), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 9], 5, 568446438), f, r, d[n + 14], 9, -1019803690), m, f, d[n + 3], 14, -187363961), i, m, d[n + 8], 20, 1163531501), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 13], 5, -1444681467), f, r, d[n + 2], 9, -51403784), m, f, d[n + 7], 14, 1735328473), i, m, d[n + 12], 20, -1926607734), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 5], 4, -378558), f, r, d[n + 8], 11, -2022574463), m, f, d[n + 11], 16, 1839030562), i, m, d[n + 14], 23, -35309556), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 1], 4, -1530992060), f, r, d[n + 4], 11, 1272893353), m, f, d[n + 7], 16, -155497632), i, m, d[n + 10], 23, -1094730640), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 13], 4, 681279174), f, r, d[n + 0], 11, -358537222), m, f, d[n + 3], 16, -722521979), i, m, d[n + 6], 23, 76029189), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 9], 4, -640364487), f, r, d[n + 12], 11, -421815835), m, f, d[n + 15], 16, 530742520), i, m, d[n + 2], 23, -995338651), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 0], 6, -198630844), f, r, d[n + 7], 10, 1126891415), m, f, d[n + 14], 15, -1416354905), i, m, d[n + 5], 21, -57434055), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 12], 6, 1700485571), f, r, d[n + 3], 10, -1894986606), m, f, d[n + 10], 15, -1051523), i, m, d[n + 1], 21, -2054922799), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 8], 6, 1873313359), f, r, d[n + 15], 10, -30611744), m, f, d[n + 6], 15, -1560198380), i, m, d[n + 13], 21, 1309151649), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 4], 6, -145523070), f, r, d[n + 11], 10, -1120210379), m, f, d[n + 2], 15, 718787259), i, m, d[n + 9], 21, -343485551), m = add(m, h), f = add(f, t), r = add(r, g), i = add(i, e);
}
return Array(m, f, r, i);
}
function cmn(d, g, m, f, r, i) {
return add(rol(add(add(g, d), add(f, i)), r), m);
}
function ff(d, g, m, f, r, i, n) {
return cmn(g & m | ~g & f, d, g, r, i, n);
}
function gg(d, g, m, f, r, i, n) {
return cmn(g & f | m & ~f, d, g, r, i, n);
}
function hh(d, g, m, f, r, i, n) {
return cmn(g ^ m ^ f, d, g, r, i, n);
}
function ii(d, g, m, f, r, i, n) {
return cmn(m ^ (g | ~f), d, g, r, i, n);
}
function add(d, g) {
let m = (65535 & d) + (65535 & g);
return (d >> 16) + (g >> 16) + (m >> 16) << 16 | 65535 & m;
}
function rol(d, g) {
return d << g | d >>> 32 - g;
}

View File

@ -1048,9 +1048,9 @@ describe('Remote Frame Buffer Protocol Client', function () {
expect(client._rfbVersion).to.equal(3.3);
});
it('should interpret version 003.889 as version 3.3', function () {
it('should interpret version 003.889 as version 3.8', function () {
sendVer('003.889', client);
expect(client._rfbVersion).to.equal(3.3);
expect(client._rfbVersion).to.equal(3.8);
});
it('should interpret version 003.007 as version 3.7', function () {
@ -1273,6 +1273,79 @@ describe('Remote Frame Buffer Protocol Client', function () {
});
});
describe('ARD Authentication (type 30) Handler', function () {
beforeEach(function () {
client._rfbInitState = 'Security';
client._rfbVersion = 3.8;
});
it('should fire the credentialsrequired event if all credentials are missing', function () {
const spy = sinon.spy();
client.addEventListener("credentialsrequired", spy);
client._rfbCredentials = {};
sendSecurity(30, client);
expect(client._rfbCredentials).to.be.empty;
expect(spy).to.have.been.calledOnce;
expect(spy.args[0][0].detail.types).to.have.members(["username", "password"]);
});
it('should fire the credentialsrequired event if some credentials are missing', function () {
const spy = sinon.spy();
client.addEventListener("credentialsrequired", spy);
client._rfbCredentials = { password: 'password'};
sendSecurity(30, client);
expect(spy).to.have.been.calledOnce;
expect(spy.args[0][0].detail.types).to.have.members(["username", "password"]);
});
it('should return properly encrypted credentials and public key', async function () {
client._rfbCredentials = { username: 'user',
password: 'password' };
sendSecurity(30, client);
expect(client._sock).to.have.sent([30]);
function byteArray(length) {
return Array.from(new Uint8Array(length).keys());
}
let generator = [127, 255];
let prime = byteArray(128);
let serverPrivateKey = byteArray(128);
let serverPublicKey = client._modPow(generator, serverPrivateKey, prime);
let clientPrivateKey = byteArray(128);
let clientPublicKey = client._modPow(generator, clientPrivateKey, prime);
let padding = Array.from(byteArray(64), byte => String.fromCharCode(65+byte%26)).join('');
await client._negotiateARDAuthAsync(generator, 128, prime, serverPublicKey, clientPrivateKey, padding);
client._negotiateARDAuth();
expect(client._rfbInitState).to.equal('SecurityResult');
let expectEncrypted = new Uint8Array([
232, 234, 159, 162, 170, 180, 138, 104, 164, 49, 53, 96, 20, 36, 21, 15,
217, 219, 107, 173, 196, 60, 96, 142, 215, 71, 13, 185, 185, 47, 5, 175,
151, 30, 194, 55, 173, 214, 141, 161, 36, 138, 146, 3, 178, 89, 43, 248,
131, 134, 205, 174, 9, 150, 171, 74, 222, 201, 20, 2, 30, 168, 162, 123,
46, 86, 81, 221, 44, 211, 180, 247, 221, 61, 95, 155, 157, 241, 76, 76,
49, 217, 234, 75, 147, 237, 199, 159, 93, 140, 191, 174, 52, 90, 133, 58,
243, 81, 112, 182, 64, 62, 149, 7, 151, 28, 36, 161, 247, 247, 36, 96,
230, 95, 58, 207, 46, 183, 100, 139, 143, 155, 224, 43, 219, 3, 71, 139]);
let output = new Uint8Array(256);
output.set(expectEncrypted, 0);
output.set(clientPublicKey, 128);
expect(client._sock).to.have.sent(output);
});
});
describe('XVP Authentication (type 22) Handler', function () {
beforeEach(function () {
client._rfbInitState = 'Security';