133 lines
4.4 KiB
JavaScript
133 lines
4.4 KiB
JavaScript
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 };
|
|
}
|
|
}
|