From 0d00e2476aab8bd72528ca01745891deea427283 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 31 Dec 2024 09:32:37 +0900 Subject: [PATCH] =?UTF-8?q?Request=20Body=EC=9D=98=20=ED=95=84=EB=93=9C?= =?UTF-8?q?=EB=A7=88=EB=8B=A4=20=EC=84=9C=EB=AA=85=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EA=B3=A0,=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A0=84?= =?UTF-8?q?=EC=B2=B4=EC=97=90=20=EB=8C=80=ED=95=B4=20=EC=84=9C=EB=AA=85?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/modules/crypto.ts | 15 ++++++-- src/lib/server/modules/crypto.ts | 35 +++++++++++++++++-- src/lib/server/services/auth.ts | 2 +- src/lib/server/services/client.ts | 2 +- src/lib/server/services/mek.ts | 23 +----------- src/lib/services/key.ts | 2 +- src/routes/(fullscreen)/key/export/service.ts | 13 +++---- .../client/{ => register}/verify/+server.ts | 0 src/routes/api/mek/register/+server.ts | 20 +++++------ .../api/mek/register/initial/+server.ts | 16 ++++----- 10 files changed, 73 insertions(+), 55 deletions(-) rename src/routes/api/client/{ => register}/verify/+server.ts (100%) diff --git a/src/lib/modules/crypto.ts b/src/lib/modules/crypto.ts index 0d82085..872265a 100644 --- a/src/lib/modules/crypto.ts +++ b/src/lib/modules/crypto.ts @@ -45,7 +45,7 @@ export const exportRSAKeyToBase64 = async (key: CryptoKey) => { return encodeToBase64((await exportRSAKey(key)).key); }; -export const encryptRSAPlaintext = async (plaintext: ArrayBuffer, publicKey: CryptoKey) => { +export const encryptRSAPlaintext = async (plaintext: BufferSource, publicKey: CryptoKey) => { return await window.crypto.subtle.encrypt( { name: "RSA-OAEP", @@ -55,7 +55,7 @@ export const encryptRSAPlaintext = async (plaintext: ArrayBuffer, publicKey: Cry ); }; -export const decryptRSACiphertext = async (ciphertext: ArrayBuffer, privateKey: CryptoKey) => { +export const decryptRSACiphertext = async (ciphertext: BufferSource, privateKey: CryptoKey) => { return await window.crypto.subtle.decrypt( { name: "RSA-OAEP", @@ -65,7 +65,7 @@ export const decryptRSACiphertext = async (ciphertext: ArrayBuffer, privateKey: ); }; -export const signRSAMessage = async (message: ArrayBuffer, privateKey: CryptoKey) => { +export const signRSAMessage = async (message: BufferSource, privateKey: CryptoKey) => { return await window.crypto.subtle.sign( { name: "RSA-PSS", @@ -100,3 +100,12 @@ export const makeAESKeyNonextractable = async (key: CryptoKey) => { export const exportAESKey = async (key: CryptoKey) => { return await window.crypto.subtle.exportKey("raw", key); }; + +export const signRequest = async (data: T, privateKey: CryptoKey) => { + const dataBuffer = new TextEncoder().encode(JSON.stringify(data)); + const signature = await signRSAMessage(dataBuffer, privateKey); + return JSON.stringify({ + data, + signature: encodeToBase64(signature), + }); +}; diff --git a/src/lib/server/modules/crypto.ts b/src/lib/server/modules/crypto.ts index 49201d9..38efc7f 100644 --- a/src/lib/server/modules/crypto.ts +++ b/src/lib/server/modules/crypto.ts @@ -1,5 +1,8 @@ +import { error } from "@sveltejs/kit"; import { constants, randomBytes, createPublicKey, publicEncrypt, verify } from "crypto"; import { promisify } from "util"; +import { z } from "zod"; +import { getClient } from "$lib/server/db/client"; const makePubKeyToPem = (pubKey: string) => `-----BEGIN PUBLIC KEY-----\n${pubKey}\n-----END PUBLIC KEY-----`; @@ -17,10 +20,10 @@ export const encryptAsymmetric = (data: Buffer, encPubKey: string) => { return publicEncrypt({ key: makePubKeyToPem(encPubKey), oaepHash: "sha256" }, data); }; -export const verifySignature = (data: string, signature: string, sigPubKey: string) => { +export const verifySignature = (data: Buffer, signature: string, sigPubKey: string) => { return verify( "rsa-sha256", - Buffer.from(data, "base64"), + data, { key: makePubKeyToPem(sigPubKey), padding: constants.RSA_PKCS1_PSS_PADDING, @@ -34,3 +37,31 @@ export const generateChallenge = async (length: number, encPubKey: string) => { const challenge = encryptAsymmetric(answer, encPubKey); return { answer, challenge }; }; + +export const parseSignedRequest = async ( + clientId: number, + data: unknown, + schema: T, +) => { + const zodRes = z + .object({ + data: schema, + signature: z.string().base64().nonempty(), + }) + .safeParse(data); + if (!zodRes.success) error(400, "Invalid request body"); + + const { data: parsedData, signature } = zodRes.data; + if (!parsedData) error(500, "Invalid request body"); + + const client = await getClient(clientId); + if (!client) { + error(500, "Invalid access token"); + } else if ( + !verifySignature(Buffer.from(JSON.stringify(parsedData)), signature, client.sigPubKey) + ) { + error(400, "Invalid signature"); + } + + return parsedData; +}; diff --git a/src/lib/server/services/auth.ts b/src/lib/server/services/auth.ts index aeaf858..83dbc33 100644 --- a/src/lib/server/services/auth.ts +++ b/src/lib/server/services/auth.ts @@ -149,7 +149,7 @@ export const upgradeToken = async ( const client = await getClient(challenge.clientId); if (!client) { error(500, "Invalid challenge answer"); - } else if (!verifySignature(answer, sigAnswer, client.sigPubKey)) { + } else if (!verifySignature(Buffer.from(answer, "base64"), sigAnswer, client.sigPubKey)) { error(401, "Invalid challenge answer signature"); } diff --git a/src/lib/server/services/client.ts b/src/lib/server/services/client.ts index 07729c0..004ef1a 100644 --- a/src/lib/server/services/client.ts +++ b/src/lib/server/services/client.ts @@ -104,7 +104,7 @@ export const verifyUserClient = async ( const client = await getClient(challenge.clientId); if (!client) { error(500, "Invalid challenge answer"); - } else if (!verifySignature(answer, sigAnswer, client.sigPubKey)) { + } else if (!verifySignature(Buffer.from(answer, "base64"), sigAnswer, client.sigPubKey)) { error(401, "Invalid challenge answer signature"); } diff --git a/src/lib/server/services/mek.ts b/src/lib/server/services/mek.ts index a358879..f2de646 100644 --- a/src/lib/server/services/mek.ts +++ b/src/lib/server/services/mek.ts @@ -1,18 +1,16 @@ import { error } from "@sveltejs/kit"; -import { getAllUserClients, getClient, setUserClientStateToActive } from "$lib/server/db/client"; +import { getAllUserClients, setUserClientStateToActive } from "$lib/server/db/client"; import { getAllValidClientMeks, registerInitialMek, registerActiveMek, getNextActiveMekVersion, } from "$lib/server/db/mek"; -import { verifySignature } from "$lib/server/modules/crypto"; import { isInitialMekNeeded } from "$lib/server/modules/mek"; interface NewClientMek { clientId: number; encMek: string; - sigEncMek: string; } export const getClientMekList = async (userId: number, clientId: number) => { @@ -30,19 +28,11 @@ export const registerInitialActiveMek = async ( userId: number, createdBy: number, encMek: string, - sigEncMek: string, ) => { if (!(await isInitialMekNeeded(userId))) { error(409, "Initial MEK already registered"); } - const client = await getClient(createdBy); - if (!client) { - error(500, "Invalid access token"); - } else if (!verifySignature(encMek, sigEncMek, client.sigPubKey)) { - error(400, "Invalid signature"); - } - await registerInitialMek(userId, createdBy, encMek); await setUserClientStateToActive(userId, createdBy); }; @@ -63,17 +53,6 @@ export const registerNewActiveMek = async ( error(400, "Invalid key list"); } - const client = await getClient(createdBy); - if (!client) { - error(500, "Invalid access token"); - } else if ( - !clientMeks.every(({ encMek, sigEncMek }) => - verifySignature(encMek, sigEncMek, client.sigPubKey), - ) - ) { - error(400, "Invalid signature"); - } - const newMekVersion = await getNextActiveMekVersion(userId); await registerActiveMek(userId, newMekVersion, createdBy, clientMeks); }; diff --git a/src/lib/services/key.ts b/src/lib/services/key.ts index 0183982..4d7b97a 100644 --- a/src/lib/services/key.ts +++ b/src/lib/services/key.ts @@ -28,7 +28,7 @@ export const requestClientRegistration = async ( const answer = await decryptRSACiphertext(decodeFromBase64(challenge), decryptKey); const sigAnswer = await signRSAMessage(answer, signKey); - res = await callAPI("/api/client/verify", { + res = await callAPI("/api/client/register/verify", { method: "POST", headers: { "Content-Type": "application/json", diff --git a/src/routes/(fullscreen)/key/export/service.ts b/src/routes/(fullscreen)/key/export/service.ts index 12d62c7..b26ff61 100644 --- a/src/routes/(fullscreen)/key/export/service.ts +++ b/src/routes/(fullscreen)/key/export/service.ts @@ -1,6 +1,6 @@ import { callAPI } from "$lib/hooks"; import { storeRSAKey } from "$lib/indexedDB"; -import { encodeToBase64, encryptRSAPlaintext, signRSAMessage } from "$lib/modules/crypto"; +import { encodeToBase64, encryptRSAPlaintext, signRequest } from "$lib/modules/crypto"; import type { ClientKeys } from "$lib/stores"; export { requestTokenUpgrade } from "$lib/services/auth"; @@ -47,16 +47,17 @@ export const requestInitialMekRegistration = async ( signKey: CryptoKey, ) => { const mekDraftEncrypted = await encryptRSAPlaintext(mekDraft, encryptKey); - const mekDraftEncryptedSigned = await signRSAMessage(mekDraftEncrypted, signKey); const res = await callAPI("/api/mek/register/initial", { method: "POST", headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ - mek: encodeToBase64(mekDraftEncrypted), - sigMek: encodeToBase64(mekDraftEncryptedSigned), - }), + body: await signRequest( + { + mek: encodeToBase64(mekDraftEncrypted), + }, + signKey, + ), }); return res.ok || res.status === 409; }; diff --git a/src/routes/api/client/verify/+server.ts b/src/routes/api/client/register/verify/+server.ts similarity index 100% rename from src/routes/api/client/verify/+server.ts rename to src/routes/api/client/register/verify/+server.ts diff --git a/src/routes/api/mek/register/+server.ts b/src/routes/api/mek/register/+server.ts index dfab18e..ab454a7 100644 --- a/src/routes/api/mek/register/+server.ts +++ b/src/routes/api/mek/register/+server.ts @@ -1,33 +1,31 @@ -import { error, text } from "@sveltejs/kit"; +import { text } from "@sveltejs/kit"; import { z } from "zod"; import { authorize } from "$lib/server/modules/auth"; +import { parseSignedRequest } from "$lib/server/modules/crypto"; import { registerNewActiveMek } from "$lib/server/services/mek"; import type { RequestHandler } from "@sveltejs/kit"; export const POST: RequestHandler = async ({ request, cookies }) => { const { userId, clientId } = await authorize(cookies, "activeClient"); - - const zodRes = z - .object({ + const { meks } = await parseSignedRequest( + clientId, + await request.json(), + z.object({ meks: z.array( z.object({ clientId: z.number().int().positive(), mek: z.string().base64().nonempty(), - sigMek: z.string().base64().nonempty(), }), ), - }) - .safeParse(await request.json()); - if (!zodRes.success) error(400, "Invalid request body"); - const { meks } = zodRes.data; + }), + ); await registerNewActiveMek( userId, clientId, - meks.map(({ clientId, mek, sigMek }) => ({ + meks.map(({ clientId, mek }) => ({ clientId, encMek: mek, - sigEncMek: sigMek, })), ); return text("MEK registered", { headers: { "Content-Type": "text/plain" } }); diff --git a/src/routes/api/mek/register/initial/+server.ts b/src/routes/api/mek/register/initial/+server.ts index 771b886..0f495ff 100644 --- a/src/routes/api/mek/register/initial/+server.ts +++ b/src/routes/api/mek/register/initial/+server.ts @@ -1,6 +1,7 @@ import { error, text } from "@sveltejs/kit"; import { z } from "zod"; import { authenticate } from "$lib/server/modules/auth"; +import { parseSignedRequest } from "$lib/server/modules/crypto"; import { registerInitialActiveMek } from "$lib/server/services/mek"; import type { RequestHandler } from "@sveltejs/kit"; @@ -10,15 +11,14 @@ export const POST: RequestHandler = async ({ request, cookies }) => { error(403, "Forbidden"); } - const zodRes = z - .object({ + const { mek } = await parseSignedRequest( + clientId, + await request.json(), + z.object({ mek: z.string().base64().nonempty(), - sigMek: z.string().base64().nonempty(), - }) - .safeParse(await request.json()); - if (!zodRes.success) error(400, "Invalid request body"); - const { mek, sigMek } = zodRes.data; + }), + ); - await registerInitialActiveMek(userId, clientId, mek, sigMek); + await registerInitialActiveMek(userId, clientId, mek); return text("MEK registered", { headers: { "Content-Type": "text/plain" } }); };