키 가져오기 기능 추가

This commit is contained in:
static
2025-07-12 01:28:44 +09:00
parent c47885d571
commit eac81abe5a
10 changed files with 304 additions and 118 deletions

View File

@@ -46,6 +46,56 @@ export const exportRSAKeyToBase64 = async (key: CryptoKey) => {
return encodeToBase64((await exportRSAKey(key)).key);
};
export const importEncryptionKeyPairFromBase64 = async (
encryptKeyBase64: string,
decryptKeyBase64: string,
) => {
const algorithm: RsaHashedImportParams = {
name: "RSA-OAEP",
hash: "SHA-256",
};
const encryptKey = await window.crypto.subtle.importKey(
"spki",
decodeFromBase64(encryptKeyBase64),
algorithm,
true,
["encrypt", "wrapKey"],
);
const decryptKey = await window.crypto.subtle.importKey(
"pkcs8",
decodeFromBase64(decryptKeyBase64),
algorithm,
true,
["decrypt", "unwrapKey"],
);
return { encryptKey, decryptKey };
};
export const importSigningKeyPairFromBase64 = async (
signKeyBase64: string,
verifyKeyBase64: string,
) => {
const algorithm: RsaHashedImportParams = {
name: "RSA-PSS",
hash: "SHA-256",
};
const signKey = await window.crypto.subtle.importKey(
"pkcs8",
decodeFromBase64(signKeyBase64),
algorithm,
true,
["sign"],
);
const verifyKey = await window.crypto.subtle.importKey(
"spki",
decodeFromBase64(verifyKeyBase64),
algorithm,
true,
["verify"],
);
return { signKey, verifyKey };
};
export const makeRSAKeyNonextractable = async (key: CryptoKey) => {
const { key: exportedKey, format } = await exportRSAKey(key);
return await window.crypto.subtle.importKey(

65
src/lib/modules/key.ts Normal file
View File

@@ -0,0 +1,65 @@
import { z } from "zod";
import { storeClientKey } from "$lib/indexedDB";
import type { ClientKeys } from "$lib/stores";
const serializedClientKeysSchema = z.intersection(
z.object({
generator: z.literal("ArkVault"),
exportedAt: z.string().datetime(),
}),
z.object({
version: z.literal(1),
encryptKey: z.string().base64().nonempty(),
decryptKey: z.string().base64().nonempty(),
signKey: z.string().base64().nonempty(),
verifyKey: z.string().base64().nonempty(),
}),
);
type SerializedClientKeys = z.infer<typeof serializedClientKeysSchema>;
type DeserializedClientKeys = {
encryptKeyBase64: string;
decryptKeyBase64: string;
signKeyBase64: string;
verifyKeyBase64: string;
};
export const serializeClientKeys = ({
encryptKeyBase64,
decryptKeyBase64,
signKeyBase64,
verifyKeyBase64,
}: DeserializedClientKeys) => {
return JSON.stringify({
version: 1,
generator: "ArkVault",
exportedAt: new Date().toISOString(),
encryptKey: encryptKeyBase64,
decryptKey: decryptKeyBase64,
signKey: signKeyBase64,
verifyKey: verifyKeyBase64,
} satisfies SerializedClientKeys);
};
export const deserializeClientKeys = (serialized: string) => {
const zodRes = serializedClientKeysSchema.safeParse(JSON.parse(serialized));
if (zodRes.success) {
return {
encryptKeyBase64: zodRes.data.encryptKey,
decryptKeyBase64: zodRes.data.decryptKey,
signKeyBase64: zodRes.data.signKey,
verifyKeyBase64: zodRes.data.verifyKey,
} satisfies DeserializedClientKeys;
}
return undefined;
};
export const storeClientKeys = async (clientKeys: ClientKeys) => {
await Promise.all([
storeClientKey(clientKeys.encryptKey, "encrypt"),
storeClientKey(clientKeys.decryptKey, "decrypt"),
storeClientKey(clientKeys.signKey, "sign"),
storeClientKey(clientKeys.verifyKey, "verify"),
]);
};

View File

@@ -2,18 +2,23 @@ import { callGetApi, callPostApi } from "$lib/hooks";
import { storeMasterKeys } from "$lib/indexedDB";
import {
encodeToBase64,
exportRSAKeyToBase64,
decryptChallenge,
signMessageRSA,
unwrapMasterKey,
signMasterKeyWrapped,
verifyMasterKeyWrapped,
} from "$lib/modules/crypto";
import type {
ClientRegisterRequest,
ClientRegisterResponse,
ClientRegisterVerifyRequest,
InitialHmacSecretRegisterRequest,
MasterKeyListResponse,
InitialMasterKeyRegisterRequest,
} from "$lib/server/schemas";
import { masterKeyStore } from "$lib/stores";
import { requestSessionUpgrade } from "$lib/services/auth";
import { masterKeyStore, type ClientKeys } from "$lib/stores";
export const requestClientRegistration = async (
encryptKeyBase64: string,
@@ -38,6 +43,35 @@ export const requestClientRegistration = async (
return res.ok;
};
export const requestClientRegistrationAndSessionUpgrade = async (
{ encryptKey, decryptKey, signKey, verifyKey }: ClientKeys,
force: boolean,
) => {
const encryptKeyBase64 = await exportRSAKeyToBase64(encryptKey);
const verifyKeyBase64 = await exportRSAKeyToBase64(verifyKey);
const [res, error] = await requestSessionUpgrade(
encryptKeyBase64,
decryptKey,
verifyKeyBase64,
signKey,
force,
);
if (error === undefined) return [res] as const;
if (
error === "Unregistered client" &&
!(await requestClientRegistration(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey))
) {
return [false] as const;
} else if (error === "Already logged in") {
return [false, force ? undefined : error] as const;
}
return [
(await requestSessionUpgrade(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey))[0],
] as const;
};
export const requestMasterKeyDownload = async (decryptKey: CryptoKey, verifyKey: CryptoKey) => {
const res = await callGetApi("/api/mek/list");
if (!res.ok) return false;
@@ -68,3 +102,23 @@ export const requestMasterKeyDownload = async (decryptKey: CryptoKey, verifyKey:
return true;
};
export const requestInitialMasterKeyAndHmacSecretRegistration = async (
masterKeyWrapped: string,
hmacSecretWrapped: string,
signKey: CryptoKey,
) => {
let res = await callPostApi<InitialMasterKeyRegisterRequest>("/api/mek/register/initial", {
mek: masterKeyWrapped,
mekSig: await signMasterKeyWrapped(masterKeyWrapped, 1, signKey),
});
if (!res.ok) {
return res.status === 403 || res.status === 409;
}
res = await callPostApi<InitialHmacSecretRegisterRequest>("/api/hsk/register/initial", {
mekVersion: 1,
hsk: hmacSecretWrapped,
});
return res.ok;
};