diff --git a/.prettierignore b/.prettierignore index ab78a95..0d5b39a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,3 +2,6 @@ package-lock.json pnpm-lock.yaml yarn.lock + +# Output +/drizzle diff --git a/src/lib/modules/crypto.ts b/src/lib/modules/crypto.ts index ad7b6ef..a5ed99b 100644 --- a/src/lib/modules/crypto.ts +++ b/src/lib/modules/crypto.ts @@ -50,7 +50,7 @@ export const makeRSAKeyNonextractable = async (key: CryptoKey, type: RSAKeyType) ); }; -export const exportRSAKey = async (key: CryptoKey, type: RSAKeyType) => { +const exportRSAKey = async (key: CryptoKey, type: RSAKeyType) => { const format = type === "public" ? ("spki" as const) : ("pkcs8" as const); return { format, @@ -58,6 +58,10 @@ export const exportRSAKey = async (key: CryptoKey, type: RSAKeyType) => { }; }; +export const exportRSAKeyToBase64 = async (key: CryptoKey, type: RSAKeyType) => { + return encodeToBase64((await exportRSAKey(key, type)).key); +}; + export const encryptRSAPlaintext = async (plaintext: ArrayBuffer, publicKey: CryptoKey) => { return await window.crypto.subtle.encrypt( { diff --git a/src/lib/services/auth.ts b/src/lib/services/auth.ts new file mode 100644 index 0000000..4ce69c3 --- /dev/null +++ b/src/lib/services/auth.ts @@ -0,0 +1,41 @@ +import { + encodeToBase64, + decodeFromBase64, + decryptRSACiphertext, + signRSAMessage, +} from "$lib/modules/crypto"; + +export const requestTokenUpgrade = async ( + encPubKeyBase64: string, + encPrivKey: CryptoKey, + sigPubKeyBase64: string, + sigPrivKey: CryptoKey, +) => { + let res = await fetch("/api/auth/upgradeToken", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + encPubKey: encPubKeyBase64, + sigPubKey: sigPubKeyBase64, + }), + }); + if (!res.ok) return false; + + const { challenge } = await res.json(); + const answer = await decryptRSACiphertext(decodeFromBase64(challenge), encPrivKey); + const sigAnswer = await signRSAMessage(answer, sigPrivKey); + + res = await fetch("/api/auth/upgradeToken/verify", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + answer: encodeToBase64(answer), + sigAnswer: encodeToBase64(sigAnswer), + }), + }); + return res.ok; +}; diff --git a/src/lib/services/key.ts b/src/lib/services/key.ts new file mode 100644 index 0000000..56b96e5 --- /dev/null +++ b/src/lib/services/key.ts @@ -0,0 +1,42 @@ +import { callAPI } from "$lib/hooks"; +import { + encodeToBase64, + decodeFromBase64, + decryptRSACiphertext, + signRSAMessage, +} from "$lib/modules/crypto"; + +export const requestClientRegistration = async ( + encPubKeyBase64: string, + encPrivKey: CryptoKey, + sigPubKeyBase64: string, + sigPrivKey: CryptoKey, +) => { + let res = await callAPI("/api/client/register", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + encPubKey: encPubKeyBase64, + sigPubKey: sigPubKeyBase64, + }), + }); + if (!res.ok) return false; + + const { challenge } = await res.json(); + const answer = await decryptRSACiphertext(decodeFromBase64(challenge), encPrivKey); + const sigAnswer = await signRSAMessage(answer, sigPrivKey); + + res = await callAPI("/api/client/verify", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + answer: encodeToBase64(answer), + sigAnswer: encodeToBase64(sigAnswer), + }), + }); + return res.ok; +}; diff --git a/src/routes/(fullscreen)/auth/login/+page.svelte b/src/routes/(fullscreen)/auth/login/+page.svelte index 015bcfc..fd5390d 100644 --- a/src/routes/(fullscreen)/auth/login/+page.svelte +++ b/src/routes/(fullscreen)/auth/login/+page.svelte @@ -5,8 +5,8 @@ import { TitleDiv, BottomDiv } from "$lib/components/divs"; import { TextInput } from "$lib/components/inputs"; import { refreshToken } from "$lib/hooks/callAPI"; - import { keyPairStore } from "$lib/stores"; - import { requestLogin } from "./service"; + import { keyPairsStore } from "$lib/stores"; + import { requestLogin, requestTokenUpgrade } from "./service"; let { data } = $props(); @@ -16,14 +16,23 @@ const login = async () => { // TODO: Validation - if (await requestLogin(email, password, $keyPairStore)) { + try { + if (!(await requestLogin(email, password))) throw new Error("Failed to login"); + + if ( + $keyPairsStore && + !(await requestTokenUpgrade($keyPairsStore.encKeyPair, $keyPairsStore.sigKeyPair)) + ) + throw new Error("Failed to upgrade token"); + await goto( - $keyPairStore + $keyPairsStore ? data.redirectPath : "/key/generate?redirect=" + encodeURIComponent(data.redirectPath), ); - } else { + } catch (e) { // TODO: Alert + throw e; } }; diff --git a/src/routes/(fullscreen)/auth/login/service.ts b/src/routes/(fullscreen)/auth/login/service.ts index d8abe33..0a7f7d9 100644 --- a/src/routes/(fullscreen)/auth/login/service.ts +++ b/src/routes/(fullscreen)/auth/login/service.ts @@ -1,48 +1,46 @@ -import { encodeToBase64, exportRSAKey } from "$lib/modules/crypto"; -import { requestPubKeyRegistration } from "../../key/export/service"; +import { exportRSAKeyToBase64 } from "$lib/modules/crypto"; +import { requestTokenUpgrade as requestTokenUpgradeInternal } from "$lib/services/auth"; +import { requestClientRegistration } from "$lib/services/key"; -const callLoginAPI = async (email: string, password: string, pubKeyBase64?: string) => { - return await fetch("/api/auth/login", { +export const requestLogin = async (email: string, password: string) => { + const res = await fetch("/api/auth/login", { method: "POST", headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ - email, - password, - pubKey: pubKeyBase64, - }), + body: JSON.stringify({ email, password }), }); + return res.ok; }; -export const requestLogin = async ( - email: string, - password: string, - keyPair: CryptoKeyPair | null, - registerPubKey = true, -): Promise => { - const pubKeyBase64 = keyPair - ? encodeToBase64((await exportRSAKey(keyPair.publicKey, "public")).key) - : undefined; - let loginRes = await callLoginAPI(email, password, pubKeyBase64); - if (loginRes.ok) { +export const requestTokenUpgrade = async (encKeyPair: CryptoKeyPair, sigKeyPair: CryptoKeyPair) => { + const encPubKeyBase64 = await exportRSAKeyToBase64(encKeyPair.publicKey, "public"); + const sigPubKeyBase64 = await exportRSAKeyToBase64(sigKeyPair.publicKey, "public"); + if ( + await requestTokenUpgradeInternal( + encPubKeyBase64, + encKeyPair.privateKey, + sigPubKeyBase64, + sigKeyPair.privateKey, + ) + ) { return true; - } else if (loginRes.status !== 401 || !keyPair || !registerPubKey) { - return false; } - const { message } = await loginRes.json(); - if (message !== "Unregistered public key") { - return false; - } - - loginRes = await callLoginAPI(email, password); - if (!loginRes.ok) { - return false; - } - - if (await requestPubKeyRegistration(pubKeyBase64!, keyPair.privateKey)) { - return requestLogin(email, password, keyPair, false); + if ( + await requestClientRegistration( + encPubKeyBase64, + encKeyPair.privateKey, + sigPubKeyBase64, + sigKeyPair.privateKey, + ) + ) { + return await requestTokenUpgradeInternal( + encPubKeyBase64, + encKeyPair.privateKey, + sigPubKeyBase64, + sigKeyPair.privateKey, + ); } else { return false; } diff --git a/src/routes/(fullscreen)/key/export/service.ts b/src/routes/(fullscreen)/key/export/service.ts index 88a45e6..97cfd8b 100644 --- a/src/routes/(fullscreen)/key/export/service.ts +++ b/src/routes/(fullscreen)/key/export/service.ts @@ -1,12 +1,9 @@ import { callAPI } from "$lib/hooks"; import { storeRSAKey } from "$lib/indexedDB"; -import { - encodeToBase64, - decodeFromBase64, - encryptRSAPlaintext, - decryptRSACiphertext, - signRSAMessage, -} from "$lib/modules/crypto"; +import { encodeToBase64, encryptRSAPlaintext } from "$lib/modules/crypto"; + +export { requestTokenUpgrade } from "$lib/services/auth"; +export { requestClientRegistration } from "$lib/services/key"; type ExportedKeyPairs = { generator: "ArkVault"; @@ -36,41 +33,6 @@ export const makeKeyPairsSaveable = ( } satisfies ExportedKeyPairs; }; -export const requestClientRegistration = async ( - encPubKeyBase64: string, - encPrivKey: CryptoKey, - sigPubKeyBase64: string, - sigPrivKey: CryptoKey, -) => { - let res = await callAPI("/api/client/register", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - encPubKey: encPubKeyBase64, - sigPubKey: sigPubKeyBase64, - }), - }); - if (!res.ok) return false; - - const { challenge } = await res.json(); - const answer = await decryptRSACiphertext(decodeFromBase64(challenge), encPrivKey); - const sigAnswer = await signRSAMessage(answer, sigPrivKey); - - res = await callAPI("/api/client/verify", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - answer: encodeToBase64(answer), - sigAnswer: encodeToBase64(sigAnswer), - }), - }); - return res.ok; -}; - export const storeKeyPairsPersistently = async ( encKeyPair: CryptoKeyPair, sigKeyPair: CryptoKeyPair, @@ -81,41 +43,6 @@ export const storeKeyPairsPersistently = async ( await storeRSAKey(sigKeyPair.privateKey, "sign"); }; -export const requestTokenUpgrade = async ( - encPubKeyBase64: string, - encPrivKey: CryptoKey, - sigPubKeyBase64: string, - sigPrivKey: CryptoKey, -) => { - let res = await fetch("/api/auth/upgradeToken", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - encPubKey: encPubKeyBase64, - sigPubKey: sigPubKeyBase64, - }), - }); - if (!res.ok) return false; - - const { challenge } = await res.json(); - const answer = await decryptRSACiphertext(decodeFromBase64(challenge), encPrivKey); - const sigAnswer = await signRSAMessage(answer, sigPrivKey); - - res = await fetch("/api/auth/upgradeToken/verify", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - answer: encodeToBase64(answer), - sigAnswer: encodeToBase64(sigAnswer), - }), - }); - return res.ok; -}; - export const requestInitialMekRegistration = async ( mekDraft: ArrayBuffer, publicKey: CryptoKey, diff --git a/src/routes/(fullscreen)/key/generate/service.ts b/src/routes/(fullscreen)/key/generate/service.ts index 8c5e6d2..c5aacf7 100644 --- a/src/routes/(fullscreen)/key/generate/service.ts +++ b/src/routes/(fullscreen)/key/generate/service.ts @@ -1,19 +1,14 @@ import { - encodeToBase64, generateRSAEncKeyPair, generateRSASigKeyPair, makeRSAKeyNonextractable, - exportRSAKey, + exportRSAKeyToBase64, generateAESKey, makeAESKeyNonextractable, exportAESKey, } from "$lib/modules/crypto"; import { keyPairsStore, mekStore } from "$lib/stores"; -const exportRSAKeyToBase64 = async (key: CryptoKey, type: "public" | "private") => { - return encodeToBase64((await exportRSAKey(key, type)).key); -}; - export const generateKeyPairs = async () => { const encKeyPair = await generateRSAEncKeyPair(); const sigKeyPair = await generateRSASigKeyPair();