Files
WebDrop/web/src/core-bridge/worker/domainCrypto.ts
T

23 lines
1.5 KiB
TypeScript

import { bytes, b64, unb64, sealJson, openJson } from './accountCrypto';
export interface ObjectContext { spaceId: string; channelId: string; domain: 'chat' | 'files'; epoch: number; itemId: string }
const label = (c: ObjectContext) => JSON.stringify(['webdrop/object-key/v1', c.spaceId, c.channelId, c.domain, c.epoch, c.itemId]);
export const randomDomainKey = () => b64(crypto.getRandomValues(new Uint8Array(32)));
async function aes(value: string): Promise<CryptoKey> {
const material = unb64(value);
try { if (material.length !== 32) throw new Error('数据域密钥格式无效'); return await crypto.subtle.importKey('raw', bytes(material), 'AES-GCM', false, ['encrypt', 'decrypt']); }
finally { material.fill(0); }
}
export async function newObjectKey(domainKey: string, context: ObjectContext): Promise<{ key: CryptoKey; envelope: string }> {
const raw = crypto.getRandomValues(new Uint8Array(32));
try {
const key = await crypto.subtle.importKey('raw', bytes(raw), 'AES-GCM', false, ['encrypt', 'decrypt']);
return { key, envelope: await sealJson({ key: b64(raw) }, await aes(domainKey), label(context)) };
} finally { raw.fill(0); }
}
export async function unwrapObjectKey(domainKey: string, envelope: string, context: ObjectContext): Promise<CryptoKey> {
const value = await openJson(envelope, await aes(domainKey), label(context)) as { key?: unknown };
if (typeof value.key !== 'string') throw new Error('对象密钥封装格式无效');
return aes(value.key);
}