feat(federation): seal federation peer client keys at rest (FED-M2-05) (#495)
This commit was merged in pull request #495.
This commit is contained in:
@@ -10,3 +10,4 @@ export {
|
||||
type SsoTeamSyncConfig,
|
||||
type SupportedSsoProviderId,
|
||||
} from './sso.js';
|
||||
export { seal, unseal } from './seal.js';
|
||||
|
||||
52
packages/auth/src/seal.ts
Normal file
52
packages/auth/src/seal.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'node:crypto';
|
||||
|
||||
const ALGORITHM = 'aes-256-gcm';
|
||||
const IV_LENGTH = 12; // 96-bit IV for GCM
|
||||
const TAG_LENGTH = 16; // 128-bit auth tag
|
||||
|
||||
/**
|
||||
* Derive a 32-byte AES-256 key from BETTER_AUTH_SECRET using SHA-256.
|
||||
* Throws if BETTER_AUTH_SECRET is not set.
|
||||
*/
|
||||
function deriveKey(): Buffer {
|
||||
const secret = process.env['BETTER_AUTH_SECRET'];
|
||||
if (!secret) {
|
||||
throw new Error('BETTER_AUTH_SECRET is not set — cannot derive encryption key');
|
||||
}
|
||||
return createHash('sha256').update(secret).digest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Seal a plaintext string using AES-256-GCM.
|
||||
* Output format: base64(IV || authTag || ciphertext)
|
||||
*/
|
||||
export function seal(plaintext: string): string {
|
||||
const key = deriveKey();
|
||||
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([iv, authTag, encrypted]);
|
||||
return combined.toString('base64');
|
||||
}
|
||||
|
||||
/**
|
||||
* Unseal a value sealed by `seal()`.
|
||||
* Throws on authentication failure (tampered data) or if BETTER_AUTH_SECRET is unset.
|
||||
*/
|
||||
export function unseal(encoded: string): string {
|
||||
const key = deriveKey();
|
||||
const combined = Buffer.from(encoded, 'base64');
|
||||
|
||||
const iv = combined.subarray(0, IV_LENGTH);
|
||||
const authTag = combined.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH);
|
||||
const ciphertext = combined.subarray(IV_LENGTH + TAG_LENGTH);
|
||||
|
||||
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
||||
decipher.setAuthTag(authTag);
|
||||
|
||||
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
||||
return decrypted.toString('utf8');
|
||||
}
|
||||
Reference in New Issue
Block a user