forked from novnc/noVNC
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Cleanup for the cryptographic algorithms that are not supported by Su…
…btleCrypto
- Loading branch information
Showing
10 changed files
with
663 additions
and
413 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.