summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPierre Ossman <ossman@cendio.se>2023-01-20 16:52:32 +0100
committerPierre Ossman <ossman@cendio.se>2023-01-20 16:52:32 +0100
commita0e6e7b1d82b105889c9c092692218a786b55f27 (patch)
tree8ff3d737bec82b303d950161b68d0188686366d2
parent90455eef0692d2e35276fd31286114d0955016b0 (diff)
parentf974b7313762f791ca8376def9525100258ebf46 (diff)
downloadnovnc-a0e6e7b1d82b105889c9c092692218a786b55f27.tar.gz
Merge branch 'crypto-cleanup-fallback' of https://github.com/pdlan/noVNC
-rw-r--r--core/crypto/aes.js178
-rw-r--r--core/crypto/bigint.js34
-rw-r--r--core/crypto/crypto.js90
-rw-r--r--core/crypto/des.js (renamed from core/des.js)72
-rw-r--r--core/crypto/dh.js55
-rw-r--r--core/crypto/md5.js (renamed from core/util/md5.js)13
-rw-r--r--core/crypto/rsa.js132
-rw-r--r--core/ra2.js327
-rw-r--r--core/rfb.js122
-rw-r--r--tests/test.rfb.js53
10 files changed, 663 insertions, 413 deletions
diff --git a/core/crypto/aes.js b/core/crypto/aes.js
new file mode 100644
index 0000000..e6aaea7
--- /dev/null
+++ b/core/crypto/aes.js
@@ -0,0 +1,178 @@
+export class AESECBCipher {
+ constructor() {
+ this._key = null;
+ }
+
+ get algorithm() {
+ return { name: "AES-ECB" };
+ }
+
+ static async importKey(key, _algorithm, extractable, keyUsages) {
+ const cipher = new AESECBCipher;
+ await cipher._importKey(key, extractable, keyUsages);
+ return cipher;
+ }
+
+ async _importKey(key, extractable, keyUsages) {
+ this._key = await window.crypto.subtle.importKey(
+ "raw", key, {name: "AES-CBC"}, extractable, keyUsages);
+ }
+
+ async encrypt(_algorithm, plaintext) {
+ const x = new Uint8Array(plaintext);
+ if (x.length % 16 !== 0 || this._key === null) {
+ return null;
+ }
+ const n = x.length / 16;
+ for (let i = 0; i < n; i++) {
+ const y = new Uint8Array(await window.crypto.subtle.encrypt({
+ name: "AES-CBC",
+ iv: new Uint8Array(16),
+ }, this._key, x.slice(i * 16, i * 16 + 16))).slice(0, 16);
+ x.set(y, i * 16);
+ }
+ return x;
+ }
+}
+
+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]);
+ }
+
+ get algorithm() {
+ return { name: "AES-EAX" };
+ }
+
+ 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;
+ }
+
+ static async importKey(key, _algorithm, _extractable, _keyUsages) {
+ const cipher = new AESEAXCipher;
+ await cipher._importKey(key);
+ return cipher;
+ }
+
+ async _importKey(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"]);
+ await this._initCMAC();
+ }
+
+ async encrypt(algorithm, message) {
+ const ad = algorithm.additionalData;
+ const nonce = algorithm.iv;
+ const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
+ const encrypted = await this._encryptCTR(message, nCMAC);
+ const adCMAC = await this._computeCMAC(ad, 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(algorithm, data) {
+ const encrypted = data.slice(0, data.length - 16);
+ const ad = algorithm.additionalData;
+ const nonce = algorithm.iv;
+ const mac = data.slice(data.length - 16);
+ const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
+ const adCMAC = await this._computeCMAC(ad, 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;
+ }
+}
diff --git a/core/crypto/bigint.js b/core/crypto/bigint.js
new file mode 100644
index 0000000..d344326
--- /dev/null
+++ b/core/crypto/bigint.js
@@ -0,0 +1,34 @@
+export function modPow(b, e, m) {
+ let r = 1n;
+ b = b % m;
+ while (e > 0n) {
+ if ((e & 1n) === 1n) {
+ r = (r * b) % m;
+ }
+ e = e >> 1n;
+ b = (b * b) % m;
+ }
+ return r;
+}
+
+export function bigIntToU8Array(bigint, padLength=0) {
+ let hex = bigint.toString(16);
+ if (padLength === 0) {
+ padLength = Math.ceil(hex.length / 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;
+}
+
+export function u8ArrayToBigInt(arr) {
+ let hex = '0x';
+ for (let i = 0; i < arr.length; i++) {
+ hex += arr[i].toString(16).padStart(2, '0');
+ }
+ return BigInt(hex);
+}
diff --git a/core/crypto/crypto.js b/core/crypto/crypto.js
new file mode 100644
index 0000000..cc17da2
--- /dev/null
+++ b/core/crypto/crypto.js
@@ -0,0 +1,90 @@
+import { AESECBCipher, AESEAXCipher } from "./aes.js";
+import { DESCBCCipher, DESECBCipher } from "./des.js";
+import { RSACipher } from "./rsa.js";
+import { DHCipher } from "./dh.js";
+import { MD5 } from "./md5.js";
+
+// A single interface for the cryptographic algorithms not supported by SubtleCrypto.
+// Both synchronous and asynchronous implmentations are allowed.
+class LegacyCrypto {
+ constructor() {
+ this._algorithms = {
+ "AES-ECB": AESECBCipher,
+ "AES-EAX": AESEAXCipher,
+ "DES-ECB": DESECBCipher,
+ "DES-CBC": DESCBCCipher,
+ "RSA-PKCS1-v1_5": RSACipher,
+ "DH": DHCipher,
+ "MD5": MD5,
+ };
+ }
+
+ encrypt(algorithm, key, data) {
+ if (key.algorithm.name !== algorithm.name) {
+ throw new Error("algorithm does not match");
+ }
+ if (typeof key.encrypt !== "function") {
+ throw new Error("key does not support encryption");
+ }
+ return key.encrypt(algorithm, data);
+ }
+
+ decrypt(algorithm, key, data) {
+ if (key.algorithm.name !== algorithm.name) {
+ throw new Error("algorithm does not match");
+ }
+ if (typeof key.decrypt !== "function") {
+ throw new Error("key does not support encryption");
+ }
+ return key.decrypt(algorithm, data);
+ }
+
+ importKey(format, keyData, algorithm, extractable, keyUsages) {
+ if (format !== "raw") {
+ throw new Error("key format is not supported");
+ }
+ const alg = this._algorithms[algorithm.name];
+ if (typeof alg === "undefined" || typeof alg.importKey !== "function") {
+ throw new Error("algorithm is not supported");
+ }
+ return alg.importKey(keyData, algorithm, extractable, keyUsages);
+ }
+
+ generateKey(algorithm, extractable, keyUsages) {
+ const alg = this._algorithms[algorithm.name];
+ if (typeof alg === "undefined" || typeof alg.generateKey !== "function") {
+ throw new Error("algorithm is not supported");
+ }
+ return alg.generateKey(algorithm, extractable, keyUsages);
+ }
+
+ exportKey(format, key) {
+ if (format !== "raw") {
+ throw new Error("key format is not supported");
+ }
+ if (typeof key.exportKey !== "function") {
+ throw new Error("key does not support exportKey");
+ }
+ return key.exportKey();
+ }
+
+ digest(algorithm, data) {
+ const alg = this._algorithms[algorithm];
+ if (typeof alg !== "function") {
+ throw new Error("algorithm is not supported");
+ }
+ return alg(data);
+ }
+
+ deriveBits(algorithm, key, length) {
+ if (key.algorithm.name !== algorithm.name) {
+ throw new Error("algorithm does not match");
+ }
+ if (typeof key.deriveBits !== "function") {
+ throw new Error("key does not support deriveBits");
+ }
+ return key.deriveBits(algorithm, length);
+ }
+}
+
+export default new LegacyCrypto;
diff --git a/core/des.js b/core/crypto/des.js
index ba1ebde..8dab31f 100644
--- a/core/des.js
+++ b/core/crypto/des.js
@@ -128,7 +128,7 @@ const SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d,
/* eslint-enable comma-spacing */
-export default class DES {
+class DES {
constructor(password) {
this.keys = [];
@@ -258,9 +258,73 @@ export default class DES {
}
return b;
}
+}
+
+export class DESECBCipher {
+ constructor() {
+ this._cipher = null;
+ }
+
+ get algorithm() {
+ return { name: "DES-ECB" };
+ }
+
+ static importKey(key, _algorithm, _extractable, _keyUsages) {
+ const cipher = new DESECBCipher;
+ cipher._importKey(key);
+ return cipher;
+ }
- // Encrypt 16 bytes of text using passwd as key
- encrypt(t) {
- return this.enc8(t.slice(0, 8)).concat(this.enc8(t.slice(8, 16)));
+ _importKey(key, _extractable, _keyUsages) {
+ this._cipher = new DES(key);
+ }
+
+ encrypt(_algorithm, plaintext) {
+ const x = new Uint8Array(plaintext);
+ if (x.length % 8 !== 0 || this._cipher === null) {
+ return null;
+ }
+ const n = x.length / 8;
+ for (let i = 0; i < n; i++) {
+ x.set(this._cipher.enc8(x.slice(i * 8, i * 8 + 8)), i * 8);
+ }
+ return x;
+ }
+}
+
+export class DESCBCCipher {
+ constructor() {
+ this._cipher = null;
+ }
+
+ get algorithm() {
+ return { name: "DES-CBC" };
+ }
+
+ static importKey(key, _algorithm, _extractable, _keyUsages) {
+ const cipher = new DESCBCCipher;
+ cipher._importKey(key);
+ return cipher;
+ }
+
+ _importKey(key) {
+ this._cipher = new DES(key);
+ }
+
+ encrypt(algorithm, plaintext) {
+ const x = new Uint8Array(plaintext);
+ let y = new Uint8Array(algorithm.iv);
+ if (x.length % 8 !== 0 || this._cipher === null) {
+ return null;
+ }
+ const n = x.length / 8;
+ for (let i = 0; i < n; i++) {
+ for (let j = 0; j < 8; j++) {
+ y[j] ^= plaintext[i * 8 + j];
+ }
+ y = this._cipher.enc8(y);
+ x.set(y, i * 8);
+ }
+ return x;
}
}
diff --git a/core/crypto/dh.js b/core/crypto/dh.js
new file mode 100644
index 0000000..bd705d9
--- /dev/null
+++ b/core/crypto/dh.js
@@ -0,0 +1,55 @@
+import { modPow, bigIntToU8Array, u8ArrayToBigInt } from "./bigint.js";
+
+class DHPublicKey {
+ constructor(key) {
+ this._key = key;
+ }
+
+ get algorithm() {
+ return { name: "DH" };
+ }
+
+ exportKey() {
+ return this._key;
+ }
+}
+
+export class DHCipher {
+ constructor() {
+ this._g = null;
+ this._p = null;
+ this._gBigInt = null;
+ this._pBigInt = null;
+ this._privateKey = null;
+ }
+
+ get algorithm() {
+ return { name: "DH" };
+ }
+
+ static generateKey(algorithm, _extractable) {
+ const cipher = new DHCipher;
+ cipher._generateKey(algorithm);
+ return { privateKey: cipher, publicKey: new DHPublicKey(cipher._publicKey) };
+ }
+
+ _generateKey(algorithm) {
+ const g = algorithm.g;
+ const p = algorithm.p;
+ this._keyBytes = p.length;
+ this._gBigInt = u8ArrayToBigInt(g);
+ this._pBigInt = u8ArrayToBigInt(p);
+ this._privateKey = window.crypto.getRandomValues(new Uint8Array(this._keyBytes));
+ this._privateKeyBigInt = u8ArrayToBigInt(this._privateKey);
+ this._publicKey = bigIntToU8Array(modPow(
+ this._gBigInt, this._privateKeyBigInt, this._pBigInt), this._keyBytes);
+ }
+
+ deriveBits(algorithm, length) {
+ const bytes = Math.ceil(length / 8);
+ const pkey = new Uint8Array(algorithm.public);
+ const len = bytes > this._keyBytes ? bytes : this._keyBytes;
+ const secret = modPow(u8ArrayToBigInt(pkey), this._privateKeyBigInt, this._pBigInt);
+ return bigIntToU8Array(secret, len).slice(0, len);
+ }
+}
diff --git a/core/util/md5.js b/core/crypto/md5.js
index 49762ef..fcfefff 100644
--- a/core/util/md5.js
+++ b/core/crypto/md5.js
@@ -7,12 +7,15 @@
*/
/*
- * Performs MD5 hashing on a string of binary characters, returns an array of bytes
+ * Performs MD5 hashing on an array of bytes, returns an array of bytes
*/
-export function MD5(d) {
- let r = M(V(Y(X(d), 8 * d.length)));
- return r;
+export async function MD5(d) {
+ let s = "";
+ for (let i = 0; i < d.length; i++) {
+ s += String.fromCharCode(d[i]);
+ }
+ return M(V(Y(X(s), 8 * s.length)));
}
function M(d) {
@@ -76,4 +79,4 @@ function add(d, g) {
function rol(d, g) {
return d << g | d >>> 32 - g;
-} \ No newline at end of file
+}
diff --git a/core/crypto/rsa.js b/core/crypto/rsa.js
new file mode 100644
index 0000000..68e8e86
--- /dev/null
+++ b/core/crypto/rsa.js
@@ -0,0 +1,132 @@
+import Base64 from "../base64.js";
+import { modPow, bigIntToU8Array, u8ArrayToBigInt } from "./bigint.js";
+
+export class RSACipher {
+ constructor() {
+ this._keyLength = 0;
+ this._keyBytes = 0;
+ this._n = null;
+ this._e = null;
+ this._d = null;
+ this._nBigInt = null;
+ this._eBigInt = null;
+ this._dBigInt = null;
+ this._extractable = false;
+ }
+
+ get algorithm() {
+ return { name: "RSA-PKCS1-v1_5" };
+ }
+
+ _base64urlDecode(data) {
+ data = data.replace(/-/g, "+").replace(/_/g, "/");
+ data = data.padEnd(Math.ceil(data.length / 4) * 4, "=");
+ return Base64.decode(data);
+ }
+
+ _padArray(arr, length) {
+ const res = new Uint8Array(length);
+ res.set(arr, length - arr.length);
+ return res;
+ }
+
+ static async generateKey(algorithm, extractable, _keyUsages) {
+ const cipher = new RSACipher;
+ await cipher._generateKey(algorithm, extractable);
+ return { privateKey: cipher };
+ }
+
+ async _generateKey(algorithm, extractable) {
+ this._keyLength = algorithm.modulusLength;
+ this._keyBytes = Math.ceil(this._keyLength / 8);
+ const key = await window.crypto.subtle.generateKey(
+ {
+ name: "RSA-OAEP",
+ modulusLength: algorithm.modulusLength,
+ publicExponent: algorithm.publicExponent,
+ hash: {name: "SHA-256"},
+ },
+ true, ["encrypt", "decrypt"]);
+ const privateKey = await window.crypto.subtle.exportKey("jwk", key.privateKey);
+ this._n = this._padArray(this._base64urlDecode(privateKey.n), this._keyBytes);
+ this._nBigInt = u8ArrayToBigInt(this._n);
+ this._e = this._padArray(this._base64urlDecode(privateKey.e), this._keyBytes);
+ this._eBigInt = u8ArrayToBigInt(this._e);
+ this._d = this._padArray(this._base64urlDecode(privateKey.d), this._keyBytes);
+ this._dBigInt = u8ArrayToBigInt(this._d);
+ this._extractable = extractable;
+ }
+
+ static async importKey(key, _algorithm, extractable, keyUsages) {
+ if (keyUsages.length !== 1 || keyUsages[0] !== "encrypt") {
+ throw new Error("only support importing RSA public key");
+ }
+ const cipher = new RSACipher;
+ await cipher._importKey(key, extractable);
+ return cipher;
+ }
+
+ async _importKey(key, extractable) {
+ const n = key.n;
+ const e = key.e;
+ if (n.length !== e.length) {
+ throw new Error("the sizes of modulus and public exponent do not match");
+ }
+ this._keyBytes = n.length;
+ this._keyLength = this._keyBytes * 8;
+ this._n = new Uint8Array(this._keyBytes);
+ this._e = new Uint8Array(this._keyBytes);
+ this._n.set(n);
+ this._e.set(e);
+ this._nBigInt = u8ArrayToBigInt(this._n);
+ this._eBigInt = u8ArrayToBigInt(this._e);
+ this._extractable = extractable;
+ }
+
+ async encrypt(_algorithm, 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 = u8ArrayToBigInt(em);
+ const c = modPow(emBigInt, this._eBigInt, this._nBigInt);
+ return bigIntToU8Array(c, this._keyBytes);
+ }
+
+ async decrypt(_algorithm, message) {
+ if (message.length !== this._keyBytes) {
+ return null;
+ }
+ const msgBigInt = u8ArrayToBigInt(message);
+ const emBigInt = modPow(msgBigInt, this._dBigInt, this._nBigInt);
+ const em = 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);
+ }
+
+ async exportKey() {
+ if (!this._extractable) {
+ throw new Error("key is not extractable");
+ }
+ return { n: this._n, e: this._e, d: this._d };
+ }
+}
diff --git a/core/ra2.js b/core/ra2.js
index 81a8a89..647aea2 100644
--- a/core/ra2.js
+++ b/core/ra2.js
@@ -1,146 +1,25 @@
-import Base64 from './base64.js';
import { encodeUTF8 } from './util/strings.js';
import EventTargetMixin from './util/eventtarget.js';
+import legacyCrypto from './crypto/crypto.js';
-export class AESEAXCipher {
+class RA2Cipher {
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._cipher = null;
this._counter = new Uint8Array(16);
}
async setKey(key) {
- await this._cipher.setKey(key);
+ this._cipher = await legacyCrypto.importKey(
+ "raw", key, { name: "AES-EAX" }, false, ["encrypt, decrypt"]);
}
async makeMessage(message) {
const ad = new Uint8Array([(message.length & 0xff00) >>> 8, message.length & 0xff]);
- const encrypted = await this._cipher.encrypt(message, ad, this._counter);
+ const encrypted = await legacyCrypto.encrypt({
+ name: "AES-EAX",
+ iv: this._counter,
+ additionalData: ad,
+ }, this._cipher, message);
for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
const res = new Uint8Array(message.length + 2 + 16);
res.set(ad);
@@ -148,164 +27,18 @@ export class RA2Cipher {
return res;
}
- async receiveMessage(length, encrypted, mac) {
+ async receiveMessage(length, encrypted) {
const ad = new Uint8Array([(length & 0xff00) >>> 8, length & 0xff]);
- const res = await this._cipher.decrypt(encrypted, ad, this._counter, mac);
+ const res = await legacyCrypto.decrypt({
+ name: "AES-EAX",
+ iv: this._counter,
+ additionalData: ad,
+ }, this._cipher, encrypted);
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();
@@ -417,8 +150,8 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
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 serverRSACipher = await legacyCrypto.importKey(
+ "raw", { n: serverN, e: serverE }, { name: "RSA-PKCS1-v1_5" }, false, ["encrypt"]);
const serverPublickey = new Uint8Array(4 + serverKeyBytes * 2);
serverPublickey.set(serverKeyLengthBuffer);
serverPublickey.set(serverN, 4);
@@ -433,10 +166,14 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
// 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 clientRSACipher = (await legacyCrypto.generateKey({
+ name: "RSA-PKCS1-v1_5",
+ modulusLength: clientKeyLength,
+ publicExponent: new Uint8Array([1, 0, 1]),
+ }, true, ["encrypt"])).privateKey;
+ const clientExportedRSAKey = await legacyCrypto.exportKey("raw", clientRSACipher);
+ const clientN = clientExportedRSAKey.n;
+ const clientE = clientExportedRSAKey.e;
const clientPublicKey = new Uint8Array(4 + clientKeyBytes * 2);
clientPublicKey[0] = (clientKeyLength & 0xff000000) >>> 24;
clientPublicKey[1] = (clientKeyLength & 0xff0000) >>> 16;
@@ -449,7 +186,8 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
// 3: Send client random
const clientRandom = new Uint8Array(16);
window.crypto.getRandomValues(clientRandom);
- const clientEncryptedRandom = serverRSACipher.encrypt(clientRandom);
+ const clientEncryptedRandom = await legacyCrypto.encrypt(
+ { name: "RSA-PKCS1-v1_5" }, serverRSACipher, clientRandom);
const clientRandomMessage = new Uint8Array(2 + serverKeyBytes);
clientRandomMessage[0] = (serverKeyBytes & 0xff00) >>> 8;
clientRandomMessage[1] = serverKeyBytes & 0xff;
@@ -462,7 +200,8 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
throw new Error("RA2: wrong encrypted message length");
}
const serverEncryptedRandom = this._sock.rQshiftBytes(clientKeyBytes);
- const serverRandom = clientRSACipher.decrypt(serverEncryptedRandom);
+ const serverRandom = await legacyCrypto.decrypt(
+ { name: "RSA-PKCS1-v1_5" }, clientRSACipher, serverEncryptedRandom);
if (serverRandom === null || serverRandom.length !== 16) {
throw new Error("RA2: corrupted server encrypted random");
}
@@ -500,7 +239,7 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
throw new Error("RA2: wrong server hash");
}
const serverHashReceived = await serverCipher.receiveMessage(
- 20, this._sock.rQshiftBytes(20), this._sock.rQshiftBytes(16));
+ 20, this._sock.rQshiftBytes(20 + 16));
if (serverHashReceived === null) {
throw new Error("RA2: failed to authenticate the message");
}
@@ -516,7 +255,7 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
throw new Error("RA2: wrong subtype");
}
let subtype = (await serverCipher.receiveMessage(
- 1, this._sock.rQshiftBytes(1), this._sock.rQshiftBytes(16)));
+ 1, this._sock.rQshiftBytes(1 + 16)));
if (subtype === null) {
throw new Error("RA2: failed to authenticate the message");
}
@@ -564,4 +303,4 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
set hasStarted(s) {
this._hasStarted = s;
}
-} \ No newline at end of file
+}
diff --git a/core/rfb.js b/core/rfb.js
index 6afd7c6..e573cd4 100644
--- a/core/rfb.js
+++ b/core/rfb.js
@@ -21,12 +21,11 @@ import Keyboard from "./input/keyboard.js";
import GestureHandler from "./input/gesturehandler.js";
import Cursor from "./util/cursor.js";
import Websock from "./websock.js";
-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 { MD5 } from "./util/md5.js";
+import legacyCrypto from "./crypto/crypto.js";
import RawDecoder from "./decoders/raw.js";
import CopyRectDecoder from "./decoders/copyrect.js";
@@ -1681,77 +1680,35 @@ export default class RFB extends EventTargetMixin {
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);
+ let clientKey = legacyCrypto.generateKey(
+ { name: "DH", g: generator, p: prime }, false, ["deriveBits"]);
+ this._negotiateARDAuthAsync(keyLength, serverPublicKey, clientKey);
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 b = BigInt(baseHex);
- let e = BigInt(exponentHex);
- let m = BigInt(modulusHex);
- let r = 1n;
- b = b % m;
- while (e > 0) {
- if (e % 2n === 1n) {
- r = (r * b) % m;
- }
- e = e / 2n;
- b = (b * b) % m;
- }
- let hexResult = r.toString(16);
-
- while (hexResult.length/2<exponent.length || (hexResult.length%2 != 0)) {
- hexResult = "0"+hexResult;
- }
+ async _negotiateARDAuthAsync(keyLength, serverPublicKey, clientKey) {
+ const clientPublicKey = legacyCrypto.exportKey("raw", clientKey.publicKey);
+ const sharedKey = legacyCrypto.deriveBits(
+ { name: "DH", public: serverPublicKey }, clientKey.privateKey, keyLength * 8);
- let bytesResult = [];
- for (let c = 0; c < hexResult.length; c += 2) {
- bytesResult.push(parseInt(hexResult.substr(c, 2), 16));
- }
- return bytesResult;
- }
+ const username = encodeUTF8(this._rfbCredentials.username).substring(0, 63);
+ const password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
- 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);
+ const credentials = window.crypto.getRandomValues(new Uint8Array(128));
+ for (let i = 0; i < username.length; i++) {
+ credentials[i] = username.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);
+ credentials[username.length] = 0;
+ for (let i = 0; i < password.length; i++) {
+ credentials[64 + i] = password.charCodeAt(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);
+ credentials[64 + password.length] = 0;
- 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);
+ const key = await legacyCrypto.digest("MD5", sharedKey);
+ const cipher = await legacyCrypto.importKey(
+ "raw", key, { name: "AES-ECB" }, false, ["encrypt"]);
+ const encrypted = await legacyCrypto.encrypt({ name: "AES-ECB" }, cipher, credentials);
this._rfbCredentials.ardCredentials = encrypted;
this._rfbCredentials.ardPublicKey = clientPublicKey;
@@ -1905,7 +1862,8 @@ export default class RFB extends EventTargetMixin {
if (e.message !== "disconnect normally") {
this._fail(e.message);
}
- }).then(() => {
+ })
+ .then(() => {
this.dispatchEvent(new CustomEvent('securityresult'));
this._rfbInitState = "SecurityResult";
return true;
@@ -1934,15 +1892,15 @@ export default class RFB extends EventTargetMixin {
const g = this._sock.rQshiftBytes(8);
const p = this._sock.rQshiftBytes(8);
const A = this._sock.rQshiftBytes(8);
- const b = window.crypto.getRandomValues(new Uint8Array(8));
- const B = new Uint8Array(this._modPow(g, b, p));
- const secret = new Uint8Array(this._modPow(A, b, p));
+ const dhKey = legacyCrypto.generateKey({ name: "DH", g: g, p: p }, true, ["deriveBits"]);
+ const B = legacyCrypto.exportKey("raw", dhKey.publicKey);
+ const secret = legacyCrypto.deriveBits({ name: "DH", public: A }, dhKey.privateKey, 64);
- const des = new DES(secret);
+ const key = legacyCrypto.importKey("raw", secret, { name: "DES-CBC" }, false, ["encrypt"]);
const username = encodeUTF8(this._rfbCredentials.username).substring(0, 255);
const password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
- const usernameBytes = new Uint8Array(256);
- const passwordBytes = new Uint8Array(64);
+ let usernameBytes = new Uint8Array(256);
+ let passwordBytes = new Uint8Array(64);
window.crypto.getRandomValues(usernameBytes);
window.crypto.getRandomValues(passwordBytes);
for (let i = 0; i < username.length; i++) {
@@ -1953,22 +1911,8 @@ export default class RFB extends EventTargetMixin {
passwordBytes[i] = password.charCodeAt(i);
}
passwordBytes[password.length] = 0;
- let x = new Uint8Array(secret);
- for (let i = 0; i < 32; i++) {
- for (let j = 0; j < 8; j++) {
- x[j] ^= usernameBytes[i * 8 + j];
- }
- x = des.enc8(x);
- usernameBytes.set(x, i * 8);
- }
- x = new Uint8Array(secret);
- for (let i = 0; i < 8; i++) {
- for (let j = 0; j < 8; j++) {
- x[j] ^= passwordBytes[i * 8 + j];
- }
- x = des.enc8(x);
- passwordBytes.set(x, i * 8);
- }
+ usernameBytes = legacyCrypto.encrypt({ name: "DES-CBC", iv: secret }, key, usernameBytes);
+ passwordBytes = legacyCrypto.encrypt({ name: "DES-CBC", iv: secret }, key, passwordBytes);
this._sock.send(B);
this._sock.send(usernameBytes);
this._sock.send(passwordBytes);
@@ -2937,7 +2881,9 @@ export default class RFB extends EventTargetMixin {
static genDES(password, challenge) {
const passwordChars = password.split('').map(c => c.charCodeAt(0));
- return (new DES(passwordChars)).encrypt(challenge);
+ const key = legacyCrypto.importKey(
+ "raw", passwordChars, { name: "DES-ECB" }, false, ["encrypt"]);
+ return legacyCrypto.encrypt({ name: "DES-ECB" }, key, challenge);
}
}
diff --git a/tests/test.rfb.js b/tests/test.rfb.js
index 2da3818..4262ee6 100644
--- a/tests/test.rfb.js
+++ b/tests/test.rfb.js
@@ -8,6 +8,7 @@ import { encodings } from '../core/encodings.js';
import { toUnsigned32bit } from '../core/util/int.js';
import { encodeUTF8 } from '../core/util/strings.js';
import KeyTable from '../core/input/keysym.js';
+import legacyCrypto from '../core/crypto/crypto.js';
import FakeWebSocket from './fake.websocket.js';
@@ -1270,6 +1271,19 @@ describe('Remote Frame Buffer Protocol Client', function () {
});
describe('ARD Authentication (type 30) Handler', function () {
+ let byteArray = new Uint8Array(Array.from(new Uint8Array(128).keys()));
+ function fakeGetRandomValues(arr) {
+ if (arr.length == 128) {
+ arr.set(byteArray);
+ }
+ return arr;
+ }
+ before(() => {
+ sinon.stub(window.crypto, "getRandomValues").callsFake(fakeGetRandomValues);
+ });
+ after(() => {
+ window.crypto.getRandomValues.restore();
+ });
it('should fire the credentialsrequired event if all credentials are missing', function () {
const spy = sinon.spy();
client.addEventListener("credentialsrequired", spy);
@@ -1298,35 +1312,30 @@ describe('Remote Frame Buffer Protocol Client', function () {
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('');
+ const generator = new Uint8Array([127, 255]);
+ const prime = new Uint8Array(byteArray);
+ const serverKey = legacyCrypto.generateKey(
+ { name: "DH", g: generator, p: prime }, false, ["deriveBits"]);
+ const clientKey = legacyCrypto.generateKey(
+ { name: "DH", g: generator, p: prime }, false, ["deriveBits"]);
+ const serverPublicKey = legacyCrypto.exportKey("raw", serverKey.publicKey);
+ const clientPublicKey = legacyCrypto.exportKey("raw", clientKey.publicKey);
- await client._negotiateARDAuthAsync(generator, 128, prime, serverPublicKey, clientPrivateKey, padding);
+ await client._negotiateARDAuthAsync(128, serverPublicKey, clientKey);
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]);
+ 199, 39, 204, 95, 190, 70, 127, 66, 5, 106, 153, 228, 123, 236, 150, 206,
+ 62, 107, 11, 4, 21, 242, 92, 184, 9, 81, 35, 125, 56, 167, 1, 215,
+ 182, 145, 183, 75, 245, 197, 47, 19, 122, 94, 64, 76, 77, 163, 222, 143,
+ 186, 174, 84, 39, 244, 179, 227, 114, 83, 231, 42, 106, 205, 43, 159, 110,
+ 209, 240, 157, 246, 237, 206, 134, 153, 195, 112, 92, 60, 28, 234, 91, 66,
+ 131, 38, 187, 195, 110, 167, 212, 241, 32, 250, 212, 213, 202, 89, 180, 21,
+ 71, 217, 209, 81, 42, 61, 118, 248, 65, 123, 98, 78, 139, 111, 202, 137,
+ 50, 185, 37, 173, 58, 99, 187, 53, 42, 125, 13, 165, 232, 163, 151, 42, 0]);
let output = new Uint8Array(256);
output.set(expectEncrypted, 0);