security improvement and bugs fixing; modify makefile

This commit is contained in:
LC mac
2026-02-18 03:24:05 +08:00
parent 127b479f4f
commit 4da39b7b89
21 changed files with 52111 additions and 930 deletions

View File

@@ -29,6 +29,7 @@ function bytesToBase64(bytes: Uint8Array): string {
* @private
*/
let sessionKey: CryptoKey | null = null;
let sessionKeyId: string | null = null;
let keyCreatedAt = 0;
let keyOperationCount = 0;
const KEY_ALGORITHM = 'AES-GCM';
@@ -46,6 +47,7 @@ export interface EncryptedBlob {
* uses `{ name: "AES-GCM", length: 256 }`.
*/
alg: 'A256GCM';
keyId: string; // The ID of the key used for encryption
iv_b64: string; // Initialization Vector (base64)
ct_b64: string; // Ciphertext (base64)
}
@@ -56,7 +58,7 @@ export interface EncryptedBlob {
* Get or create session key with automatic rotation.
* Key rotates every 5 minutes or after 1000 operations.
*/
export async function getSessionKey(): Promise<CryptoKey> {
export async function getSessionKey(): Promise<{ key: CryptoKey; keyId: string }> {
const now = Date.now();
const shouldRotate =
!sessionKey ||
@@ -69,9 +71,11 @@ export async function getSessionKey(): Promise<CryptoKey> {
const elapsed = now - keyCreatedAt;
console.debug?.(`Rotating session key (age: ${elapsed}ms, ops: ${keyOperationCount})`);
sessionKey = null;
sessionKeyId = null;
}
const key = await window.crypto.subtle.generateKey(
// ✅ FIXED: Use global `crypto` instead of `window.crypto` for Node.js/Bun compatibility
const key = await crypto.subtle.generateKey(
{
name: KEY_ALGORITHM,
length: KEY_LENGTH,
@@ -80,11 +84,12 @@ export async function getSessionKey(): Promise<CryptoKey> {
['encrypt', 'decrypt'],
);
sessionKey = key;
sessionKeyId = crypto.randomUUID();
keyCreatedAt = now;
keyOperationCount = 0;
}
return sessionKey!;
return { key: sessionKey!, keyId: sessionKeyId! };
}
/**
@@ -93,17 +98,19 @@ export async function getSessionKey(): Promise<CryptoKey> {
* @returns A promise that resolves to an EncryptedBlob.
*/
export async function encryptJsonToBlob<T>(data: T): Promise<EncryptedBlob> {
const key = await getSessionKey(); // Ensures key exists and handles rotation
const { key, keyId } = await getSessionKey(); // Ensures key exists and handles rotation
keyOperationCount++; // Track operations for rotation
if (!key) {
throw new Error('Session key not initialized. Call getSessionKey() first.');
throw new Error('Session key not initialized or has been destroyed.');
}
const iv = window.crypto.getRandomValues(new Uint8Array(12)); // 96-bit IV is recommended for AES-GCM
// ✅ FIXED: Use global `crypto` instead of `window.crypto`
const iv = crypto.getRandomValues(new Uint8Array(12)); // 96-bit IV is recommended for AES-GCM
const plaintext = new TextEncoder().encode(JSON.stringify(data));
const ciphertext = await window.crypto.subtle.encrypt(
// ✅ FIXED: Use global `crypto` instead of `window.crypto`
const ciphertext = await crypto.subtle.encrypt(
{
name: KEY_ALGORITHM,
iv: new Uint8Array(iv),
@@ -115,6 +122,7 @@ export async function encryptJsonToBlob<T>(data: T): Promise<EncryptedBlob> {
return {
v: 1,
alg: 'A256GCM',
keyId: keyId,
iv_b64: bytesToBase64(iv),
ct_b64: bytesToBase64(new Uint8Array(ciphertext)),
};
@@ -126,7 +134,7 @@ export async function encryptJsonToBlob<T>(data: T): Promise<EncryptedBlob> {
* @returns A promise that resolves to the original decrypted object.
*/
export async function decryptBlobToJson<T>(blob: EncryptedBlob): Promise<T> {
const key = await getSessionKey(); // Ensures key exists and handles rotation
const { key, keyId } = await getSessionKey(); // Ensures key exists and handles rotation
keyOperationCount++; // Track operations for rotation
if (!key) {
@@ -135,11 +143,15 @@ export async function decryptBlobToJson<T>(blob: EncryptedBlob): Promise<T> {
if (blob.v !== 1 || blob.alg !== 'A256GCM') {
throw new Error('Invalid or unsupported encrypted blob format.');
}
if (blob.keyId !== keyId) {
throw new Error('Session expired. The encryption key has rotated. Please re-enter your seed phrase.');
}
const iv = base64ToBytes(blob.iv_b64);
const ciphertext = base64ToBytes(blob.ct_b64);
const decrypted = await window.crypto.subtle.decrypt(
// ✅ FIXED: Use global `crypto` instead of `window.crypto`
const decrypted = await crypto.subtle.decrypt(
{
name: KEY_ALGORITHM,
iv: new Uint8Array(iv),
@@ -158,6 +170,7 @@ export async function decryptBlobToJson<T>(blob: EncryptedBlob): Promise<T> {
*/
export function destroySessionKey(): void {
sessionKey = null;
sessionKeyId = null;
keyOperationCount = 0;
keyCreatedAt = 0;
}