From 73cce61c9bf670bb028f288ec0d571df1e7d7420 Mon Sep 17 00:00:00 2001 From: Krishnadas Menon Date: Thu, 16 Nov 2023 17:23:16 -0800 Subject: [PATCH 01/16] add hmac signature' --- lib/events.js | 3 ++- lib/webhook.js | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/events.js b/lib/events.js index 657897e..3f64634 100644 --- a/lib/events.js +++ b/lib/events.js @@ -44,7 +44,8 @@ class AssetComputeEvents { this.webhookEvents = new AssetComputeWebhookEvents({ webhookUrl: params.auth.webhookUrl, orgId: orgId, - clientId: clientId + clientId: clientId, + hmacSignature: params.auth.hmacSignature }); } else if (params.auth && params.auth.accessToken && orgId && clientId) { diff --git a/lib/webhook.js b/lib/webhook.js index 72e92ef..74f2a03 100644 --- a/lib/webhook.js +++ b/lib/webhook.js @@ -27,6 +27,7 @@ class AssetComputeWebhookEvents { this.webhookUrl = options.webhookUrl; this.orgId = options.orgId; this.clientId = options.clientId; + this.hmacSignature = options.hmacSignature; } async sendEvent(event, retryOptions) { @@ -36,7 +37,8 @@ class AssetComputeWebhookEvents { headers: { 'Content-Type': 'application/json', 'x-ims-org-id': this.orgId, - 'x-api-key': this.clientId + 'x-api-key': this.clientId, + 'x-ac-hmac-signature' : this.hmacSignature }, body: JSON.stringify({ user_guid: this.orgId, From fd11238ef837859143c1d6a09413168fd68b9a3d Mon Sep 17 00:00:00 2001 From: Krishnadas Menon Date: Tue, 21 Nov 2023 16:47:08 -0800 Subject: [PATCH 02/16] hmac signature --- lib/events.js | 2 +- lib/hmac-signature.js | 27 +++++++++++++++++++++++++++ lib/webhook.js | 18 ++++++++++-------- 3 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 lib/hmac-signature.js diff --git a/lib/events.js b/lib/events.js index 3f64634..408ed00 100644 --- a/lib/events.js +++ b/lib/events.js @@ -45,7 +45,7 @@ class AssetComputeEvents { webhookUrl: params.auth.webhookUrl, orgId: orgId, clientId: clientId, - hmacSignature: params.auth.hmacSignature + hmacPrivateKey: params.hmacPrivateKey || process.env.HMAC_PRIVATE_KEY }); } else if (params.auth && params.auth.accessToken && orgId && clientId) { diff --git a/lib/hmac-signature.js b/lib/hmac-signature.js new file mode 100644 index 0000000..4a7e8c2 --- /dev/null +++ b/lib/hmac-signature.js @@ -0,0 +1,27 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +'use strict'; + +const crypto = require('crypto'); + +function generateHMACSignature(data, privateKey) { + const sign = crypto.createSign('SHA256'); + sign.write(data); + sign.end(); + const signature = sign.sign(privateKey, 'base64'); + return signature; +} + +module.exports = { + generateHMACSignature +}; diff --git a/lib/webhook.js b/lib/webhook.js index 74f2a03..b4661a8 100644 --- a/lib/webhook.js +++ b/lib/webhook.js @@ -13,6 +13,7 @@ 'use strict'; const fetch = require('@adobe/node-fetch-retry'); +const { generateHMACSignature } = require('./hmac-signature'); class AssetComputeWebhookEvents { @@ -27,24 +28,25 @@ class AssetComputeWebhookEvents { this.webhookUrl = options.webhookUrl; this.orgId = options.orgId; this.clientId = options.clientId; - this.hmacSignature = options.hmacSignature; + this.hmacPrivateKey = options.hmacPrivateKey; } - async sendEvent(event, retryOptions) { + async sendEvent(event, retryOptions) { const url = this.webhookUrl; + const eventBody = JSON.stringify({ + user_guid: this.orgId, + event_code: event.code, + event:event.payload || {} + }); const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-ims-org-id': this.orgId, 'x-api-key': this.clientId, - 'x-ac-hmac-signature' : this.hmacSignature + 'x-ac-hmac-signature' : this.hmacPrivateKey ? generateHMACSignature(eventBody, this.hmacPrivateKey) : undefined, }, - body: JSON.stringify({ - user_guid: this.orgId, - event_code: event.code, - event:event.payload || {} - }), + body: eventBody, retryOptions: retryOptions }); if (response.status !== 200 && response.status !== 202) { From b55f9b1fb5bcc100dea75f797815f212e748b44c Mon Sep 17 00:00:00 2001 From: Krishnadas Menon Date: Tue, 21 Nov 2023 18:09:35 -0800 Subject: [PATCH 03/16] hmac tests --- lib/hmac-signature.js | 14 +++++++-- test/events.test.js | 25 ++++++++++++++++ test/hmac-signature.test.js | 58 +++++++++++++++++++++++++++++++++++++ test/test-private.pem | 28 ++++++++++++++++++ test/test-public.pem | 9 ++++++ 5 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 test/hmac-signature.test.js create mode 100644 test/test-private.pem create mode 100644 test/test-public.pem diff --git a/lib/hmac-signature.js b/lib/hmac-signature.js index 4a7e8c2..68446a6 100644 --- a/lib/hmac-signature.js +++ b/lib/hmac-signature.js @@ -15,13 +15,23 @@ const crypto = require('crypto'); function generateHMACSignature(data, privateKey) { - const sign = crypto.createSign('SHA256'); + const sign = crypto.createSign('RSA-SHA256'); sign.write(data); sign.end(); const signature = sign.sign(privateKey, 'base64'); return signature; } +function verifyHMACSign(data, signature, pubKey) { + const verify = crypto.createVerify('RSA-SHA256'); + verify.write(data); + verify.end(); + const verified = verify.verify(pubKey, signature, 'base64'); + return verified; +} + + module.exports = { - generateHMACSignature + generateHMACSignature, + verifyHMACSign }; diff --git a/test/events.test.js b/test/events.test.js index 0fc4935..adcb2a7 100644 --- a/test/events.test.js +++ b/test/events.test.js @@ -242,4 +242,29 @@ describe("AssetComputeEvents", function() { }]); }); + it("sendEvent - Webhook events with hmac signature", async function() { + // not setting this + delete process.env.ASSET_COMPUTE_UNIT_TEST_OUT; + const nockSendEventWebHook = nock(FAKE_WEBHOOK_URL) + .filteringRequestBody(body => { + body = JSON.parse(body); + delete body.event.date; + console.log("Webhook mock received:", body); + return body; + }) + .post("/", { + user_guid: "orgId", + event_code: AssetComputeEvents.EVENT_CODE, + event: { + test: "value", + type: "my_event", + requestId: "requestId" + } + }) + .reply(200, {}); + const events = new AssetComputeEvents(FAKE_WEBHOOK_PARAMS); + await events.sendEvent("my_event", {test: "value"}); + assert.ok(nockSendEventWebHook.isDone(), "webhook event not properly sent"); + }); + }); diff --git a/test/hmac-signature.test.js b/test/hmac-signature.test.js new file mode 100644 index 0000000..ef1a066 --- /dev/null +++ b/test/hmac-signature.test.js @@ -0,0 +1,58 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +'use strict'; + +const { generateHMACSignature, verifyHMACSign } = require('../lib/hmac-signature'); +const assert = require('assert'); +const MetricsTestHelper = require("@adobe/openwhisk-newrelic/lib/testhelper"); +const fs = require('fs'); +const path = require('path'); + +let privateKey; +let publicKey; +// const = fs.readFileSync('./test-public.pem'); + +describe("HMACSignature", function() { + before(() => { + const pvtkeyFilePath = path.join(__dirname, 'test-private.pem'); + const pubkeyFilePath = path.join(__dirname, 'test-public.pem'); + privateKey = fs.readFileSync(pvtkeyFilePath, 'utf8'); + publicKey = fs.readFileSync(pubkeyFilePath, 'utf8'); + }); + beforeEach(function() { + delete process.env.ASSET_COMPUTE_UNIT_TEST_OUT; + MetricsTestHelper.beforeEachTest(); + }); + + afterEach(function() { + MetricsTestHelper.afterEachTest(); + }); + + it("should generate HMACSignature", function() { + const data = 'Some data to sign'; + const signature = generateHMACSignature(data, privateKey); + assert.ok(signature); + }); + it("should verify HMAC signature", function() { + const data = 'Some data to sign'; + const signature = generateHMACSignature(data, privateKey); + assert.ok(verifyHMACSign(data, signature, publicKey)); + }); + + it("should fail HMAC signature verification if data changes", function() { + let data = 'Some data to sign'; + const signature = generateHMACSignature(data, privateKey); + data = 'data changes'; + assert.ok(!verifyHMACSign(data, signature, publicKey)); + }); +}); diff --git a/test/test-private.pem b/test/test-private.pem new file mode 100644 index 0000000..882534e --- /dev/null +++ b/test/test-private.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCs9Mn5rTKTV2zQ +f0Ulc1ubuy+Q9zMhafytGseYZagLDYvKqOiB1weotdKNClx4oNR1WP+5T7KsTPOx +fbWTqAjmidnyQrqiVRLAc7jQGHyPVihMg89jv5lndOkhqu+5VOs36zmC4udTlWTT +RulKEqhqsLf/2B1sUDr9ZLN/IjHy/+UHq91bPetNqrgSz0KIC/axhyRBs847yvQl +dQUHg3i4qlZLDDGz41H4GdvGbkAGnrhZEBt9W3N8zCR22ebDv/UsG/q9uwyaIjY1 +t0QMEpbkiLRMtLkQya67EOVNFoGUf7RogOS1V0S5qGn20qFzUvUo/3bUMNUj+cfa +W7nBAJxxAgMBAAECggEAN/4z2UoSIpNeHg3l897p3Hv4RUVSX1HxxJTz40md4p4r +EJwoUcisqRjcJl8hWsguoCME/xhnLtLm3iJlg30WDJS6Y2zpAR7rqDSOs4gJ41mw +SD0wCgoAfvbhds3hOC3ezMaxYsdUvsGR17iQVDPAJHI7z29G70b2IgzU86a9Zv6a +/K+056RI1gljeUg88T0AjpM7WCk+0/eY++uwP+xjXNXwz2eSAuLvWI/NMXEYlKkY ++8lH6TjEbHtidnwRWI9S86ki9DktMLM9pea15gmGfD3FReoUQ5jW8UXLCtU+QpqT +laPbKLI3HWkgrprAop+t1kLvqCSuQP5ebczqc3RaQQKBgQDXrV9OqQuDpjVkVfRn +FJ6zsMuU7P0pC8LzmrJ1dd/O40WyQJlrYF4CZOOWQo6qJGGMC/RmA2g50y6Vae/v +5Le1xh1Sc5PEcCHYW0prIjlWdZb6BVOvfekaSdBLSfRyYaCpeDGyoGi2ZXDWZaNu +/4fKavLiglbBsrM3qnJpiucX2QKBgQDNSrnOWdxk5jW1xrpr8fMMvGsts6oGSmPP +tJuuya1feCg1HaPzK1egfo23H6zu2Qlg/nK4EH+5QV6KMIOA7rvaWPmFoeHcxJVN +d1YnaqVZs7RCHo5j/JWe4MFsoA6uOk5GaI1AoF8rF6G+BJB3+Iy+uucXiMiKICZX +qz5pDOKiWQKBgQCxTvDJhCPZKS0/vYJdtA/8fd4AIwzFT8wH5SfNdQh311QXBRCb +u+/NXA/WGgWbTql7LuDPdQGceEcVwq/ksk1tfN/iRu42UlEgw3xcsxgcQjPKe9Ke +tY6LdB9vpe72kdg5Onzw8vri6GZpe8lumViTSnU/ThPwze6UVuYUaoMtkQKBgCfI +P1F5oHf9FJNxO1cHv+3L5Ykps90cNVNe0HyCoJJLmp8Qa2EIGScE3zmJbd2rOWIO +bPS2XcnsnQFyfUlS+hxJ+bCnujkp7zTf11Y0GG7VoCzf9v/xRp1POKxwJ+gjbF/A +iv7nFQoovjMwdz6Ib93WIkoxO4tox4Vdre51jMWhAoGADRLmi/9lTPLuBOuZ9pNm +L9k///AIqIraSD0pOMzwtAsoluW7YK/0k3O7IcN2ExWAfGkDH0MA3/C6RkUmvRU0 +Hn9LNTIdwOIMo7QWQwy46310zkXTdAUtM9/ZtRuq//x+WqEaGYQ2JagC/OSZMkJI +dx4H9taaFtaoWoXI75lkI5Y= +-----END PRIVATE KEY----- diff --git a/test/test-public.pem b/test/test-public.pem new file mode 100644 index 0000000..b8df31f --- /dev/null +++ b/test/test-public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArPTJ+a0yk1ds0H9FJXNb +m7svkPczIWn8rRrHmGWoCw2LyqjogdcHqLXSjQpceKDUdVj/uU+yrEzzsX21k6gI +5onZ8kK6olUSwHO40Bh8j1YoTIPPY7+ZZ3TpIarvuVTrN+s5guLnU5Vk00bpShKo +arC3/9gdbFA6/WSzfyIx8v/lB6vdWz3rTaq4Es9CiAv2sYckQbPOO8r0JXUFB4N4 +uKpWSwwxs+NR+Bnbxm5ABp64WRAbfVtzfMwkdtnmw7/1LBv6vbsMmiI2NbdEDBKW +5Ii0TLS5EMmuuxDlTRaBlH+0aIDktVdEuahp9tKhc1L1KP921DDVI/nH2lu5wQCc +cQIDAQAB +-----END PUBLIC KEY----- From ca75957bd113c9cfe1ad139d234d500435346ab1 Mon Sep 17 00:00:00 2001 From: Krishnadas Menon Date: Wed, 22 Nov 2023 12:53:17 -0800 Subject: [PATCH 04/16] add tests --- lib/events.js | 2 +- lib/webhook.js | 5 ++-- test/events.test.js | 17 +++++++++-- test/hmac-signature.test.js | 41 +++++++++++++++++++++++++-- test/{ => resources}/test-private.pem | 0 test/{ => resources}/test-public.pem | 0 6 files changed, 58 insertions(+), 7 deletions(-) rename test/{ => resources}/test-private.pem (100%) rename test/{ => resources}/test-public.pem (100%) diff --git a/lib/events.js b/lib/events.js index 408ed00..cf29196 100644 --- a/lib/events.js +++ b/lib/events.js @@ -45,7 +45,7 @@ class AssetComputeEvents { webhookUrl: params.auth.webhookUrl, orgId: orgId, clientId: clientId, - hmacPrivateKey: params.hmacPrivateKey || process.env.HMAC_PRIVATE_KEY + hmacPrivateKey: params.hmacPrivateKey }); } else if (params.auth && params.auth.accessToken && orgId && clientId) { diff --git a/lib/webhook.js b/lib/webhook.js index b4661a8..1d9bc8c 100644 --- a/lib/webhook.js +++ b/lib/webhook.js @@ -28,7 +28,7 @@ class AssetComputeWebhookEvents { this.webhookUrl = options.webhookUrl; this.orgId = options.orgId; this.clientId = options.clientId; - this.hmacPrivateKey = options.hmacPrivateKey; + this.hmacPrivateKey = options.hmacPrivateKey || process.env.HMAC_PRIVATE_KEY; } async sendEvent(event, retryOptions) { @@ -38,13 +38,14 @@ class AssetComputeWebhookEvents { event_code: event.code, event:event.payload || {} }); + const signature = this.hmacPrivateKey ? generateHMACSignature(eventBody, this.hmacPrivateKey) : undefined; const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-ims-org-id': this.orgId, 'x-api-key': this.clientId, - 'x-ac-hmac-signature' : this.hmacPrivateKey ? generateHMACSignature(eventBody, this.hmacPrivateKey) : undefined, + 'x-ac-hmac-signature' : signature, }, body: eventBody, retryOptions: retryOptions diff --git a/test/events.test.js b/test/events.test.js index adcb2a7..845a894 100644 --- a/test/events.test.js +++ b/test/events.test.js @@ -17,6 +17,7 @@ const AssetComputeMetrics = require('../lib/metrics'); const jsonwebtoken = require('jsonwebtoken'); const tmp = require('tmp'); const fs = require('fs'); +const path = require('path'); const nock = require('nock'); const assert = require('assert'); const MetricsTestHelper = require("@adobe/openwhisk-newrelic/lib/testhelper"); @@ -242,11 +243,20 @@ describe("AssetComputeEvents", function() { }]); }); - it("sendEvent - Webhook events with hmac signature", async function() { + it("sendEvent - Webhook events with hmac signature using pvt-pub keypair", async function() { + const { generateHMACSignature, verifyHMACSign } = require('../lib/hmac-signature'); + const pvtkeyFilePath = path.join(__dirname, 'resources/test-private.pem'); + const pubkeyFilePath = path.join(__dirname, 'resources/test-public.pem'); + const privateKey = fs.readFileSync(pvtkeyFilePath, 'utf8'); + const publicKey = fs.readFileSync(pubkeyFilePath, 'utf8'); + let signature; + let payload; // not setting this - delete process.env.ASSET_COMPUTE_UNIT_TEST_OUT; + process.env.HMAC_PRIVATE_KEY = privateKey; const nockSendEventWebHook = nock(FAKE_WEBHOOK_URL) .filteringRequestBody(body => { + signature = generateHMACSignature(body, privateKey); + payload = body; body = JSON.parse(body); delete body.event.date; console.log("Webhook mock received:", body); @@ -262,9 +272,12 @@ describe("AssetComputeEvents", function() { } }) .reply(200, {}); + const events = new AssetComputeEvents(FAKE_WEBHOOK_PARAMS); await events.sendEvent("my_event", {test: "value"}); assert.ok(nockSendEventWebHook.isDone(), "webhook event not properly sent"); + delete process.env.HMAC_PRIVATE_KEY; + assert.ok(verifyHMACSign(payload, signature, publicKey)); }); }); diff --git a/test/hmac-signature.test.js b/test/hmac-signature.test.js index ef1a066..9b6b48a 100644 --- a/test/hmac-signature.test.js +++ b/test/hmac-signature.test.js @@ -24,8 +24,8 @@ let publicKey; describe("HMACSignature", function() { before(() => { - const pvtkeyFilePath = path.join(__dirname, 'test-private.pem'); - const pubkeyFilePath = path.join(__dirname, 'test-public.pem'); + const pvtkeyFilePath = path.join(__dirname, 'resources/test-private.pem'); + const pubkeyFilePath = path.join(__dirname, 'resources/test-public.pem'); privateKey = fs.readFileSync(pvtkeyFilePath, 'utf8'); publicKey = fs.readFileSync(pubkeyFilePath, 'utf8'); }); @@ -48,6 +48,43 @@ describe("HMACSignature", function() { const signature = generateHMACSignature(data, privateKey); assert.ok(verifyHMACSign(data, signature, publicKey)); }); + it("should verify HMAC signature payload", function() { + const jsonData = { + "user_guid": "CDC364125DC22DAD0A494005@AdobeOrg", + "event_code": "asset_compute", + "event": { + "rendition": { + "fmt": "png", + "hei": 200, + "height": 200, + "name": "rendition.png", + "pipeline": false, + "url": "https://adobesampleassetsrepous.blob.core.windows.net/nui-it-scratch?sp=racw&st=2023-11-13T23:04:46Z&se=2023-11-14T07:04:46Z&spr=https&sv=2022-11-02&sr=c&sig=CP4qQY1XHOnIOnvdccGcnK%2BuBtNBcdf0MDZ2R0%2Bqi10%3D", + "wid": 200, + "width": 200 + }, + "errorReason": "GenericError", + "errorMessage": "PUT 'https://adobesampleassetsrepous.blob.core.windows.net/nui-it-scratch?sp=racw&st=2023-11-13T23:04:46Z&se=2023-11-14T07:04:46Z&spr=https&sv=2022-11-02&sr=c&sig=CP4qQY1XHOnIOnvdccGcnK%2BuBtNBcdf0MDZ2R0%2Bqi10%3D' failed with status 400: \nMissingRequiredHeaderAn HTTP header that's mandatory for this request is not specified.\nRequestId:dc15d691-f01e-00b6-57ea-1c678c000000\nTime:2023-11-22T02:17:51.9326608Zx-ms-blob-type", + "type": "rendition_failed", + "date": "2023-11-22T02:17:51.942Z", + "requestId": "i0JHLeCEFiknQHhjticS7xJcQhubGRbQ", + "source": "https://adobesampleassetsrepous.blob.core.windows.net/nui-meahana/source/AdobeStock_64530079.jpeg?sp=r&st=2023-11-22T00:38:11Z&se=2023-11-22T08:38:11Z&spr=https&sv=2022-11-02&sr=b&sig=cagz7x3kKi2zdIn1SAWohm6MUUSHSRZXuX%2FvvepJFH4%3D", + "userData": { + "assetClass": "file", + "companyId": "test", + "contentHash": "d5a78270b79dc1242df6d12b70969b660503fc62a81a18c55cd4d6b792fc9716", + "imsOrgId": "test@AdobeOrg", + "ingestionId": "test-ingestion", + "itemId": "https://business.adobe.com/media_1ca933d32ca97e654f88cda8ad61a232bb4087ace.png", + "mimeType": "image/png", + "requestId": "test-request" + } + } + }; + const data = JSON.stringify(jsonData); + const signature = generateHMACSignature(data, privateKey); + assert.ok(verifyHMACSign(data, signature, publicKey)); + }); it("should fail HMAC signature verification if data changes", function() { let data = 'Some data to sign'; diff --git a/test/test-private.pem b/test/resources/test-private.pem similarity index 100% rename from test/test-private.pem rename to test/resources/test-private.pem diff --git a/test/test-public.pem b/test/resources/test-public.pem similarity index 100% rename from test/test-public.pem rename to test/resources/test-public.pem From 4ed0bbd991ba36a508d75f4b639cdbcb3132ebc7 Mon Sep 17 00:00:00 2001 From: Krishnadas Menon Date: Wed, 22 Nov 2023 13:47:05 -0800 Subject: [PATCH 05/16] add more tests --- lib/webhook.js | 2 +- test/events.test.js | 68 +++++++++++++++++++++++++++++-------- test/hmac-signature.test.js | 10 +++--- 3 files changed, 60 insertions(+), 20 deletions(-) diff --git a/lib/webhook.js b/lib/webhook.js index 1d9bc8c..bb54989 100644 --- a/lib/webhook.js +++ b/lib/webhook.js @@ -28,7 +28,7 @@ class AssetComputeWebhookEvents { this.webhookUrl = options.webhookUrl; this.orgId = options.orgId; this.clientId = options.clientId; - this.hmacPrivateKey = options.hmacPrivateKey || process.env.HMAC_PRIVATE_KEY; + this.hmacPrivateKey = options.hmacPrivateKey; } async sendEvent(event, retryOptions) { diff --git a/test/events.test.js b/test/events.test.js index 845a894..b71c2e7 100644 --- a/test/events.test.js +++ b/test/events.test.js @@ -21,6 +21,7 @@ const path = require('path'); const nock = require('nock'); const assert = require('assert'); const MetricsTestHelper = require("@adobe/openwhisk-newrelic/lib/testhelper"); +const { generateHMACSignature, verifyHMACSign } = require('../lib/hmac-signature'); const FAKE_PARAMS = { @@ -67,6 +68,9 @@ const FAKE_WEBHOOK_PARAMS_NO_WEBHOOK = { } }; +let privateKey; +let publicKey; + describe("AssetComputeEvents", function() { beforeEach(function() { delete process.env.ASSET_COMPUTE_UNIT_TEST_OUT; @@ -242,21 +246,24 @@ describe("AssetComputeEvents", function() { location: "WebhookEvents" }]); }); +}); - it("sendEvent - Webhook events with hmac signature using pvt-pub keypair", async function() { - const { generateHMACSignature, verifyHMACSign } = require('../lib/hmac-signature'); +describe("HMACSignature sendEvent - Webhook events with hmac signature", function() { + before(() => { const pvtkeyFilePath = path.join(__dirname, 'resources/test-private.pem'); const pubkeyFilePath = path.join(__dirname, 'resources/test-public.pem'); - const privateKey = fs.readFileSync(pvtkeyFilePath, 'utf8'); - const publicKey = fs.readFileSync(pubkeyFilePath, 'utf8'); - let signature; - let payload; - // not setting this - process.env.HMAC_PRIVATE_KEY = privateKey; - const nockSendEventWebHook = nock(FAKE_WEBHOOK_URL) + privateKey = fs.readFileSync(pvtkeyFilePath, 'utf8'); + publicKey = fs.readFileSync(pubkeyFilePath, 'utf8'); + }); + + it("sendEvent - Webhook events with hmac signature exists", async function() { + const nockSendEventWebHook = nock(FAKE_WEBHOOK_URL,{ + reqheaders: { + 'x-ims-org-id': () => true, + 'x-ac-hmac-signature': (val) => val && val.length > 0 + } + }) .filteringRequestBody(body => { - signature = generateHMACSignature(body, privateKey); - payload = body; body = JSON.parse(body); delete body.event.date; console.log("Webhook mock received:", body); @@ -272,12 +279,45 @@ describe("AssetComputeEvents", function() { } }) .reply(200, {}); - + FAKE_WEBHOOK_PARAMS.hmacPrivateKey = privateKey; + const events = new AssetComputeEvents(FAKE_WEBHOOK_PARAMS); + await events.sendEvent("my_event", {test: "value"}); + assert.ok(nockSendEventWebHook.isDone(), "webhook event not properly sent"); + }); + it("sendEvent - Webhook events with hmac signature using pvt-pub keypair", async function() { + let webhookPayload; + let signatureHeader; + const nockSendEventWebHook = nock(FAKE_WEBHOOK_URL,{ + reqheaders: { + 'x-ims-org-id': () => true, + 'x-ac-hmac-signature': (val) => { + signatureHeader = val; + return val && val.length > 0; + } + } + }) + .filteringRequestBody(body => { + webhookPayload = body; + body = JSON.parse(body); + delete body.event.date; + console.log("Webhook mock received:", body); + return body; + }) + .post("/", { + user_guid: "orgId", + event_code: AssetComputeEvents.EVENT_CODE, + event: { + test: "value", + type: "my_event", + requestId: "requestId" + } + }) + .reply(200, {}); + FAKE_WEBHOOK_PARAMS.hmacPrivateKey = privateKey; const events = new AssetComputeEvents(FAKE_WEBHOOK_PARAMS); await events.sendEvent("my_event", {test: "value"}); assert.ok(nockSendEventWebHook.isDone(), "webhook event not properly sent"); - delete process.env.HMAC_PRIVATE_KEY; - assert.ok(verifyHMACSign(payload, signature, publicKey)); + assert.ok(verifyHMACSign(webhookPayload, signatureHeader, publicKey)); }); }); diff --git a/test/hmac-signature.test.js b/test/hmac-signature.test.js index 9b6b48a..3d7d7eb 100644 --- a/test/hmac-signature.test.js +++ b/test/hmac-signature.test.js @@ -50,7 +50,7 @@ describe("HMACSignature", function() { }); it("should verify HMAC signature payload", function() { const jsonData = { - "user_guid": "CDC364125DC22DAD0A494005@AdobeOrg", + "user_guid": "test@AdobeOrg", "event_code": "asset_compute", "event": { "rendition": { @@ -59,23 +59,23 @@ describe("HMACSignature", function() { "height": 200, "name": "rendition.png", "pipeline": false, - "url": "https://adobesampleassetsrepous.blob.core.windows.net/nui-it-scratch?sp=racw&st=2023-11-13T23:04:46Z&se=2023-11-14T07:04:46Z&spr=https&sv=2022-11-02&sr=c&sig=CP4qQY1XHOnIOnvdccGcnK%2BuBtNBcdf0MDZ2R0%2Bqi10%3D", + "url": "bloblstore?sp=racw&st=2023-11-13T23:04:46Z&se=2023-11-14T07:04:46Z&spr=https&sv=2022-11-02&sr=c&sig=CP4qQY1XHOnIOnvdccGcnK%2BuBtNBcdf0MDZ2R0%2Bqi10%3D", "wid": 200, "width": 200 }, "errorReason": "GenericError", - "errorMessage": "PUT 'https://adobesampleassetsrepous.blob.core.windows.net/nui-it-scratch?sp=racw&st=2023-11-13T23:04:46Z&se=2023-11-14T07:04:46Z&spr=https&sv=2022-11-02&sr=c&sig=CP4qQY1XHOnIOnvdccGcnK%2BuBtNBcdf0MDZ2R0%2Bqi10%3D' failed with status 400: \nMissingRequiredHeaderAn HTTP header that's mandatory for this request is not specified.\nRequestId:dc15d691-f01e-00b6-57ea-1c678c000000\nTime:2023-11-22T02:17:51.9326608Zx-ms-blob-type", + "errorMessage": "PUT 'bloblstore?sp=racw&st=2023-11-13T23:04:46Z&se=2023-11-14T07:04:46Z&spr=https&sv=2022-11-02&sr=c&sig=CP4qQY1XHOnIOnvdccGcnK%2BuBtNBcdf0MDZ2R0%2Bqi10%3D' failed with status 400: \nMissingRequiredHeaderAn HTTP header that's mandatory for this request is not specified.\nRequestId:dc15d691-f01e-00b6-57ea-1c678c000000\nTime:2023-11-22T02:17:51.9326608Zx-ms-blob-type", "type": "rendition_failed", "date": "2023-11-22T02:17:51.942Z", "requestId": "i0JHLeCEFiknQHhjticS7xJcQhubGRbQ", - "source": "https://adobesampleassetsrepous.blob.core.windows.net/nui-meahana/source/AdobeStock_64530079.jpeg?sp=r&st=2023-11-22T00:38:11Z&se=2023-11-22T08:38:11Z&spr=https&sv=2022-11-02&sr=b&sig=cagz7x3kKi2zdIn1SAWohm6MUUSHSRZXuX%2FvvepJFH4%3D", + "source": "bloblstore.jpeg?sp=r&st=2023-11-22T00:38:11Z&se=2023-11-22T08:38:11Z&spr=https&sv=2022-11-02&sr=b&sig=cagz7x3kKi2zdIn1SAWohm6MUUSHSRZXuX%2FvvepJFH4%3D", "userData": { "assetClass": "file", "companyId": "test", "contentHash": "d5a78270b79dc1242df6d12b70969b660503fc62a81a18c55cd4d6b792fc9716", "imsOrgId": "test@AdobeOrg", "ingestionId": "test-ingestion", - "itemId": "https://business.adobe.com/media_1ca933d32ca97e654f88cda8ad61a232bb4087ace.png", + "itemId": "https://business.adobe.com/some-media.png", "mimeType": "image/png", "requestId": "test-request" } From 7c140845326159e60b04cd984fb796bfcb54983e Mon Sep 17 00:00:00 2001 From: Krishnadas Menon Date: Wed, 22 Nov 2023 14:33:29 -0800 Subject: [PATCH 06/16] fix test --- test/events.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/events.test.js b/test/events.test.js index b71c2e7..eb26d1d 100644 --- a/test/events.test.js +++ b/test/events.test.js @@ -21,7 +21,7 @@ const path = require('path'); const nock = require('nock'); const assert = require('assert'); const MetricsTestHelper = require("@adobe/openwhisk-newrelic/lib/testhelper"); -const { generateHMACSignature, verifyHMACSign } = require('../lib/hmac-signature'); +const { verifyHMACSign } = require('../lib/hmac-signature'); const FAKE_PARAMS = { From d01b0b89fce3f2af517ce20330604b59eadf1202 Mon Sep 17 00:00:00 2001 From: Krishnadas Menon Date: Wed, 22 Nov 2023 15:01:47 -0800 Subject: [PATCH 07/16] comments --- lib/hmac-signature.js | 11 +++++++++++ lib/webhook.js | 9 +++++++++ 2 files changed, 20 insertions(+) diff --git a/lib/hmac-signature.js b/lib/hmac-signature.js index 68446a6..54d5190 100644 --- a/lib/hmac-signature.js +++ b/lib/hmac-signature.js @@ -14,6 +14,11 @@ const crypto = require('crypto'); +/** + * Generates a HMAC signature for the given data using the given private key. + * @param {string} data the data to sign + * @param {string} privateKey the private key to use + */ function generateHMACSignature(data, privateKey) { const sign = crypto.createSign('RSA-SHA256'); sign.write(data); @@ -22,6 +27,12 @@ function generateHMACSignature(data, privateKey) { return signature; } +/** + * Verifies the given signature for the given data using the given public key. Used for tests + * @param {string} data the data to verify + * @param {string} signature the signature to verify + * @param {string} pubKey the public key to use + */ function verifyHMACSign(data, signature, pubKey) { const verify = crypto.createVerify('RSA-SHA256'); verify.write(data); diff --git a/lib/webhook.js b/lib/webhook.js index bb54989..8543d43 100644 --- a/lib/webhook.js +++ b/lib/webhook.js @@ -31,6 +31,11 @@ class AssetComputeWebhookEvents { this.hmacPrivateKey = options.hmacPrivateKey; } + /** + * sends an event to the webhook + * @param {*} event event payload + * @param {*} retryOptions to be passed to fetch + */ async sendEvent(event, retryOptions) { const url = this.webhookUrl; const eventBody = JSON.stringify({ @@ -58,6 +63,10 @@ class AssetComputeWebhookEvents { } } + /** + * Gets the webhook url + * @returns the webhook url + */ getWebhookUrl() { return this.webhookUrl; } From b4298d429d9addc3be8cf8367b6979486db93c85 Mon Sep 17 00:00:00 2001 From: Krishnadas Menon Date: Wed, 22 Nov 2023 15:02:53 -0800 Subject: [PATCH 08/16] comments typo --- lib/webhook.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/webhook.js b/lib/webhook.js index 8543d43..e275349 100644 --- a/lib/webhook.js +++ b/lib/webhook.js @@ -33,8 +33,8 @@ class AssetComputeWebhookEvents { /** * sends an event to the webhook - * @param {*} event event payload - * @param {*} retryOptions to be passed to fetch + * @param {object} event event payload + * @param {object} retryOptions to be passed to fetch */ async sendEvent(event, retryOptions) { const url = this.webhookUrl; From fc4d1ef791faefaf2136a5c273924f6f15e5fade Mon Sep 17 00:00:00 2001 From: Krishnadas Menon Date: Wed, 22 Nov 2023 15:08:15 -0800 Subject: [PATCH 09/16] try catch --- lib/webhook.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/webhook.js b/lib/webhook.js index e275349..90e3204 100644 --- a/lib/webhook.js +++ b/lib/webhook.js @@ -43,7 +43,13 @@ class AssetComputeWebhookEvents { event_code: event.code, event:event.payload || {} }); - const signature = this.hmacPrivateKey ? generateHMACSignature(eventBody, this.hmacPrivateKey) : undefined; + let signature; + try{ + console.log('generating HMAC signature:'); + signature = this.hmacPrivateKey ? generateHMACSignature(eventBody, this.hmacPrivateKey) : undefined; + } catch(error) { + console.error(`Error generating HMAC signature: ${error}`); + } const response = await fetch(url, { method: 'POST', headers: { From b18b62b5eb619140c7a1743afd5adffef0eff73c Mon Sep 17 00:00:00 2001 From: Krishnadas Menon Date: Wed, 22 Nov 2023 15:23:41 -0800 Subject: [PATCH 10/16] test for hmac header generation --- lib/webhook.js | 4 +++- test/events.test.js | 47 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/lib/webhook.js b/lib/webhook.js index 90e3204..b59f874 100644 --- a/lib/webhook.js +++ b/lib/webhook.js @@ -48,7 +48,9 @@ class AssetComputeWebhookEvents { console.log('generating HMAC signature:'); signature = this.hmacPrivateKey ? generateHMACSignature(eventBody, this.hmacPrivateKey) : undefined; } catch(error) { - console.error(`Error generating HMAC signature: ${error}`); + const message = `Error generating HMAC signature: ${error}`; + console.error(message); + throw Error(message); } const response = await fetch(url, { method: 'POST', diff --git a/test/events.test.js b/test/events.test.js index eb26d1d..29200f3 100644 --- a/test/events.test.js +++ b/test/events.test.js @@ -238,7 +238,7 @@ describe("AssetComputeEvents", function() { ); await events.sendEvent("my_event", {test: "value"}); - assert.ok(nockSendEventWebHook.isDone(), "io event not tried"); + assert.ok(nockSendEventWebHook.isDone(), "webhook event not tried"); await MetricsTestHelper.metricsDone(); MetricsTestHelper.assertArrayContains(receivedMetrics, [{ @@ -255,6 +255,15 @@ describe("HMACSignature sendEvent - Webhook events with hmac signature", functio privateKey = fs.readFileSync(pvtkeyFilePath, 'utf8'); publicKey = fs.readFileSync(pubkeyFilePath, 'utf8'); }); + beforeEach(function() { + delete process.env.ASSET_COMPUTE_UNIT_TEST_OUT; + MetricsTestHelper.beforeEachTest(); + }); + + afterEach(function() { + MetricsTestHelper.afterEachTest(); + }); + it("sendEvent - Webhook events with hmac signature exists", async function() { const nockSendEventWebHook = nock(FAKE_WEBHOOK_URL,{ @@ -319,5 +328,41 @@ describe("HMACSignature sendEvent - Webhook events with hmac signature", functio assert.ok(nockSendEventWebHook.isDone(), "webhook event not properly sent"); assert.ok(verifyHMACSign(webhookPayload, signatureHeader, publicKey)); }); + it("sendEvent - Webhook events with hmac signature errors for invalid pvt key, sends metrics", async function() { + const nockSendEventWebHook = nock(FAKE_WEBHOOK_URL) + .filteringRequestBody(body => { + body = JSON.parse(body); + delete body.event.date; + console.log("Webhook mock received:", body); + return body; + }) + .post("/", { + user_guid: "orgId", + event_code: AssetComputeEvents.EVENT_CODE, + event: { + test: "value", + type: "my_event", + requestId: "requestId" + } + }) + .reply(200, {}); + + const receivedMetrics = MetricsTestHelper.mockNewRelic(); + process.env.__OW_DEADLINE = Date.now() + 2000; + FAKE_WEBHOOK_PARAMS.hmacPrivateKey = "invalid-privateKey"; + const events = new AssetComputeEvents({...FAKE_WEBHOOK_PARAMS, + metrics: new AssetComputeMetrics(FAKE_WEBHOOK_PARAMS, { sendImmediately: true })}); + await events.sendEvent("my_event", {test: "value"}); + + assert.ok(!nockSendEventWebHook.isDone(), "webhook event should not be tried"); + + await MetricsTestHelper.metricsDone(); + MetricsTestHelper.assertArrayContains(receivedMetrics, [{ + eventType: "error", + location: "WebhookEvents" + }]); + + + }); }); From 607a4ee3ea691b04b09152cad2eddb1a86f2d553 Mon Sep 17 00:00:00 2001 From: Krishnadas Menon Date: Mon, 27 Nov 2023 14:28:54 -0800 Subject: [PATCH 11/16] dont send webhook event when pvt key doesn't exist --- lib/hmac-signature.js | 3 +- lib/webhook.js | 24 ++++++++----- test/events.test.js | 80 ++++++++++++++++++++++++++++++++----------- 3 files changed, 78 insertions(+), 29 deletions(-) diff --git a/lib/hmac-signature.js b/lib/hmac-signature.js index 54d5190..f1b1a48 100644 --- a/lib/hmac-signature.js +++ b/lib/hmac-signature.js @@ -28,7 +28,8 @@ function generateHMACSignature(data, privateKey) { } /** - * Verifies the given signature for the given data using the given public key. Used for tests + * Verifies the given signature for the given data using the given public key. + * Currently used in tests * @param {string} data the data to verify * @param {string} signature the signature to verify * @param {string} pubKey the public key to use diff --git a/lib/webhook.js b/lib/webhook.js index b59f874..3cba08d 100644 --- a/lib/webhook.js +++ b/lib/webhook.js @@ -44,14 +44,22 @@ class AssetComputeWebhookEvents { event:event.payload || {} }); let signature; - try{ - console.log('generating HMAC signature:'); - signature = this.hmacPrivateKey ? generateHMACSignature(eventBody, this.hmacPrivateKey) : undefined; - } catch(error) { - const message = `Error generating HMAC signature: ${error}`; - console.error(message); - throw Error(message); - } + + if(this.hmacPrivateKey) { + console.log('generating HMAC signature'); + try{ + signature = generateHMACSignature(eventBody, this.hmacPrivateKey); + } catch(error) { + const message = `Error generating HMAC signature: ${error}`; + console.error(message); + // throw to send error metrics + throw Error(message); + } + } else { + console.log('HMAC private key not provided, skipping HMAC signature generation'); + // throw to send error metrics + throw Error("HMAC private key not provided"); + } const response = await fetch(url, { method: 'POST', headers: { diff --git a/test/events.test.js b/test/events.test.js index 29200f3..b653217 100644 --- a/test/events.test.js +++ b/test/events.test.js @@ -186,6 +186,25 @@ describe("AssetComputeEvents", function() { }]); }); + +}); + +describe("HMACSignature sendEvent - Webhook events with hmac signature", function() { + before(() => { + const pvtkeyFilePath = path.join(__dirname, 'resources/test-private.pem'); + const pubkeyFilePath = path.join(__dirname, 'resources/test-public.pem'); + privateKey = fs.readFileSync(pvtkeyFilePath, 'utf8'); + publicKey = fs.readFileSync(pubkeyFilePath, 'utf8'); + }); + beforeEach(function() { + delete process.env.ASSET_COMPUTE_UNIT_TEST_OUT; + MetricsTestHelper.beforeEachTest(); + }); + + afterEach(function() { + MetricsTestHelper.afterEachTest(); + }); + it("sendEvent - Webhook events", async function() { // not setting this delete process.env.ASSET_COMPUTE_UNIT_TEST_OUT; @@ -206,6 +225,7 @@ describe("AssetComputeEvents", function() { } }) .reply(200, {}); + FAKE_WEBHOOK_PARAMS.hmacPrivateKey = privateKey; const events = new AssetComputeEvents(FAKE_WEBHOOK_PARAMS); await events.sendEvent("my_event", {test: "value"}); assert.ok(nockSendEventWebHook.isDone(), "webhook event not properly sent"); @@ -229,7 +249,7 @@ describe("AssetComputeEvents", function() { const receivedMetrics = MetricsTestHelper.mockNewRelic(); process.env.__OW_DEADLINE = Date.now() + 2000; - + FAKE_WEBHOOK_PARAMS.hmacPrivateKey = privateKey; const events = new AssetComputeEvents({ ...FAKE_WEBHOOK_PARAMS, metrics: new AssetComputeMetrics(FAKE_WEBHOOK_PARAMS, { sendImmediately: true }) @@ -246,24 +266,6 @@ describe("AssetComputeEvents", function() { location: "WebhookEvents" }]); }); -}); - -describe("HMACSignature sendEvent - Webhook events with hmac signature", function() { - before(() => { - const pvtkeyFilePath = path.join(__dirname, 'resources/test-private.pem'); - const pubkeyFilePath = path.join(__dirname, 'resources/test-public.pem'); - privateKey = fs.readFileSync(pvtkeyFilePath, 'utf8'); - publicKey = fs.readFileSync(pubkeyFilePath, 'utf8'); - }); - beforeEach(function() { - delete process.env.ASSET_COMPUTE_UNIT_TEST_OUT; - MetricsTestHelper.beforeEachTest(); - }); - - afterEach(function() { - MetricsTestHelper.afterEachTest(); - }); - it("sendEvent - Webhook events with hmac signature exists", async function() { const nockSendEventWebHook = nock(FAKE_WEBHOOK_URL,{ @@ -361,8 +363,46 @@ describe("HMACSignature sendEvent - Webhook events with hmac signature", functio eventType: "error", location: "WebhookEvents" }]); + }); - + it("sendEvent - Webhook events when pvt key is not available", async function() { + const nockSendEventWebHook = nock(FAKE_WEBHOOK_URL,{ + reqheaders: { + 'x-ims-org-id': () => true, + 'x-ac-hmac-signature': (val) => { + console.log('val: ',val); + return val === "undefined"; + } + } + }) + .filteringRequestBody(body => { + body = JSON.parse(body); + delete body.event.date; + console.log("Webhook mock received:", body); + return body; + }) + .post("/", { + user_guid: "orgId", + event_code: AssetComputeEvents.EVENT_CODE, + event: { + test: "value", + type: "my_event", + requestId: "requestId" + } + }) + .reply(200, {}); + + const receivedMetrics = MetricsTestHelper.mockNewRelic(); + process.env.__OW_DEADLINE = Date.now() + 2000; + const events = new AssetComputeEvents({...FAKE_WEBHOOK_PARAMS, + metrics: new AssetComputeMetrics(FAKE_WEBHOOK_PARAMS, { sendImmediately: true })}); + await events.sendEvent("my_event", {test: "value"}); + assert.ok(!nockSendEventWebHook.isDone(), "webhook event not properly sent"); + await MetricsTestHelper.metricsDone(); + MetricsTestHelper.assertArrayContains(receivedMetrics, [{ + eventType: "error", + location: "WebhookEvents" + }]); }); }); From a7d1d3781e398931631147402dcc043df7fa34fd Mon Sep 17 00:00:00 2001 From: Krishnadas Menon Date: Wed, 29 Nov 2023 21:01:26 -0800 Subject: [PATCH 12/16] use base64 --- lib/webhook.js | 1 + test/events.test.js | 14 ++--- test/hmac-signature.test.js | 75 ++++++++++++++++++++++---- test/resources/test-private-base64.txt | 1 + test/resources/test-private.pem | 28 ---------- 5 files changed, 73 insertions(+), 46 deletions(-) create mode 100644 test/resources/test-private-base64.txt delete mode 100644 test/resources/test-private.pem diff --git a/lib/webhook.js b/lib/webhook.js index 3cba08d..a40b616 100644 --- a/lib/webhook.js +++ b/lib/webhook.js @@ -46,6 +46,7 @@ class AssetComputeWebhookEvents { let signature; if(this.hmacPrivateKey) { + this.hmacPrivateKey = Buffer.from(this.hmacPrivateKey, 'base64'); console.log('generating HMAC signature'); try{ signature = generateHMACSignature(eventBody, this.hmacPrivateKey); diff --git a/test/events.test.js b/test/events.test.js index b653217..55dee2e 100644 --- a/test/events.test.js +++ b/test/events.test.js @@ -68,7 +68,7 @@ const FAKE_WEBHOOK_PARAMS_NO_WEBHOOK = { } }; -let privateKey; +let base64privateKey; let publicKey; describe("AssetComputeEvents", function() { @@ -191,9 +191,9 @@ describe("AssetComputeEvents", function() { describe("HMACSignature sendEvent - Webhook events with hmac signature", function() { before(() => { - const pvtkeyFilePath = path.join(__dirname, 'resources/test-private.pem'); + const base64pvtkeyFilePath = path.join(__dirname, 'resources/test-private-base64.txt'); const pubkeyFilePath = path.join(__dirname, 'resources/test-public.pem'); - privateKey = fs.readFileSync(pvtkeyFilePath, 'utf8'); + base64privateKey = fs.readFileSync(base64pvtkeyFilePath, 'utf8'); publicKey = fs.readFileSync(pubkeyFilePath, 'utf8'); }); beforeEach(function() { @@ -225,7 +225,7 @@ describe("HMACSignature sendEvent - Webhook events with hmac signature", functio } }) .reply(200, {}); - FAKE_WEBHOOK_PARAMS.hmacPrivateKey = privateKey; + FAKE_WEBHOOK_PARAMS.hmacPrivateKey = base64privateKey; const events = new AssetComputeEvents(FAKE_WEBHOOK_PARAMS); await events.sendEvent("my_event", {test: "value"}); assert.ok(nockSendEventWebHook.isDone(), "webhook event not properly sent"); @@ -249,7 +249,7 @@ describe("HMACSignature sendEvent - Webhook events with hmac signature", functio const receivedMetrics = MetricsTestHelper.mockNewRelic(); process.env.__OW_DEADLINE = Date.now() + 2000; - FAKE_WEBHOOK_PARAMS.hmacPrivateKey = privateKey; + FAKE_WEBHOOK_PARAMS.hmacPrivateKey = base64privateKey; const events = new AssetComputeEvents({ ...FAKE_WEBHOOK_PARAMS, metrics: new AssetComputeMetrics(FAKE_WEBHOOK_PARAMS, { sendImmediately: true }) @@ -290,7 +290,7 @@ describe("HMACSignature sendEvent - Webhook events with hmac signature", functio } }) .reply(200, {}); - FAKE_WEBHOOK_PARAMS.hmacPrivateKey = privateKey; + FAKE_WEBHOOK_PARAMS.hmacPrivateKey = base64privateKey; const events = new AssetComputeEvents(FAKE_WEBHOOK_PARAMS); await events.sendEvent("my_event", {test: "value"}); assert.ok(nockSendEventWebHook.isDone(), "webhook event not properly sent"); @@ -324,7 +324,7 @@ describe("HMACSignature sendEvent - Webhook events with hmac signature", functio } }) .reply(200, {}); - FAKE_WEBHOOK_PARAMS.hmacPrivateKey = privateKey; + FAKE_WEBHOOK_PARAMS.hmacPrivateKey = base64privateKey; const events = new AssetComputeEvents(FAKE_WEBHOOK_PARAMS); await events.sendEvent("my_event", {test: "value"}); assert.ok(nockSendEventWebHook.isDone(), "webhook event not properly sent"); diff --git a/test/hmac-signature.test.js b/test/hmac-signature.test.js index 3d7d7eb..0ff60ee 100644 --- a/test/hmac-signature.test.js +++ b/test/hmac-signature.test.js @@ -18,15 +18,15 @@ const MetricsTestHelper = require("@adobe/openwhisk-newrelic/lib/testhelper"); const fs = require('fs'); const path = require('path'); -let privateKey; +let base64privateKey; let publicKey; -// const = fs.readFileSync('./test-public.pem'); + describe("HMACSignature", function() { before(() => { - const pvtkeyFilePath = path.join(__dirname, 'resources/test-private.pem'); + const base64pvtkeyFilePath = path.join(__dirname, 'resources/test-private-base64.txt'); const pubkeyFilePath = path.join(__dirname, 'resources/test-public.pem'); - privateKey = fs.readFileSync(pvtkeyFilePath, 'utf8'); + base64privateKey = fs.readFileSync(base64pvtkeyFilePath, 'utf8'); publicKey = fs.readFileSync(pubkeyFilePath, 'utf8'); }); beforeEach(function() { @@ -38,17 +38,21 @@ describe("HMACSignature", function() { MetricsTestHelper.afterEachTest(); }); - it("should generate HMACSignature", function() { + it("should generate HMACSignature using base64 pvtkey", function() { const data = 'Some data to sign'; - const signature = generateHMACSignature(data, privateKey); + const base64DecodeprivateKey = Buffer.from(base64privateKey, 'base64'); + const signature = generateHMACSignature(data, base64DecodeprivateKey); assert.ok(signature); }); - it("should verify HMAC signature", function() { + + it("should verify HMAC signature using base64 pvtkey", function() { const data = 'Some data to sign'; - const signature = generateHMACSignature(data, privateKey); + const base64DecodeprivateKey = Buffer.from(base64privateKey, 'base64'); + const signature = generateHMACSignature(data, base64DecodeprivateKey); assert.ok(verifyHMACSign(data, signature, publicKey)); }); - it("should verify HMAC signature payload", function() { + + it("should verify HMAC signature payload using base64 pvtkey", function() { const jsonData = { "user_guid": "test@AdobeOrg", "event_code": "asset_compute", @@ -82,14 +86,63 @@ describe("HMACSignature", function() { } }; const data = JSON.stringify(jsonData); - const signature = generateHMACSignature(data, privateKey); + const base64DecodeprivateKey = Buffer.from(base64privateKey, 'base64'); + const signature = generateHMACSignature(data, base64DecodeprivateKey); assert.ok(verifyHMACSign(data, signature, publicKey)); }); it("should fail HMAC signature verification if data changes", function() { let data = 'Some data to sign'; - const signature = generateHMACSignature(data, privateKey); + const base64DecodeprivateKey = Buffer.from(base64privateKey, 'base64'); + const signature = generateHMACSignature(data, base64DecodeprivateKey); data = 'data changes'; assert.ok(!verifyHMACSign(data, signature, publicKey)); }); + + it.skip("prod-test should verify HMAC signature", function() { + // const data = 'Some data to sign'; + const Jsondata = { + "user_guid": "Project Nui IT Org", + "event_code": "asset_compute", + "event": { + "rendition": { + "fmt": "png", + "hei": 200, + "height": 200, + "name": "rendition.png", + "pipeline": false, + "userData": { + "path": "4c60e8bb-2ca3-4d0e-b66e-402fbae36299/images/jpeg/2000x2000.jpg/rendition.png" + }, + "wid": 200, + "width": 200 + }, + "metadata": { + "repo:size": 78533, + "tiff:imageWidth": 200, + "tiff:imageHeight": 200, + "dc:format": "image/png", + "repo:sha1": "4e25bb2936809a8b6d075807a953fd9d63256563" + }, + "type": "rendition_created", + "date": "2023-11-29T22:45:32.265Z", + "requestId": "D5yzPaUIyNCXymTq03C7vcxcyAsP0f3X", + "source": "https://adobesampleassetsrepous.blob.core.windows.net/adobe-sample-asset-repository/images/jpeg/2000x2000.jpg?sv=2019-02-02&spr=https&se=2023-11-29T22%3A55%3A31Z&sr=b&sp=r&sig=x1pt9B1DyIPykHjsYOn5LX8aTp2y1bPDFsXeJr4q1TU%3D", + "userData": { + "assetClass": "file", + "companyId": "test", + "contentHash": "d5a78270b79dc1242df6d12b70969b660503fc62a81a18c55cd4d6b792fc9716", + "imsOrgId": "test@AdobeOrg", + "ingestionId": "test-ingestion", + "itemId": "https://business.adobe.com/media_087ace.png", + "mimeType": "image/png", + "requestId": "test-request" + } + } + }; + const data = JSON.stringify(Jsondata); + // const signature = generateHMACSignature(data, privateKey); + const signature = "J9omnuUC0gIaYcWS5RiOXznmyhw/AXnKJuXWquQoK4rXTes2vNUSwIAg4Tlmo+PgQ/1xDsTfzcrbRjjktVMFfBJTSpm1goTiI4CS0wv2bg3EbM1kzmvphUPQ9dMJ5HWleOD8XklWLexaw+ApEzQZp1H+AhXk2nUGJsUgqB1UUIhPJvZALN/vXw9oE06rSb647yCSV3WP4JWRYXeu5s+sjYLbRNYqukzJum5EiKTh3qslhvNjX+DwEBr65ePqolQYnrBTDmz63nQew/3bpLkd1nJh8cUt4Z6Swir4gJDAVgUA/BzfU1oEahtWZIRsu8BigAQHzMaLOaU7dR2KJeikPQ=="; + assert.ok(verifyHMACSign(data, signature, publicKey)); + }); }); diff --git a/test/resources/test-private-base64.txt b/test/resources/test-private-base64.txt new file mode 100644 index 0000000..32cd827 --- /dev/null +++ b/test/resources/test-private-base64.txt @@ -0,0 +1 @@ +LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQ3M5TW41clRLVFYyelEKZjBVbGMxdWJ1eStROXpNaGFmeXRHc2VZWmFnTERZdktxT2lCMXdlb3RkS05DbHg0b05SMVdQKzVUN0tzVFBPeApmYldUcUFqbWlkbnlRcnFpVlJMQWM3alFHSHlQVmloTWc4OWp2NWxuZE9raHF1KzVWT3MzNnptQzR1ZFRsV1RUClJ1bEtFcWhxc0xmLzJCMXNVRHI5WkxOL0lqSHkvK1VIcTkxYlBldE5xcmdTejBLSUMvYXhoeVJCczg0N3l2UWwKZFFVSGczaTRxbFpMRERHejQxSDRHZHZHYmtBR25yaFpFQnQ5VzNOOHpDUjIyZWJEdi9Vc0cvcTl1d3lhSWpZMQp0MFFNRXBia2lMUk10TGtReWE2N0VPVk5Gb0dVZjdSb2dPUzFWMFM1cUduMjBxRnpVdlVvLzNiVU1OVWorY2ZhClc3bkJBSnh4QWdNQkFBRUNnZ0VBTi80ejJVb1NJcE5lSGczbDg5N3AzSHY0UlVWU1gxSHh4SlR6NDBtZDRwNHIKRUp3b1VjaXNxUmpjSmw4aFdzZ3VvQ01FL3hobkx0TG0zaUpsZzMwV0RKUzZZMnpwQVI3cnFEU09zNGdKNDFtdwpTRDB3Q2dvQWZ2YmhkczNoT0MzZXpNYXhZc2RVdnNHUjE3aVFWRFBBSkhJN3oyOUc3MGIySWd6VTg2YTladjZhCi9LKzA1NlJJMWdsamVVZzg4VDBBanBNN1dDayswL2VZKyt1d1AreGpYTlh3ejJlU0F1THZXSS9OTVhFWWxLa1kKKzhsSDZUakViSHRpZG53UldJOVM4NmtpOURrdE1MTTlwZWExNWdtR2ZEM0ZSZW9VUTVqVzhVWExDdFUrUXBxVApsYVBiS0xJM0hXa2dycHJBb3ArdDFrTHZxQ1N1UVA1ZWJjenFjM1JhUVFLQmdRRFhyVjlPcVF1RHBqVmtWZlJuCkZKNnpzTXVVN1AwcEM4THptckoxZGQvTzQwV3lRSmxyWUY0Q1pPT1dRbzZxSkdHTUMvUm1BMmc1MHk2VmFlL3YKNUxlMXhoMVNjNVBFY0NIWVcwcHJJamxXZFpiNkJWT3ZmZWthU2RCTFNmUnlZYUNwZURHeW9HaTJaWERXWmFOdQovNGZLYXZMaWdsYkJzck0zcW5KcGl1Y1gyUUtCZ1FETlNybk9XZHhrNWpXMXhycHI4Zk1NdkdzdHM2b0dTbVBQCnRKdXV5YTFmZUNnMUhhUHpLMWVnZm8yM0g2enUyUWxnL25LNEVIKzVRVjZLTUlPQTdydmFXUG1Gb2VIY3hKVk4KZDFZbmFxVlpzN1JDSG81ai9KV2U0TUZzb0E2dU9rNUdhSTFBb0Y4ckY2RytCSkIzK0l5K3V1Y1hpTWlLSUNaWApxejVwRE9LaVdRS0JnUUN4VHZESmhDUFpLUzAvdllKZHRBLzhmZDRBSXd6RlQ4d0g1U2ZOZFFoMzExUVhCUkNiCnUrL05YQS9XR2dXYlRxbDdMdURQZFFHY2VFY1Z3cS9rc2sxdGZOL2lSdTQyVWxFZ3czeGNzeGdjUWpQS2U5S2UKdFk2TGRCOXZwZTcya2RnNU9uenc4dnJpNkdacGU4bHVtVmlUU25VL1RoUHd6ZTZVVnVZVWFvTXRrUUtCZ0NmSQpQMUY1b0hmOUZKTnhPMWNIdiszTDVZa3BzOTBjTlZOZTBIeUNvSkpMbXA4UWEyRUlHU2NFM3ptSmJkMnJPV0lPCmJQUzJYY25zblFGeWZVbFMraHhKK2JDbnVqa3A3elRmMTFZMEdHN1ZvQ3pmOXYveFJwMVBPS3h3SitnamJGL0EKaXY3bkZRb292ak13ZHo2SWI5M1dJa294TzR0b3g0VmRyZTUxak1XaEFvR0FEUkxtaS85bFRQTHVCT3VaOXBObQpMOWsvLy9BSXFJcmFTRDBwT016d3RBc29sdVc3WUsvMGszTzdJY04yRXhXQWZHa0RIME1BMy9DNlJrVW12UlUwCkhuOUxOVElkd09JTW83UVdRd3k0NjMxMHprWFRkQVV0TTkvWnRSdXEvL3grV3FFYUdZUTJKYWdDL09TWk1rSkkKZHg0SDl0YWFGdGFvV29YSTc1bGtJNVk9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K diff --git a/test/resources/test-private.pem b/test/resources/test-private.pem deleted file mode 100644 index 882534e..0000000 --- a/test/resources/test-private.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCs9Mn5rTKTV2zQ -f0Ulc1ubuy+Q9zMhafytGseYZagLDYvKqOiB1weotdKNClx4oNR1WP+5T7KsTPOx -fbWTqAjmidnyQrqiVRLAc7jQGHyPVihMg89jv5lndOkhqu+5VOs36zmC4udTlWTT -RulKEqhqsLf/2B1sUDr9ZLN/IjHy/+UHq91bPetNqrgSz0KIC/axhyRBs847yvQl -dQUHg3i4qlZLDDGz41H4GdvGbkAGnrhZEBt9W3N8zCR22ebDv/UsG/q9uwyaIjY1 -t0QMEpbkiLRMtLkQya67EOVNFoGUf7RogOS1V0S5qGn20qFzUvUo/3bUMNUj+cfa -W7nBAJxxAgMBAAECggEAN/4z2UoSIpNeHg3l897p3Hv4RUVSX1HxxJTz40md4p4r -EJwoUcisqRjcJl8hWsguoCME/xhnLtLm3iJlg30WDJS6Y2zpAR7rqDSOs4gJ41mw -SD0wCgoAfvbhds3hOC3ezMaxYsdUvsGR17iQVDPAJHI7z29G70b2IgzU86a9Zv6a -/K+056RI1gljeUg88T0AjpM7WCk+0/eY++uwP+xjXNXwz2eSAuLvWI/NMXEYlKkY -+8lH6TjEbHtidnwRWI9S86ki9DktMLM9pea15gmGfD3FReoUQ5jW8UXLCtU+QpqT -laPbKLI3HWkgrprAop+t1kLvqCSuQP5ebczqc3RaQQKBgQDXrV9OqQuDpjVkVfRn -FJ6zsMuU7P0pC8LzmrJ1dd/O40WyQJlrYF4CZOOWQo6qJGGMC/RmA2g50y6Vae/v -5Le1xh1Sc5PEcCHYW0prIjlWdZb6BVOvfekaSdBLSfRyYaCpeDGyoGi2ZXDWZaNu -/4fKavLiglbBsrM3qnJpiucX2QKBgQDNSrnOWdxk5jW1xrpr8fMMvGsts6oGSmPP -tJuuya1feCg1HaPzK1egfo23H6zu2Qlg/nK4EH+5QV6KMIOA7rvaWPmFoeHcxJVN -d1YnaqVZs7RCHo5j/JWe4MFsoA6uOk5GaI1AoF8rF6G+BJB3+Iy+uucXiMiKICZX -qz5pDOKiWQKBgQCxTvDJhCPZKS0/vYJdtA/8fd4AIwzFT8wH5SfNdQh311QXBRCb -u+/NXA/WGgWbTql7LuDPdQGceEcVwq/ksk1tfN/iRu42UlEgw3xcsxgcQjPKe9Ke -tY6LdB9vpe72kdg5Onzw8vri6GZpe8lumViTSnU/ThPwze6UVuYUaoMtkQKBgCfI -P1F5oHf9FJNxO1cHv+3L5Ykps90cNVNe0HyCoJJLmp8Qa2EIGScE3zmJbd2rOWIO -bPS2XcnsnQFyfUlS+hxJ+bCnujkp7zTf11Y0GG7VoCzf9v/xRp1POKxwJ+gjbF/A -iv7nFQoovjMwdz6Ib93WIkoxO4tox4Vdre51jMWhAoGADRLmi/9lTPLuBOuZ9pNm -L9k///AIqIraSD0pOMzwtAsoluW7YK/0k3O7IcN2ExWAfGkDH0MA3/C6RkUmvRU0 -Hn9LNTIdwOIMo7QWQwy46310zkXTdAUtM9/ZtRuq//x+WqEaGYQ2JagC/OSZMkJI -dx4H9taaFtaoWoXI75lkI5Y= ------END PRIVATE KEY----- From f2c41358c3cafdda8aede052407881c83bd3ae01 Mon Sep 17 00:00:00 2001 From: Krishnadas Menon Date: Wed, 29 Nov 2023 21:27:31 -0800 Subject: [PATCH 13/16] fix test --- test/events.test.js | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/test/events.test.js b/test/events.test.js index 0e9f1c1..8fdf50f 100644 --- a/test/events.test.js +++ b/test/events.test.js @@ -264,23 +264,6 @@ describe("HMACSignature sendEvent - Webhook events with hmac signature", functio location: "WebhookEvents" }]); }); -}); - -describe("HMACSignature sendEvent - Webhook events with hmac signature", function() { - before(() => { - const pvtkeyFilePath = path.join(__dirname, 'resources/test-private.pem'); - const pubkeyFilePath = path.join(__dirname, 'resources/test-public.pem'); - privateKey = fs.readFileSync(pvtkeyFilePath, 'utf8'); - publicKey = fs.readFileSync(pubkeyFilePath, 'utf8'); - }); - beforeEach(function() { - delete process.env.ASSET_COMPUTE_UNIT_TEST_OUT; - MetricsTestHelper.beforeEachTest(); - }); - - afterEach(function() { - MetricsTestHelper.afterEachTest(); - }); it("sendEvent - Webhook events with hmac signature exists", async function() { const nockSendEventWebHook = nock(FAKE_WEBHOOK_URL,{ @@ -421,5 +404,4 @@ describe("HMACSignature sendEvent - Webhook events with hmac signature", functio location: "WebhookEvents" }]); }); - }); From d849f6a6007d68451df73cbff84628b41ee3adfe Mon Sep 17 00:00:00 2001 From: Krishnadas Menon Date: Wed, 29 Nov 2023 22:16:42 -0800 Subject: [PATCH 14/16] more logs --- lib/webhook.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/webhook.js b/lib/webhook.js index a40b616..5d3e862 100644 --- a/lib/webhook.js +++ b/lib/webhook.js @@ -46,10 +46,10 @@ class AssetComputeWebhookEvents { let signature; if(this.hmacPrivateKey) { - this.hmacPrivateKey = Buffer.from(this.hmacPrivateKey, 'base64'); - console.log('generating HMAC signature'); + const decodeHmacPrivateKey = Buffer.from(this.hmacPrivateKey, 'base64'); + console.log('generating HMAC signature using private key'); try{ - signature = generateHMACSignature(eventBody, this.hmacPrivateKey); + signature = generateHMACSignature(eventBody, decodeHmacPrivateKey); } catch(error) { const message = `Error generating HMAC signature: ${error}`; console.error(message); From a5e6aa24a1da592a86fde92aff748088e022cb06 Mon Sep 17 00:00:00 2001 From: Krishnadas Menon Date: Wed, 29 Nov 2023 23:14:09 -0800 Subject: [PATCH 15/16] post test --- lib/webhook.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/webhook.js b/lib/webhook.js index 5d3e862..ea9375b 100644 --- a/lib/webhook.js +++ b/lib/webhook.js @@ -73,8 +73,7 @@ class AssetComputeWebhookEvents { retryOptions: retryOptions }); if (response.status !== 200 && response.status !== 202) { - // If retry is disabled then anything other than 200 or 202 is considered an error - + // If retry is disabled then anything other than 200 or 202 is considered as an error console.log(`sending event failed with ${response.status} ${response.statusText}`); throw Error(`${response.status} ${response.statusText}`); } From 7a3229700e5bc6652fe306194bf60d7a190d949e Mon Sep 17 00:00:00 2001 From: Krishnadas Menon Date: Fri, 1 Dec 2023 16:12:47 -0800 Subject: [PATCH 16/16] Update webhook.js --- lib/webhook.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/webhook.js b/lib/webhook.js index ea9375b..fa2509f 100644 --- a/lib/webhook.js +++ b/lib/webhook.js @@ -53,12 +53,12 @@ class AssetComputeWebhookEvents { } catch(error) { const message = `Error generating HMAC signature: ${error}`; console.error(message); - // throw to send error metrics + // throw error to send error metrics throw Error(message); } } else { console.log('HMAC private key not provided, skipping HMAC signature generation'); - // throw to send error metrics + // throw error to send error metrics throw Error("HMAC private key not provided"); } const response = await fetch(url, {