Remove decryption and api key storage
This commit is contained in:
parent
2e12edc249
commit
15b76f330b
8 changed files with 326 additions and 233 deletions
|
|
@ -1,123 +0,0 @@
|
|||
import { createCipheriv, createDecipheriv, randomBytes, createHash, hkdfSync } from 'crypto';
|
||||
|
||||
const ALGORITHM = 'aes-256-gcm';
|
||||
const IV_LENGTH = 12;
|
||||
const AUTH_TAG_LENGTH = 16;
|
||||
const SALT_LENGTH = 16;
|
||||
|
||||
let encryptionWarningShown = false;
|
||||
|
||||
function getMasterKey(): Buffer | null {
|
||||
const key = process.env.ENCRYPTION_KEY;
|
||||
if (!key) {
|
||||
if (!encryptionWarningShown) {
|
||||
console.warn('[SECURITY WARNING] ENCRYPTION_KEY not set. User data will NOT be encrypted.');
|
||||
console.warn('Generate one with: openssl rand -base64 36');
|
||||
encryptionWarningShown = true;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return createHash('sha256').update(key).digest();
|
||||
}
|
||||
|
||||
function deriveUserKey(userSub: string, salt: Buffer): Buffer | null {
|
||||
const masterKey = getMasterKey();
|
||||
if (!masterKey) return null;
|
||||
|
||||
return Buffer.from(
|
||||
hkdfSync('sha256', masterKey, salt, `kaboot-user-data:${userSub}`, 32)
|
||||
);
|
||||
}
|
||||
|
||||
export function encryptForUser(plaintext: string | null | undefined, userSub: string): string | null {
|
||||
if (plaintext === null || plaintext === undefined || plaintext === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const salt = randomBytes(SALT_LENGTH);
|
||||
const key = deriveUserKey(userSub, salt);
|
||||
|
||||
if (!key) {
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
const iv = randomBytes(IV_LENGTH);
|
||||
const cipher = createCipheriv(ALGORITHM, key, iv);
|
||||
const encrypted = Buffer.concat([
|
||||
cipher.update(plaintext, 'utf8'),
|
||||
cipher.final()
|
||||
]);
|
||||
const authTag = cipher.getAuthTag();
|
||||
|
||||
const combined = Buffer.concat([salt, iv, authTag, encrypted]);
|
||||
return combined.toString('base64');
|
||||
}
|
||||
|
||||
export function decryptForUser(ciphertext: string | null | undefined, userSub: string): string | null {
|
||||
if (ciphertext === null || ciphertext === undefined || ciphertext === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const masterKey = getMasterKey();
|
||||
if (!masterKey) {
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
try {
|
||||
const combined = Buffer.from(ciphertext, 'base64');
|
||||
|
||||
if (combined.length < SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH + 1) {
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
const salt = combined.subarray(0, SALT_LENGTH);
|
||||
const iv = combined.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
|
||||
const authTag = combined.subarray(SALT_LENGTH + IV_LENGTH, SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);
|
||||
const encrypted = combined.subarray(SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);
|
||||
|
||||
const key = Buffer.from(
|
||||
hkdfSync('sha256', masterKey, salt, `kaboot-user-data:${userSub}`, 32)
|
||||
);
|
||||
|
||||
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
||||
decipher.setAuthTag(authTag);
|
||||
|
||||
const decrypted = Buffer.concat([
|
||||
decipher.update(encrypted),
|
||||
decipher.final()
|
||||
]);
|
||||
|
||||
return decrypted.toString('utf8');
|
||||
} catch {
|
||||
return ciphertext;
|
||||
}
|
||||
}
|
||||
|
||||
export function isEncrypted(value: string | null | undefined): boolean {
|
||||
if (!value) return false;
|
||||
|
||||
try {
|
||||
const decoded = Buffer.from(value, 'base64');
|
||||
return decoded.length >= SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH + 1;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function encryptJsonForUser(data: object | null | undefined, userSub: string): string | null {
|
||||
if (data === null || data === undefined) {
|
||||
return null;
|
||||
}
|
||||
return encryptForUser(JSON.stringify(data), userSub);
|
||||
}
|
||||
|
||||
export function decryptJsonForUser<T>(ciphertext: string | null | undefined, userSub: string): T | null {
|
||||
const plaintext = decryptForUser(ciphertext, userSub);
|
||||
if (!plaintext) return null;
|
||||
|
||||
try {
|
||||
return JSON.parse(plaintext) as T;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue