From 415745dc1f1883679c1cc571979512bd7f7f0d4f Mon Sep 17 00:00:00 2001 From: code-by-jassu Date: Tue, 22 Jul 2025 08:43:24 +0530 Subject: [PATCH 1/2] Fix: encryptProjectConfig now supports AES-GCM --- package.json | 2 +- src/util.test.ts | 38 +++++++++++++++++++---------------- src/util.ts | 52 ++++++++++++++++++++++++++++++++---------------- src/version.ts | 2 +- 4 files changed, 58 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index 53b7f31..7abbea1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@digitalocean/functions-deployer", - "version": "5.0.21", + "version": "5.0.22", "description": "The the functions deployer for DigitalOcean", "main": "lib/index.js", "repository": { diff --git a/src/util.test.ts b/src/util.test.ts index 26bda39..21db380 100644 --- a/src/util.test.ts +++ b/src/util.test.ts @@ -165,7 +165,7 @@ describe('validateTriggers', () => { }); }); -describe('encryptProjectConfig', () => { +describe('encryptProjectConfig with AES-GCM', () => { const yml = ` packages: - name: test-triggers @@ -175,23 +175,27 @@ describe('encryptProjectConfig', () => { `; const mockKey = - 'cd7a175002a713696fef83b30e93638388eed520eec1cbddec8f51633db3dcb7'; - const mockEncryptedData = - '6a0a96cbf2f656a4ec65a9d581a5628d9a5be8abe7910de6cbca8ab4b8e93a63e02a970460871d70f8bc1b773a8eb18b6b270f7e542d2159407ba89559e0477be27fe9c967c5388a7bb10185a5b1247addabe4ce97fb7aa68c034ad2bbfce4c7fcb89bdd7973d46aa215d3489b2ff4fa27523e7ff76ea1845b28010ecccd37c7'; - - it('should encrypt the project yaml data', () => { - const mockRandomBytes = jest - .spyOn(crypto, 'randomBytes') - .mockImplementationOnce(() => Buffer.from(mockKey, 'hex')); - - const res = encryptProjectConfig(yml); - expect(mockRandomBytes).toHaveBeenCalledTimes(1); - expect(res.key).toEqual(mockKey); - expect(res.config).toEqual(mockEncryptedData); + 'cd7a175002a713696fef83b30e93638388eed520eec1cbddec8f51633db3dcb7'; // 32 bytes + const mockConfig = `6a0a96cbf2f656a4ec65a9d581a5628d9a5be8abe7910de6cbca8ab4b8e93a63e02a970460871d70f8bc1b773a8eb18b6b270f7e542d2159407ba89559e0477be27fe9c967c5388a7bb10185a5b1247addabe4ce97fb7aa68c034ad2bbfce4c7fcb89bdd7973d46aa215d3489b2ff4fa27523e7ff76ea1845b28010ecccd37c7`; + + beforeEach(() => { + jest.restoreAllMocks(); + }); + + it('should decrypt AES-GCM v2 config and return original YAML', () => { + const { config, key } = encryptProjectConfig(yml); + + expect(config.startsWith('v2:')).toBe(true); + const parts = config.split(':'); + expect(parts.length).toBe(4); + + // Decrypt + const decrypted = decryptProjectConfig(config, key); + expect(decrypted).toEqual(yml); }); - it('should decrypt project yaml data given the correct key', () => { - const res = decryptProjectConfig(mockEncryptedData, mockKey); - expect(res).toEqual(yml); + it('should decrypt mock encrypted config and return original YAML', () => { + const decryptedWithOldConfig = decryptProjectConfig(mockConfig, mockKey); + expect(decryptedWithOldConfig).toEqual(yml); }); }); diff --git a/src/util.ts b/src/util.ts index d6f1288..2c40ce2 100644 --- a/src/util.ts +++ b/src/util.ts @@ -153,27 +153,45 @@ export function encryptProjectConfig(config: string): { key: string; } { const key = crypto.randomBytes(32); - const iv = Buffer.alloc(16).fill(0); - const cipher = crypto.createCipheriv('aes256', key, iv); - const encrypted = cipher.update(config, 'utf8', 'hex') + cipher.final('hex'); - return { - config: encrypted, - key: key.toString('hex') - }; + const iv = crypto.randomBytes(12); + const cipher = crypto.createCipheriv('aes-256-gcm', key, iv); + const encrypted = Buffer.concat([ + cipher.update(config, 'utf8'), + cipher.final() + ]); + const authTag = cipher.getAuthTag(); + const final = `v2:${iv.toString('hex')}:${authTag.toString( + 'hex' + )}:${encrypted.toString('hex')}`; + return { config: final, key: key.toString('hex') }; } -export function decryptProjectConfig(config: string, key: string): string { - const iv = Buffer.alloc(16).fill(0); - const decipher = crypto.createDecipheriv( - 'aes256', - Buffer.from(key, 'hex'), - iv - ); - const decryptedMessage = Buffer.concat([ - decipher.update(config, 'hex'), +export function decryptProjectConfig( + encrypted: string, + keyHex: string +): string { + const key = Buffer.from(keyHex, 'hex'); + if (encrypted.startsWith('v2:')) { + const [_v, ivHex, tagHex, encryptedHex] = encrypted.split(':'); + const decipher = crypto.createDecipheriv( + 'aes-256-gcm', + key, + Buffer.from(ivHex, 'hex') + ); + decipher.setAuthTag(Buffer.from(tagHex, 'hex')); + const decrypted = Buffer.concat([ + decipher.update(Buffer.from(encryptedHex, 'hex')), + decipher.final() + ]); + return decrypted.toString('utf8'); + } + const iv = Buffer.alloc(16, 0); + const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv); + const decrypted = Buffer.concat([ + decipher.update(Buffer.from(encrypted, 'hex')), decipher.final() ]); - return decryptedMessage.toString('utf8'); + return decrypted.toString('utf8'); } // Rename any 'functions' members of the contained PackageSpecs of a config to 'actions'. diff --git a/src/version.ts b/src/version.ts index 70aeab4..ba2f575 100644 --- a/src/version.ts +++ b/src/version.ts @@ -3,4 +3,4 @@ // DO NOT EDIT MANUALLY //////////////////////////////////////////////////////////////// -export const version = '5.0.21'; +export const version = '5.0.22'; From ad5646d7eee95e5f5e185efd9fb1f824abb88b2f Mon Sep 17 00:00:00 2001 From: code-by-jassu Date: Fri, 25 Jul 2025 17:02:30 +0530 Subject: [PATCH 2/2] test --- e2e/test_helper.bash | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/e2e/test_helper.bash b/e2e/test_helper.bash index c84f394..878354b 100755 --- a/e2e/test_helper.bash +++ b/e2e/test_helper.bash @@ -15,6 +15,12 @@ elif [ -z "$TEST_NAMESPACE" ]; then echo "Missing TEST_NAMESPACE" fi +echo "Namespace is $TEST_NAMESPACE" +echo "API key is $DO_API_KEY" + +echo "doctl auth list" +doctl auth list + if [ -z "$DOSLS" ]; then DOSLS=../bin/run fi