From f7f4934d8c8021066c4cbc3bc899c70dc4b56407 Mon Sep 17 00:00:00 2001 From: Thomas Bonnin <233326+TBonnin@users.noreply.github.com> Date: Wed, 25 Sep 2024 15:42:28 -0400 Subject: [PATCH] fix(keystore): hash should actually be hashed o_O --- .../models/privatekeys.integration.test.ts | 3 +-- packages/keystore/lib/models/privatekeys.ts | 19 +++++++------------ packages/keystore/lib/utils/encryption.ts | 19 ++++++++++++++++++- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/packages/keystore/lib/models/privatekeys.integration.test.ts b/packages/keystore/lib/models/privatekeys.integration.test.ts index 4c279547fb8..17c40cadd03 100644 --- a/packages/keystore/lib/models/privatekeys.integration.test.ts +++ b/packages/keystore/lib/models/privatekeys.integration.test.ts @@ -88,7 +88,7 @@ describe('PrivateKey', async () => { it('should be retrieved before it expires', async () => { const entityType = 'connect_session'; - const ttlInMs = 30; + const ttlInMs = 100; const createKey = await createPrivateKey(db, { displayName: 'this is my key', entityType, @@ -97,7 +97,6 @@ describe('PrivateKey', async () => { environmentId: 1, ttlInMs }); - await new Promise((resolve) => setTimeout(resolve, ttlInMs / 2)); const [keyValue] = createKey.unwrap(); const getKey = await getPrivateKey(db, keyValue); expect(getKey.isOk()).toBe(true); diff --git a/packages/keystore/lib/models/privatekeys.ts b/packages/keystore/lib/models/privatekeys.ts index 2125b1b65f6..4eaee318a55 100644 --- a/packages/keystore/lib/models/privatekeys.ts +++ b/packages/keystore/lib/models/privatekeys.ts @@ -3,7 +3,7 @@ import type knex from 'knex'; import type { Result } from '@nangohq/utils'; import { Ok, Err } from '@nangohq/utils'; import type { EntityType, PrivateKey } from '@nangohq/types'; -import { getEncryption } from '../utils/encryption.js'; +import { getEncryption, hashValue } from '../utils/encryption.js'; export const PRIVATE_KEYS_TABLE = 'private_keys'; @@ -81,12 +81,13 @@ export async function createPrivateKey( const now = new Date(); const random = crypto.randomBytes(32).toString('hex'); const keyValue = `nango_${entityType}_${random}`; + const hash = await hashValue(keyValue); const key: DbInsertPrivateKey = { display_name: displayName, account_id: accountId, environment_id: environmentId, encrypted: options.onlyStoreHash ? null : encryptValue(keyValue), - hash: hashValue(keyValue), + hash, expires_at: ttlInMs ? new Date(now.getTime() + ttlInMs) : null, entity_type: entityType, entity_id: entityId @@ -100,10 +101,11 @@ export async function createPrivateKey( export async function getPrivateKey(db: knex.Knex, keyValue: string): Promise> { const now = new Date(); + const hash = await hashValue(keyValue); const [key] = await db .update({ last_access_at: now }) .from(PRIVATE_KEYS_TABLE) - .where({ hash: hashValue(keyValue) }) + .where({ hash }) .andWhere((builder) => builder.whereNull('expires_at').orWhere('expires_at', '>', now)) .returning('*'); if (!key) { @@ -124,11 +126,8 @@ export async function deletePrivateKey( db: knex.Knex, { keyValue, entityType }: { keyValue: string; entityType: EntityType } ): Promise> { - const [key] = await db - .delete() - .from(PRIVATE_KEYS_TABLE) - .where({ hash: hashValue(keyValue), entity_type: entityType }) - .returning('*'); + const hash = await hashValue(keyValue); + const [key] = await db.delete().from(PRIVATE_KEYS_TABLE).where({ hash, entity_type: entityType }).returning('*'); if (!key) { return Err(new PrivateKeyError({ code: 'not_found', message: `Private key not found` })); } @@ -153,7 +152,3 @@ function decryptValue(encryptedValue: Buffer): Result { } return Ok(encryption.decrypt(encrypted, iv, tag)); } - -function hashValue(keyValue: string): string { - return keyValue; -} diff --git a/packages/keystore/lib/utils/encryption.ts b/packages/keystore/lib/utils/encryption.ts index 42dcf94f8b5..f11ef826154 100644 --- a/packages/keystore/lib/utils/encryption.ts +++ b/packages/keystore/lib/utils/encryption.ts @@ -1,11 +1,19 @@ +import utils from 'node:util'; +import crypto from 'crypto'; import { Encryption } from '@nangohq/utils'; import { envs } from './env.js'; +const pbkdf2 = utils.promisify(crypto.pbkdf2); + let encryption: Encryption | null = null; +function getEncryptionKey(): string | undefined { + return envs.NANGO_ENCRYPTION_KEY; +} + export function getEncryption(): Encryption { if (!encryption) { - const encryptionKey = envs.NANGO_ENCRYPTION_KEY; + const encryptionKey = getEncryptionKey(); if (!encryptionKey) { throw new Error('NANGO_ENCRYPTION_KEY is not set'); } @@ -13,3 +21,12 @@ export function getEncryption(): Encryption { } return encryption; } + +export async function hashValue(val: string): Promise { + const encryptionKey = getEncryptionKey(); + if (!encryptionKey) { + throw new Error('NANGO_ENCRYPTION_KEY is not set'); + } + + return (await pbkdf2(val, encryptionKey, 310000, 32, 'sha256')).toString('base64'); +}