diff options
Diffstat (limited to 'core/crypto/rsa.js')
-rw-r--r-- | core/crypto/rsa.js | 132 |
1 files changed, 132 insertions, 0 deletions
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 }; + } +} |