summaryrefslogtreecommitdiff
path: root/core/crypto
diff options
context:
space:
mode:
Diffstat (limited to 'core/crypto')
-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.js330
-rw-r--r--core/crypto/dh.js55
-rw-r--r--core/crypto/md5.js82
-rw-r--r--core/crypto/rsa.js132
7 files changed, 901 insertions, 0 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/crypto/des.js b/core/crypto/des.js
new file mode 100644
index 0000000..8dab31f
--- /dev/null
+++ b/core/crypto/des.js
@@ -0,0 +1,330 @@
+/*
+ * Ported from Flashlight VNC ActionScript implementation:
+ * http://www.wizhelp.com/flashlight-vnc/
+ *
+ * Full attribution follows:
+ *
+ * -------------------------------------------------------------------------
+ *
+ * This DES class has been extracted from package Acme.Crypto for use in VNC.
+ * The unnecessary odd parity code has been removed.
+ *
+ * These changes are:
+ * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+
+ * DesCipher - the DES encryption method
+ *
+ * The meat of this code is by Dave Zimmerman <dzimm@widget.com>, and is:
+ *
+ * Copyright (c) 1996 Widget Workshop, Inc. All Rights Reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software
+ * and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and
+ * without fee is hereby granted, provided that this copyright notice is kept
+ * intact.
+ *
+ * WIDGET WORKSHOP MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY
+ * OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+ * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. WIDGET WORKSHOP SHALL NOT BE LIABLE
+ * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+ * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
+ *
+ * THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE
+ * CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE
+ * PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT
+ * NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE
+ * SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE
+ * PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES"). WIDGET WORKSHOP
+ * SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR
+ * HIGH RISK ACTIVITIES.
+ *
+ *
+ * The rest is:
+ *
+ * Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Visit the ACME Labs Java page for up-to-date versions of this and other
+ * fine Java utilities: http://www.acme.com/java/
+ */
+
+/* eslint-disable comma-spacing */
+
+// Tables, permutations, S-boxes, etc.
+const PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3,
+ 25, 7,15, 6,26,19,12, 1,40,51,30,36,46,54,29,39,
+ 50,44,32,47,43,48,38,55,33,52,45,41,49,35,28,31 ],
+ totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28];
+
+const z = 0x0;
+let a,b,c,d,e,f;
+a=1<<16; b=1<<24; c=a|b; d=1<<2; e=1<<10; f=d|e;
+const SP1 = [c|e,z|z,a|z,c|f,c|d,a|f,z|d,a|z,z|e,c|e,c|f,z|e,b|f,c|d,b|z,z|d,
+ z|f,b|e,b|e,a|e,a|e,c|z,c|z,b|f,a|d,b|d,b|d,a|d,z|z,z|f,a|f,b|z,
+ a|z,c|f,z|d,c|z,c|e,b|z,b|z,z|e,c|d,a|z,a|e,b|d,z|e,z|d,b|f,a|f,
+ c|f,a|d,c|z,b|f,b|d,z|f,a|f,c|e,z|f,b|e,b|e,z|z,a|d,a|e,z|z,c|d];
+a=1<<20; b=1<<31; c=a|b; d=1<<5; e=1<<15; f=d|e;
+const SP2 = [c|f,b|e,z|e,a|f,a|z,z|d,c|d,b|f,b|d,c|f,c|e,b|z,b|e,a|z,z|d,c|d,
+ a|e,a|d,b|f,z|z,b|z,z|e,a|f,c|z,a|d,b|d,z|z,a|e,z|f,c|e,c|z,z|f,
+ z|z,a|f,c|d,a|z,b|f,c|z,c|e,z|e,c|z,b|e,z|d,c|f,a|f,z|d,z|e,b|z,
+ z|f,c|e,a|z,b|d,a|d,b|f,b|d,a|d,a|e,z|z,b|e,z|f,b|z,c|d,c|f,a|e];
+a=1<<17; b=1<<27; c=a|b; d=1<<3; e=1<<9; f=d|e;
+const SP3 = [z|f,c|e,z|z,c|d,b|e,z|z,a|f,b|e,a|d,b|d,b|d,a|z,c|f,a|d,c|z,z|f,
+ b|z,z|d,c|e,z|e,a|e,c|z,c|d,a|f,b|f,a|e,a|z,b|f,z|d,c|f,z|e,b|z,
+ c|e,b|z,a|d,z|f,a|z,c|e,b|e,z|z,z|e,a|d,c|f,b|e,b|d,z|e,z|z,c|d,
+ b|f,a|z,b|z,c|f,z|d,a|f,a|e,b|d,c|z,b|f,z|f,c|z,a|f,z|d,c|d,a|e];
+a=1<<13; b=1<<23; c=a|b; d=1<<0; e=1<<7; f=d|e;
+const SP4 = [c|d,a|f,a|f,z|e,c|e,b|f,b|d,a|d,z|z,c|z,c|z,c|f,z|f,z|z,b|e,b|d,
+ z|d,a|z,b|z,c|d,z|e,b|z,a|d,a|e,b|f,z|d,a|e,b|e,a|z,c|e,c|f,z|f,
+ b|e,b|d,c|z,c|f,z|f,z|z,z|z,c|z,a|e,b|e,b|f,z|d,c|d,a|f,a|f,z|e,
+ c|f,z|f,z|d,a|z,b|d,a|d,c|e,b|f,a|d,a|e,b|z,c|d,z|e,b|z,a|z,c|e];
+a=1<<25; b=1<<30; c=a|b; d=1<<8; e=1<<19; f=d|e;
+const SP5 = [z|d,a|f,a|e,c|d,z|e,z|d,b|z,a|e,b|f,z|e,a|d,b|f,c|d,c|e,z|f,b|z,
+ a|z,b|e,b|e,z|z,b|d,c|f,c|f,a|d,c|e,b|d,z|z,c|z,a|f,a|z,c|z,z|f,
+ z|e,c|d,z|d,a|z,b|z,a|e,c|d,b|f,a|d,b|z,c|e,a|f,b|f,z|d,a|z,c|e,
+ c|f,z|f,c|z,c|f,a|e,z|z,b|e,c|z,z|f,a|d,b|d,z|e,z|z,b|e,a|f,b|d];
+a=1<<22; b=1<<29; c=a|b; d=1<<4; e=1<<14; f=d|e;
+const SP6 = [b|d,c|z,z|e,c|f,c|z,z|d,c|f,a|z,b|e,a|f,a|z,b|d,a|d,b|e,b|z,z|f,
+ z|z,a|d,b|f,z|e,a|e,b|f,z|d,c|d,c|d,z|z,a|f,c|e,z|f,a|e,c|e,b|z,
+ b|e,z|d,c|d,a|e,c|f,a|z,z|f,b|d,a|z,b|e,b|z,z|f,b|d,c|f,a|e,c|z,
+ a|f,c|e,z|z,c|d,z|d,z|e,c|z,a|f,z|e,a|d,b|f,z|z,c|e,b|z,a|d,b|f];
+a=1<<21; b=1<<26; c=a|b; d=1<<1; e=1<<11; f=d|e;
+const SP7 = [a|z,c|d,b|f,z|z,z|e,b|f,a|f,c|e,c|f,a|z,z|z,b|d,z|d,b|z,c|d,z|f,
+ b|e,a|f,a|d,b|e,b|d,c|z,c|e,a|d,c|z,z|e,z|f,c|f,a|e,z|d,b|z,a|e,
+ b|z,a|e,a|z,b|f,b|f,c|d,c|d,z|d,a|d,b|z,b|e,a|z,c|e,z|f,a|f,c|e,
+ z|f,b|d,c|f,c|z,a|e,z|z,z|d,c|f,z|z,a|f,c|z,z|e,b|d,b|e,z|e,a|d];
+a=1<<18; b=1<<28; c=a|b; d=1<<6; e=1<<12; f=d|e;
+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,
+ c|z,b|d,b|e,z|f,a|e,a|d,c|d,c|e,z|f,z|z,z|z,c|d,b|d,b|e,a|f,a|z,
+ a|f,a|z,c|e,z|e,z|d,c|d,z|e,a|f,b|e,z|d,b|d,c|z,c|d,b|z,a|z,b|f,
+ z|z,c|f,a|d,b|d,c|z,b|e,b|f,z|z,c|f,a|e,a|e,z|f,z|f,a|d,b|z,c|e];
+
+/* eslint-enable comma-spacing */
+
+class DES {
+ constructor(password) {
+ this.keys = [];
+
+ // Set the key.
+ const pc1m = [], pcr = [], kn = [];
+
+ for (let j = 0, l = 56; j < 56; ++j, l -= 8) {
+ l += l < -5 ? 65 : l < -3 ? 31 : l < -1 ? 63 : l === 27 ? 35 : 0; // PC1
+ const m = l & 0x7;
+ pc1m[j] = ((password[l >>> 3] & (1<<m)) !== 0) ? 1: 0;
+ }
+
+ for (let i = 0; i < 16; ++i) {
+ const m = i << 1;
+ const n = m + 1;
+ kn[m] = kn[n] = 0;
+ for (let o = 28; o < 59; o += 28) {
+ for (let j = o - 28; j < o; ++j) {
+ const l = j + totrot[i];
+ pcr[j] = l < o ? pc1m[l] : pc1m[l - 28];
+ }
+ }
+ for (let j = 0; j < 24; ++j) {
+ if (pcr[PC2[j]] !== 0) {
+ kn[m] |= 1 << (23 - j);
+ }
+ if (pcr[PC2[j + 24]] !== 0) {
+ kn[n] |= 1 << (23 - j);
+ }
+ }
+ }
+
+ // cookey
+ for (let i = 0, rawi = 0, KnLi = 0; i < 16; ++i) {
+ const raw0 = kn[rawi++];
+ const raw1 = kn[rawi++];
+ this.keys[KnLi] = (raw0 & 0x00fc0000) << 6;
+ this.keys[KnLi] |= (raw0 & 0x00000fc0) << 10;
+ this.keys[KnLi] |= (raw1 & 0x00fc0000) >>> 10;
+ this.keys[KnLi] |= (raw1 & 0x00000fc0) >>> 6;
+ ++KnLi;
+ this.keys[KnLi] = (raw0 & 0x0003f000) << 12;
+ this.keys[KnLi] |= (raw0 & 0x0000003f) << 16;
+ this.keys[KnLi] |= (raw1 & 0x0003f000) >>> 4;
+ this.keys[KnLi] |= (raw1 & 0x0000003f);
+ ++KnLi;
+ }
+ }
+
+ // Encrypt 8 bytes of text
+ enc8(text) {
+ const b = text.slice();
+ let i = 0, l, r, x; // left, right, accumulator
+
+ // Squash 8 bytes to 2 ints
+ l = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];
+ r = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];
+
+ x = ((l >>> 4) ^ r) & 0x0f0f0f0f;
+ r ^= x;
+ l ^= (x << 4);
+ x = ((l >>> 16) ^ r) & 0x0000ffff;
+ r ^= x;
+ l ^= (x << 16);
+ x = ((r >>> 2) ^ l) & 0x33333333;
+ l ^= x;
+ r ^= (x << 2);
+ x = ((r >>> 8) ^ l) & 0x00ff00ff;
+ l ^= x;
+ r ^= (x << 8);
+ r = (r << 1) | ((r >>> 31) & 1);
+ x = (l ^ r) & 0xaaaaaaaa;
+ l ^= x;
+ r ^= x;
+ l = (l << 1) | ((l >>> 31) & 1);
+
+ for (let i = 0, keysi = 0; i < 8; ++i) {
+ x = (r << 28) | (r >>> 4);
+ x ^= this.keys[keysi++];
+ let fval = SP7[x & 0x3f];
+ fval |= SP5[(x >>> 8) & 0x3f];
+ fval |= SP3[(x >>> 16) & 0x3f];
+ fval |= SP1[(x >>> 24) & 0x3f];
+ x = r ^ this.keys[keysi++];
+ fval |= SP8[x & 0x3f];
+ fval |= SP6[(x >>> 8) & 0x3f];
+ fval |= SP4[(x >>> 16) & 0x3f];
+ fval |= SP2[(x >>> 24) & 0x3f];
+ l ^= fval;
+ x = (l << 28) | (l >>> 4);
+ x ^= this.keys[keysi++];
+ fval = SP7[x & 0x3f];
+ fval |= SP5[(x >>> 8) & 0x3f];
+ fval |= SP3[(x >>> 16) & 0x3f];
+ fval |= SP1[(x >>> 24) & 0x3f];
+ x = l ^ this.keys[keysi++];
+ fval |= SP8[x & 0x0000003f];
+ fval |= SP6[(x >>> 8) & 0x3f];
+ fval |= SP4[(x >>> 16) & 0x3f];
+ fval |= SP2[(x >>> 24) & 0x3f];
+ r ^= fval;
+ }
+
+ r = (r << 31) | (r >>> 1);
+ x = (l ^ r) & 0xaaaaaaaa;
+ l ^= x;
+ r ^= x;
+ l = (l << 31) | (l >>> 1);
+ x = ((l >>> 8) ^ r) & 0x00ff00ff;
+ r ^= x;
+ l ^= (x << 8);
+ x = ((l >>> 2) ^ r) & 0x33333333;
+ r ^= x;
+ l ^= (x << 2);
+ x = ((r >>> 16) ^ l) & 0x0000ffff;
+ l ^= x;
+ r ^= (x << 16);
+ x = ((r >>> 4) ^ l) & 0x0f0f0f0f;
+ l ^= x;
+ r ^= (x << 4);
+
+ // Spread ints to bytes
+ x = [r, l];
+ for (i = 0; i < 8; i++) {
+ b[i] = (x[i>>>2] >>> (8 * (3 - (i % 4)))) % 256;
+ if (b[i] < 0) { b[i] += 256; } // unsigned
+ }
+ 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;
+ }
+
+ _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/crypto/md5.js b/core/crypto/md5.js
new file mode 100644
index 0000000..fcfefff
--- /dev/null
+++ b/core/crypto/md5.js
@@ -0,0 +1,82 @@
+/*
+ * 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 an array of bytes, returns an array of bytes
+ */
+
+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) {
+ 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;
+}
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 };
+ }
+}