diff --git a/package.json b/package.json index 98aad58..7df57b1 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@types/jsonwebtoken": "^9.0.7", "@types/ms": "^0.7.34", "autoprefixer": "^10.4.20", + "dexie": "^4.0.10", "drizzle-kit": "^0.22.0", "eslint": "^9.7.0", "eslint-config-prettier": "^9.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8608e1f..8b8d506 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -52,6 +52,9 @@ devDependencies: autoprefixer: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.49) + dexie: + specifier: ^4.0.10 + version: 4.0.10 drizzle-kit: specifier: ^0.22.0 version: 0.22.8 @@ -1745,6 +1748,10 @@ packages: resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==} dev: true + /dexie@4.0.10: + resolution: {integrity: sha512-eM2RzuR3i+M046r2Q0Optl3pS31qTWf8aFuA7H9wnsHTwl8EPvroVLwvQene/6paAs39Tbk6fWZcn2aZaHkc/w==} + dev: true + /didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} dev: true diff --git a/src/lib/index.ts b/src/lib/index.ts deleted file mode 100644 index 856f2b6..0000000 --- a/src/lib/index.ts +++ /dev/null @@ -1 +0,0 @@ -// place files you want to import through the `$lib` alias in this folder. diff --git a/src/lib/indexedDB.ts b/src/lib/indexedDB.ts new file mode 100644 index 0000000..92553b0 --- /dev/null +++ b/src/lib/indexedDB.ts @@ -0,0 +1,30 @@ +import { Dexie, type EntityTable } from "dexie"; + +interface ClientKeyPair { + type: "publicKey" | "privateKey"; + key: CryptoKey; +} + +const keyStore = new Dexie("keyStore") as Dexie & { + clientKeyPairs: EntityTable; +}; + +keyStore.version(1).stores({ + clientKeyPairs: "type", +}); + +export const getKeyPairFromIndexedDB = async () => { + const pubKey = await keyStore.clientKeyPairs.get("publicKey"); + const privKey = await keyStore.clientKeyPairs.get("privateKey"); + return { + pubKey: pubKey?.key ?? null, + privKey: privKey?.key ?? null, + }; +}; + +export const storeKeyPairIntoIndexedDB = async (pubKey: CryptoKey, privKey: CryptoKey) => { + await keyStore.clientKeyPairs.bulkPut([ + { type: "publicKey", key: pubKey }, + { type: "privateKey", key: privKey }, + ]); +}; diff --git a/src/routes/(fullscreen)/auth/generateKey/service.ts b/src/routes/(fullscreen)/auth/generateKey/service.ts new file mode 100644 index 0000000..5653703 --- /dev/null +++ b/src/routes/(fullscreen)/auth/generateKey/service.ts @@ -0,0 +1,57 @@ +import { storeKeyPairIntoIndexedDB } from "$lib/indexedDB"; + +type KeyType = "public" | "private"; + +const generateRSAKeyPair = async () => { + const keyPair = await window.crypto.subtle.generateKey( + { + name: "RSA-OAEP", + modulusLength: 4096, + publicExponent: new Uint8Array([1, 0, 1]), + hash: "SHA-256", + } satisfies RsaHashedKeyGenParams, + true, + ["encrypt", "decrypt"], + ); + return keyPair; +}; + +const exportKeyAsPem = async (key: CryptoKey, type: KeyType) => { + const exportedKey = await window.crypto.subtle.exportKey( + type === "public" ? "spki" : "pkcs8", + key, + ); + const exportedKeyBase64 = btoa(String.fromCharCode(...new Uint8Array(exportedKey))) + .match(/.{1,64}/g)! + .join("\n"); + + const pemHeader = type === "public" ? "PUBLIC" : "PRIVATE"; + const pem = `-----BEGIN ${pemHeader} KEY-----\n${exportedKeyBase64}\n-----END ${pemHeader} KEY-----\n`; + return pem; +}; + +const makeRSAKeyNonextractable = async (key: CryptoKey, type: KeyType) => { + const format = type === "public" ? "spki" : "pkcs8"; + const keyUsage = type === "public" ? "encrypt" : "decrypt"; + return await window.crypto.subtle.importKey( + format, + await window.crypto.subtle.exportKey(format, key), + { + name: "RSA-OAEP", + hash: "SHA-256", + } satisfies RsaHashedImportParams, + false, + [keyUsage], + ); +}; + +export const generateKeyPair = async () => { + const keyPair = await generateRSAKeyPair(); + + const privKeySecure = await makeRSAKeyNonextractable(keyPair.privateKey, "private"); + await storeKeyPairIntoIndexedDB(keyPair.publicKey, privKeySecure); + + const pubKeyPem = await exportKeyAsPem(keyPair.publicKey, "public"); + const privKeyPem = await exportKeyAsPem(keyPair.privateKey, "private"); + return { pubKeyPem, privKeyPem }; +};