From 08a23b61b2c314e30ff87efddafc4973c7233566 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 31 Dec 2024 04:41:34 +0900 Subject: [PATCH] =?UTF-8?q?=EC=95=94=ED=98=B8=20=ED=82=A4=EA=B0=80=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=EB=90=9C=20=ED=81=B4=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EC=96=B8=ED=8A=B8=EC=97=90=EC=84=9C=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=EC=9D=84=20=EC=88=98=ED=96=89=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=EC=9D=84=20=EB=B3=80=EA=B2=BD=EB=90=9C=20API?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 우선 이메일과 비밀번호를 이용해 로그인을 수행한 후, Token Upgrade를 수행하도록 변경했습니다. --- .prettierignore | 3 + src/lib/modules/crypto.ts | 6 +- src/lib/services/auth.ts | 41 ++++++++++ src/lib/services/key.ts | 42 ++++++++++ .../(fullscreen)/auth/login/+page.svelte | 19 +++-- src/routes/(fullscreen)/auth/login/service.ts | 66 ++++++++------- src/routes/(fullscreen)/key/export/service.ts | 81 +------------------ .../(fullscreen)/key/generate/service.ts | 7 +- 8 files changed, 142 insertions(+), 123 deletions(-) create mode 100644 src/lib/services/auth.ts create mode 100644 src/lib/services/key.ts 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();