클라이언트 승인 대기 페이지 구현

This commit is contained in:
static
2024-12-31 21:58:13 +09:00
parent ccad4fbd8b
commit e5cbd46b35
13 changed files with 243 additions and 59 deletions

View File

@@ -10,7 +10,7 @@ interface KeyExportState {
signKeyBase64: string;
verifyKeyBase64: string;
mekDraft: ArrayBuffer;
masterKeyWrapped: ArrayBuffer;
}
const useAutoNull = <T>(value: T | null) => {

View File

@@ -1,26 +1,36 @@
import { Dexie, type EntityTable } from "dexie";
type RSAKeyUsage = "encrypt" | "decrypt" | "sign" | "verify";
type ClientKeyUsage = "encrypt" | "decrypt" | "sign" | "verify";
interface RSAKey {
usage: RSAKeyUsage;
interface ClientKey {
usage: ClientKeyUsage;
key: CryptoKey;
}
type MasterKeyState = "active" | "retired";
interface MasterKey {
version: number;
state: MasterKeyState;
key: CryptoKey;
}
const keyStore = new Dexie("keyStore") as Dexie & {
rsaKey: EntityTable<RSAKey, "usage">;
clientKey: EntityTable<ClientKey, "usage">;
masterKey: EntityTable<MasterKey, "version">;
};
keyStore.version(1).stores({
rsaKey: "usage",
clientKey: "usage",
masterKey: "version",
});
export const getRSAKey = async (usage: RSAKeyUsage) => {
const key = await keyStore.rsaKey.get(usage);
export const getClientKey = async (usage: ClientKeyUsage) => {
const key = await keyStore.clientKey.get(usage);
return key?.key ?? null;
};
export const storeRSAKey = async (key: CryptoKey, usage: RSAKeyUsage) => {
export const storeClientKey = async (key: CryptoKey, usage: ClientKeyUsage) => {
switch (usage) {
case "encrypt":
case "verify":
@@ -39,5 +49,16 @@ export const storeRSAKey = async (key: CryptoKey, usage: RSAKeyUsage) => {
}
break;
}
await keyStore.rsaKey.put({ usage, key });
await keyStore.clientKey.put({ usage, key });
};
export const getMasterKeys = async () => {
return await keyStore.masterKey.toArray();
};
export const storeMasterKeys = async (keys: MasterKey[]) => {
if (keys.some(({ key }) => key.extractable)) {
throw new Error("Master keys must be non-extractable");
}
await keyStore.masterKey.bulkPut(keys);
};

View File

@@ -18,7 +18,7 @@ export const generateRSAKeyPair = async (purpose: RSAKeyPurpose) => {
hash: "SHA-256",
} satisfies RsaHashedKeyGenParams,
true,
purpose === "encryption" ? ["encrypt", "decrypt"] : ["sign", "verify"],
purpose === "encryption" ? ["encrypt", "decrypt", "wrapKey", "unwrapKey"] : ["sign", "verify"],
);
};
@@ -101,6 +101,33 @@ export const exportAESKey = async (key: CryptoKey) => {
return await window.crypto.subtle.exportKey("raw", key);
};
export const wrapAESKeyUsingRSA = async (aesKey: CryptoKey, rsaPublicKey: CryptoKey) => {
return await window.crypto.subtle.wrapKey("raw", aesKey, rsaPublicKey, {
name: "RSA-OAEP",
} satisfies RsaOaepParams);
};
export const unwrapAESKeyUsingRSA = async (wrappedKey: BufferSource, rsaPrivateKey: CryptoKey) => {
return await window.crypto.subtle.unwrapKey(
"raw",
wrappedKey,
rsaPrivateKey,
{
name: "RSA-OAEP",
} satisfies RsaOaepParams,
{
name: "AES-GCM",
length: 256,
} satisfies AesKeyGenParams,
true,
["encrypt", "decrypt"],
);
};
export const digestSHA256 = async (data: BufferSource) => {
return await window.crypto.subtle.digest("SHA-256", data);
};
export const signRequest = async <T>(data: T, privateKey: CryptoKey) => {
const dataBuffer = new TextEncoder().encode(JSON.stringify(data));
const signature = await signRSAMessage(dataBuffer, privateKey);

View File

@@ -7,5 +7,11 @@ export interface ClientKeys {
verifyKey: CryptoKey;
}
export interface MasterKey {
state: "active" | "retired" | "dead";
masterKey: CryptoKey;
}
export const clientKeyStore = writable<ClientKeys | null>(null);
export const mekStore = writable<Map<number, CryptoKey>>(new Map());
export const masterKeyStore = writable<Map<number, MasterKey> | null>(null);