main
Raw Download raw file
  1import Base64 from "../base64.js";
  2import { modPow, bigIntToU8Array, u8ArrayToBigInt } from "./bigint.js";
  3
  4export class RSACipher {
  5    constructor() {
  6        this._keyLength = 0;
  7        this._keyBytes = 0;
  8        this._n = null;
  9        this._e = null;
 10        this._d = null;
 11        this._nBigInt = null;
 12        this._eBigInt = null;
 13        this._dBigInt = null;
 14        this._extractable = false;
 15    }
 16
 17    get algorithm() {
 18        return { name: "RSA-PKCS1-v1_5" };
 19    }
 20
 21    _base64urlDecode(data) {
 22        data = data.replace(/-/g, "+").replace(/_/g, "/");
 23        data = data.padEnd(Math.ceil(data.length / 4) * 4, "=");
 24        return Base64.decode(data);
 25    }
 26
 27    _padArray(arr, length) {
 28        const res = new Uint8Array(length);
 29        res.set(arr, length - arr.length);
 30        return res;
 31    }
 32
 33    static async generateKey(algorithm, extractable, _keyUsages) {
 34        const cipher = new RSACipher;
 35        await cipher._generateKey(algorithm, extractable);
 36        return { privateKey: cipher };
 37    }
 38
 39    async _generateKey(algorithm, extractable) {
 40        this._keyLength = algorithm.modulusLength;
 41        this._keyBytes = Math.ceil(this._keyLength / 8);
 42        const key = await window.crypto.subtle.generateKey(
 43            {
 44                name: "RSA-OAEP",
 45                modulusLength: algorithm.modulusLength,
 46                publicExponent: algorithm.publicExponent,
 47                hash: {name: "SHA-256"},
 48            },
 49            true, ["encrypt", "decrypt"]);
 50        const privateKey = await window.crypto.subtle.exportKey("jwk", key.privateKey);
 51        this._n = this._padArray(this._base64urlDecode(privateKey.n), this._keyBytes);
 52        this._nBigInt = u8ArrayToBigInt(this._n);
 53        this._e = this._padArray(this._base64urlDecode(privateKey.e), this._keyBytes);
 54        this._eBigInt = u8ArrayToBigInt(this._e);
 55        this._d = this._padArray(this._base64urlDecode(privateKey.d), this._keyBytes);
 56        this._dBigInt = u8ArrayToBigInt(this._d);
 57        this._extractable = extractable;
 58    }
 59
 60    static async importKey(key, _algorithm, extractable, keyUsages) {
 61        if (keyUsages.length !== 1 || keyUsages[0] !== "encrypt") {
 62            throw new Error("only support importing RSA public key");
 63        }
 64        const cipher = new RSACipher;
 65        await cipher._importKey(key, extractable);
 66        return cipher;
 67    }
 68
 69    async _importKey(key, extractable) {
 70        const n = key.n;
 71        const e = key.e;
 72        if (n.length !== e.length) {
 73            throw new Error("the sizes of modulus and public exponent do not match");
 74        }
 75        this._keyBytes = n.length;
 76        this._keyLength = this._keyBytes * 8;
 77        this._n = new Uint8Array(this._keyBytes);
 78        this._e = new Uint8Array(this._keyBytes);
 79        this._n.set(n);
 80        this._e.set(e);
 81        this._nBigInt = u8ArrayToBigInt(this._n);
 82        this._eBigInt = u8ArrayToBigInt(this._e);
 83        this._extractable = extractable;
 84    }
 85
 86    async encrypt(_algorithm, message) {
 87        if (message.length > this._keyBytes - 11) {
 88            return null;
 89        }
 90        const ps = new Uint8Array(this._keyBytes - message.length - 3);
 91        window.crypto.getRandomValues(ps);
 92        for (let i = 0; i < ps.length; i++) {
 93            ps[i] = Math.floor(ps[i] * 254 / 255 + 1);
 94        }
 95        const em = new Uint8Array(this._keyBytes);
 96        em[1] = 0x02;
 97        em.set(ps, 2);
 98        em.set(message, ps.length + 3);
 99        const emBigInt = u8ArrayToBigInt(em);
100        const c = modPow(emBigInt, this._eBigInt, this._nBigInt);
101        return bigIntToU8Array(c, this._keyBytes);
102    }
103
104    async decrypt(_algorithm, message) {
105        if (message.length !== this._keyBytes) {
106            return null;
107        }
108        const msgBigInt = u8ArrayToBigInt(message);
109        const emBigInt = modPow(msgBigInt, this._dBigInt, this._nBigInt);
110        const em = bigIntToU8Array(emBigInt, this._keyBytes);
111        if (em[0] !== 0x00 || em[1] !== 0x02) {
112            return null;
113        }
114        let i = 2;
115        for (; i < em.length; i++) {
116            if (em[i] === 0x00) {
117                break;
118            }
119        }
120        if (i === em.length) {
121            return null;
122        }
123        return em.slice(i + 1, em.length);
124    }
125
126    async exportKey() {
127        if (!this._extractable) {
128            throw new Error("key is not extractable");
129        }
130        return { n: this._n, e: this._e, d: this._d };
131    }
132}