암호 키 생성 페이지에서 검증키와 서명키를 함께 생성하도록 변경

This commit is contained in:
static
2024-12-31 04:18:34 +09:00
parent a64e85848c
commit 0ef252913a
10 changed files with 225 additions and 82 deletions

View File

@@ -3,13 +3,13 @@
import { goto } from "$app/navigation";
import { Button, TextButton } from "$lib/components/buttons";
import { BottomDiv } from "$lib/components/divs";
import { keyPairStore } from "$lib/stores";
import { keyPairsStore } from "$lib/stores";
import BeforeContinueBottomSheet from "./BeforeContinueBottomSheet.svelte";
import BeforeContinueModal from "./BeforeContinueModal.svelte";
import {
createBlobFromKeyPairBase64,
requestPubKeyRegistration,
storeKeyPairPersistently,
makeKeyPairsSaveable,
requestClientRegistration,
storeKeyPairsPersistently,
requestTokenUpgrade,
requestInitialMekRegistration,
} from "./service";
@@ -22,8 +22,9 @@
let isBeforeContinueBottomSheetOpen = $state(false);
const exportKeyPair = () => {
const keyPairBlob = createBlobFromKeyPairBase64(data.pubKeyBase64, data.privKeyBase64);
saveAs(keyPairBlob, "arkvalut-keypair.pem");
const keyPairsSaveable = makeKeyPairsSaveable(data.encKeyPair, data.sigKeyPair);
const keyPairsBlob = new Blob([JSON.stringify(keyPairsSaveable)], { type: "application/json" });
saveAs(keyPairsBlob, "arkvalut-key.json");
if (!isBeforeContinueBottomSheetOpen) {
setTimeout(() => {
@@ -33,7 +34,7 @@
};
const registerPubKey = async () => {
if (!$keyPairStore) {
if (!$keyPairsStore) {
throw new Error("Failed to find key pair");
}
@@ -41,15 +42,31 @@
isBeforeContinueBottomSheetOpen = false;
try {
if (!(await requestPubKeyRegistration(data.pubKeyBase64, $keyPairStore.privateKey)))
if (
!(await requestClientRegistration(
data.encKeyPair.pubKeyBase64,
$keyPairsStore.encKeyPair.privateKey,
data.sigKeyPair.pubKeyBase64,
$keyPairsStore.sigKeyPair.privateKey,
))
)
throw new Error("Failed to register public key");
await storeKeyPairPersistently($keyPairStore);
await storeKeyPairsPersistently($keyPairsStore.encKeyPair, $keyPairsStore.sigKeyPair);
if (!(await requestTokenUpgrade(data.pubKeyBase64)))
if (
!(await requestTokenUpgrade(
data.encKeyPair.pubKeyBase64,
$keyPairsStore.encKeyPair.privateKey,
data.sigKeyPair.pubKeyBase64,
$keyPairsStore.sigKeyPair.privateKey,
))
)
throw new Error("Failed to upgrade token");
if (!(await requestInitialMekRegistration(data.mekDraft, $keyPairStore.publicKey)))
if (
!(await requestInitialMekRegistration(data.mekDraft, $keyPairsStore.encKeyPair.publicKey))
)
throw new Error("Failed to register initial MEK");
await goto(data.redirectPath);

View File

@@ -1,59 +1,117 @@
import { callAPI } from "$lib/hooks";
import { storeKeyPairIntoIndexedDB } from "$lib/indexedDB";
import { storeRSAKey } from "$lib/indexedDB";
import {
encodeToBase64,
decodeFromBase64,
encryptRSAPlaintext,
decryptRSACiphertext,
signRSAMessage,
} from "$lib/modules/crypto";
export const createBlobFromKeyPairBase64 = (pubKeyBase64: string, privKeyBase64: string) => {
const pubKeyFormatted = pubKeyBase64.match(/.{1,64}/g)?.join("\n");
const privKeyFormatted = privKeyBase64.match(/.{1,64}/g)?.join("\n");
if (!pubKeyFormatted || !privKeyFormatted) {
throw new Error("Failed to format key pair");
}
const pubKeyPem = `-----BEGIN RSA PUBLIC KEY-----\n${pubKeyFormatted}\n-----END RSA PUBLIC KEY-----`;
const privKeyPem = `-----BEGIN RSA PRIVATE KEY-----\n${privKeyFormatted}\n-----END RSA PRIVATE KEY-----`;
return new Blob([`${pubKeyPem}\n${privKeyPem}\n`], { type: "text/plain" });
type ExportedKeyPairs = {
generator: "ArkVault";
exportedAt: Date;
} & {
version: 1;
encKeyPair: { pubKey: string; privKey: string };
sigKeyPair: { pubKey: string; privKey: string };
};
export const requestPubKeyRegistration = async (pubKeyBase64: string, privateKey: CryptoKey) => {
export const makeKeyPairsSaveable = (
encKeyPair: { pubKeyBase64: string; privKeyBase64: string },
sigKeyPair: { pubKeyBase64: string; privKeyBase64: string },
) => {
return {
version: 1,
generator: "ArkVault",
exportedAt: new Date(),
encKeyPair: {
pubKey: encKeyPair.pubKeyBase64,
privKey: encKeyPair.privKeyBase64,
},
sigKeyPair: {
pubKey: sigKeyPair.pubKeyBase64,
privKey: sigKeyPair.privKeyBase64,
},
} 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({ pubKey: pubKeyBase64 }),
body: JSON.stringify({
encPubKey: encPubKeyBase64,
sigPubKey: sigPubKeyBase64,
}),
});
if (!res.ok) return false;
const data = await res.json();
const challenge = data.challenge as string;
const answer = await decryptRSACiphertext(decodeFromBase64(challenge), privateKey);
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) }),
body: JSON.stringify({
answer: encodeToBase64(answer),
sigAnswer: encodeToBase64(sigAnswer),
}),
});
return res.ok;
};
export const storeKeyPairPersistently = async (keyPair: CryptoKeyPair) => {
await storeKeyPairIntoIndexedDB(keyPair.publicKey, keyPair.privateKey);
export const storeKeyPairsPersistently = async (
encKeyPair: CryptoKeyPair,
sigKeyPair: CryptoKeyPair,
) => {
await storeRSAKey(encKeyPair.publicKey, "encrypt");
await storeRSAKey(encKeyPair.privateKey, "decrypt");
await storeRSAKey(sigKeyPair.publicKey, "verify");
await storeRSAKey(sigKeyPair.privateKey, "sign");
};
export const requestTokenUpgrade = async (pubKeyBase64: string) => {
const res = await fetch("/api/auth/upgradeToken", {
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({ pubKey: pubKeyBase64 }),
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;
};

View File

@@ -3,14 +3,15 @@
import { Button, TextButton } from "$lib/components/buttons";
import { TitleDiv, BottomDiv } from "$lib/components/divs";
import { gotoStateful } from "$lib/hooks";
import { keyPairStore } from "$lib/stores";
import { keyPairsStore } from "$lib/stores";
import Order from "./Order.svelte";
import { generateKeyPair, generateMekDraft } from "./service";
import { generateKeyPairs, generateMekDraft } from "./service";
import IconKey from "~icons/material-symbols/key";
let { data } = $props();
// TODO: Update
const orders = [
{
title: "암호 키는 공개 키와 개인 키로 구성돼요.",
@@ -33,19 +34,19 @@
const generate = async () => {
// TODO: Loading indicator
const { pubKeyBase64, privKeyBase64 } = await generateKeyPair();
const { encKeyPair, sigKeyPair } = await generateKeyPairs();
const { mekDraft } = await generateMekDraft();
await gotoStateful("/key/export", {
redirectPath: data.redirectPath,
pubKeyBase64,
privKeyBase64,
encKeyPair,
sigKeyPair,
mekDraft,
});
};
$effect(() => {
if ($keyPairStore) {
if ($keyPairsStore) {
goto(data.redirectPath);
}
});

View File

@@ -1,26 +1,43 @@
import {
encodeToBase64,
generateRSAKeyPair,
generateRSAEncKeyPair,
generateRSASigKeyPair,
makeRSAKeyNonextractable,
exportRSAKey,
generateAESKey,
makeAESKeyNonextractable,
exportAESKey,
} from "$lib/modules/crypto";
import { keyPairStore, mekStore } from "$lib/stores";
import { keyPairsStore, mekStore } from "$lib/stores";
export const generateKeyPair = async () => {
const keyPair = await generateRSAKeyPair();
const privKeySecured = await makeRSAKeyNonextractable(keyPair.privateKey, "private");
const exportRSAKeyToBase64 = async (key: CryptoKey, type: "public" | "private") => {
return encodeToBase64((await exportRSAKey(key, type)).key);
};
keyPairStore.set({
publicKey: keyPair.publicKey,
privateKey: privKeySecured,
export const generateKeyPairs = async () => {
const encKeyPair = await generateRSAEncKeyPair();
const sigKeyPair = await generateRSASigKeyPair();
keyPairsStore.set({
encKeyPair: {
publicKey: encKeyPair.publicKey,
privateKey: await makeRSAKeyNonextractable(encKeyPair.privateKey, "private"),
},
sigKeyPair: {
publicKey: sigKeyPair.publicKey,
privateKey: await makeRSAKeyNonextractable(sigKeyPair.privateKey, "private"),
},
});
return {
pubKeyBase64: encodeToBase64((await exportRSAKey(keyPair.publicKey, "public")).key),
privKeyBase64: encodeToBase64((await exportRSAKey(keyPair.privateKey, "private")).key),
encKeyPair: {
pubKeyBase64: await exportRSAKeyToBase64(encKeyPair.publicKey, "public"),
privKeyBase64: await exportRSAKeyToBase64(encKeyPair.privateKey, "private"),
},
sigKeyPair: {
pubKeyBase64: await exportRSAKeyToBase64(sigKeyPair.publicKey, "public"),
privKeyBase64: await exportRSAKeyToBase64(sigKeyPair.privateKey, "private"),
},
};
};