mirror of
https://github.com/kccleoc/seedpgp-web.git
synced 2026-03-07 09:57:50 +08:00
security improvement and bugs fixing; modify makefile
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user