Download server/src/__tests__/scripts/rotate-encryption-key.test.ts from automindy/fokemal: direct link, hf CLI and curl.
- Browser
- Download file 4.06 kB
-
https://huggingface.co/spaces/automindy/fokemal/resolve/main/server/src/__tests__/scripts/rotate-encryption-key.test.ts
- Command line
-
hf download hf://spaces/automindy/fokemal/server/src/__tests__/scripts/rotate-encryption-key.test.ts
-
curl -L -o rotate-encryption-key.test.ts https://huggingface.co/spaces/automindy/fokemal/resolve/main/server/src/__tests__/scripts/rotate-encryption-key.test.ts
4.06 kB
| import { describe, it, expect, beforeAll } from 'vitest'; | |
| import { initDb, getDb } from '../../db/index.js'; | |
| import { encrypt, decrypt } from '../../lib/crypto.js'; | |
| import { | |
| rotateSecrets, | |
| applyRotation, | |
| encryptWith, | |
| decryptWith, | |
| } from '../../scripts/rotate-encryption-key.js'; | |
| const OLD_KEY = Buffer.from('a'.repeat(64), 'hex'); // 64 hex chars | |
| const NEW_KEY = Buffer.from('b'.repeat(64), 'hex'); | |
| // Seed one row in every encryption group with the OLD key. | |
| function seedRows() { | |
| const db = getDb(); | |
| const keyEnc = encryptWith(OLD_KEY, 'sk-prod-secret-123'); | |
| db.prepare('INSERT INTO api_keys (platform, encrypted_key, iv, auth_tag, status, enabled) VALUES (?,?,?,?,?,?)') | |
| .run('openai', keyEnc.encrypted, keyEnc.iv, keyEnc.authTag, 'unknown', 1); | |
| const proxyEnc = encryptWith(OLD_KEY, 'http://user:pass@proxy.local:8080'); | |
| db.prepare("UPDATE api_keys SET proxy_encrypted = ?, proxy_iv = ?, proxy_auth_tag = ? WHERE platform = 'openai'") | |
| .run(proxyEnc.encrypted, proxyEnc.iv, proxyEnc.authTag); | |
| const profileEnc = encryptWith(OLD_KEY, 'cp-secret'); | |
| db.prepare('INSERT INTO client_profiles (name, token_hash, encrypted_key, iv, auth_tag, enabled) VALUES (?,?,?,?,?,?)') | |
| .run('claude', 'hash1', profileEnc.encrypted, profileEnc.iv, profileEnc.authTag, 1); | |
| } | |
| describe('rotate-encryption-key: key rotation round-trip', () => { | |
| beforeAll(() => { | |
| process.env.ENCRYPTION_KEY = '0'.repeat(64); | |
| initDb(':memory:'); | |
| seedRows(); | |
| }); | |
| it('re-encrypts every stored secret so the new key can decrypt it', () => { | |
| const result = rotateSecrets(getDb(), OLD_KEY, NEW_KEY); | |
| expect(result.error).toBeUndefined(); | |
| expect(result.rows).toHaveLength(3); | |
| applyRotation(getDb(), result.rows); | |
| const rows = getDb().prepare('SELECT id, encrypted_key, iv, auth_tag FROM api_keys').all() as Array<{ id: number; encrypted_key: string; iv: string; auth_tag: string }>; | |
| expect(decryptWith(NEW_KEY, rows[0].encrypted_key, rows[0].iv, rows[0].auth_tag)).toBe('sk-prod-secret-123'); | |
| const proxy = getDb().prepare('SELECT proxy_encrypted, proxy_iv, proxy_auth_tag FROM api_keys').get() as { proxy_encrypted: string; proxy_iv: string; proxy_auth_tag: string }; | |
| expect(decryptWith(NEW_KEY, proxy.proxy_encrypted, proxy.proxy_iv, proxy.proxy_auth_tag)).toBe('http://user:pass@proxy.local:8080'); | |
| const cp = getDb().prepare('SELECT encrypted_key, iv, auth_tag FROM client_profiles').get() as { encrypted_key: string; iv: string; auth_tag: string }; | |
| expect(decryptWith(NEW_KEY, cp.encrypted_key, cp.iv, cp.auth_tag)).toBe('cp-secret'); | |
| }); | |
| }); | |
| describe('rotate-encryption-key: wrong-key protection', () => { | |
| beforeAll(() => { | |
| process.env.ENCRYPTION_KEY = '0'.repeat(64); | |
| initDb(':memory:'); | |
| seedRows(); | |
| }); | |
| it('reports an error (and no rows) when the old key cannot decrypt a value', () => { | |
| const wrong = Buffer.from('c'.repeat(64), 'hex'); | |
| const result = rotateSecrets(getDb(), wrong, NEW_KEY); | |
| expect(result.rows).toHaveLength(0); | |
| expect(result.error).toMatch(/cannot decrypt/); | |
| }); | |
| }); | |
| describe('rotate-encryption-key: aes-256-gcm parity with crypto.ts', () => { | |
| it('produces ciphertext the server crypto module can open (same params)', () => { | |
| // The rotate script must be a drop-in re-encryptor for the running server: | |
| // a value encrypted with encryptWith() has to decrypt under the server's | |
| // crypto.decrypt() with the same key, and vice versa. | |
| process.env.ENCRYPTION_KEY = 'd'.repeat(64); | |
| const key = Buffer.from('d'.repeat(64), 'hex'); | |
| initDb(':memory:'); | |
| const serverEnc = encrypt('roundtrip-parity'); | |
| // The rotate script decrypts it with the same raw key bytes. | |
| expect(decryptWith(key, serverEnc.encrypted, serverEnc.iv, serverEnc.authTag)).toBe('roundtrip-parity'); | |
| // And its own ciphertext decrypts under the server module with that key. | |
| const scriptEnc = encryptWith(key, 'script-parity'); | |
| expect(decrypt(scriptEnc.encrypted, scriptEnc.iv, scriptEnc.authTag)).toBe('script-parity'); | |
| }); | |
| }); | |