mirror of
https://github.com/kccleoc/seedpgp-web.git
synced 2026-03-07 09:57:50 +08:00
Implement security patches: CSP headers, console disabling, key rotation, clipboard security, network blocking, log cleanup, and PGP validation
This commit is contained in:
@@ -29,8 +29,12 @@ function bytesToBase64(bytes: Uint8Array): string {
|
||||
* @private
|
||||
*/
|
||||
let sessionKey: CryptoKey | null = null;
|
||||
let keyCreatedAt = 0;
|
||||
let keyOperationCount = 0;
|
||||
const KEY_ALGORITHM = 'AES-GCM';
|
||||
const KEY_LENGTH = 256;
|
||||
const KEY_ROTATION_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
||||
const MAX_KEY_OPERATIONS = 1000; // Rotate after N operations
|
||||
|
||||
/**
|
||||
* An object containing encrypted data and necessary metadata for decryption.
|
||||
@@ -55,21 +59,39 @@ export interface EncryptedBlob {
|
||||
* This function must be called before any encryption or decryption can occur.
|
||||
* @returns A promise that resolves to the generated or existing CryptoKey.
|
||||
*/
|
||||
/**
|
||||
* Get or create session key with automatic rotation.
|
||||
* Key rotates every 5 minutes or after 1000 operations.
|
||||
*/
|
||||
export async function getSessionKey(): Promise<CryptoKey> {
|
||||
if (sessionKey) {
|
||||
return sessionKey;
|
||||
const now = Date.now();
|
||||
const shouldRotate =
|
||||
!sessionKey ||
|
||||
(now - keyCreatedAt) > KEY_ROTATION_INTERVAL ||
|
||||
keyOperationCount > MAX_KEY_OPERATIONS;
|
||||
|
||||
if (shouldRotate) {
|
||||
if (sessionKey) {
|
||||
// Note: CryptoKey cannot be explicitly zeroed, but dereferencing helps GC
|
||||
const elapsed = now - keyCreatedAt;
|
||||
console.debug?.(`Rotating session key (age: ${elapsed}ms, ops: ${keyOperationCount})`);
|
||||
sessionKey = null;
|
||||
}
|
||||
|
||||
const key = await window.crypto.subtle.generateKey(
|
||||
{
|
||||
name: KEY_ALGORITHM,
|
||||
length: KEY_LENGTH,
|
||||
},
|
||||
false, // non-exportable
|
||||
['encrypt', 'decrypt'],
|
||||
);
|
||||
sessionKey = key;
|
||||
keyCreatedAt = now;
|
||||
keyOperationCount = 0;
|
||||
}
|
||||
|
||||
const key = await window.crypto.subtle.generateKey(
|
||||
{
|
||||
name: KEY_ALGORITHM,
|
||||
length: KEY_LENGTH,
|
||||
},
|
||||
false, // non-exportable
|
||||
['encrypt', 'decrypt'],
|
||||
);
|
||||
sessionKey = key;
|
||||
return key;
|
||||
return sessionKey!;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,7 +100,10 @@ export async function getSessionKey(): Promise<CryptoKey> {
|
||||
* @returns A promise that resolves to an EncryptedBlob.
|
||||
*/
|
||||
export async function encryptJsonToBlob<T>(data: T): Promise<EncryptedBlob> {
|
||||
if (!sessionKey) {
|
||||
const key = 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.');
|
||||
}
|
||||
|
||||
@@ -90,7 +115,7 @@ export async function encryptJsonToBlob<T>(data: T): Promise<EncryptedBlob> {
|
||||
name: KEY_ALGORITHM,
|
||||
iv: new Uint8Array(iv),
|
||||
},
|
||||
sessionKey,
|
||||
key,
|
||||
plaintext,
|
||||
);
|
||||
|
||||
@@ -108,7 +133,10 @@ 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> {
|
||||
if (!sessionKey) {
|
||||
const key = await getSessionKey(); // Ensures key exists and handles rotation
|
||||
keyOperationCount++; // Track operations for rotation
|
||||
|
||||
if (!key) {
|
||||
throw new Error('Session key not initialized or has been destroyed.');
|
||||
}
|
||||
if (blob.v !== 1 || blob.alg !== 'A256GCM') {
|
||||
@@ -123,7 +151,7 @@ export async function decryptBlobToJson<T>(blob: EncryptedBlob): Promise<T> {
|
||||
name: KEY_ALGORITHM,
|
||||
iv: new Uint8Array(iv),
|
||||
},
|
||||
sessionKey,
|
||||
key,
|
||||
new Uint8Array(ciphertext),
|
||||
);
|
||||
|
||||
@@ -137,6 +165,18 @@ export async function decryptBlobToJson<T>(blob: EncryptedBlob): Promise<T> {
|
||||
*/
|
||||
export function destroySessionKey(): void {
|
||||
sessionKey = null;
|
||||
keyOperationCount = 0;
|
||||
keyCreatedAt = 0;
|
||||
}
|
||||
|
||||
// Auto-clear session key when page becomes hidden
|
||||
if (typeof document !== 'undefined') {
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.hidden) {
|
||||
console.debug?.('Page hidden - clearing session key for security');
|
||||
destroySessionKey();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user