Skip to content
Open
3 changes: 2 additions & 1 deletion lib/hmac-signature.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 18 additions & 10 deletions lib/webhook.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,23 @@ 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) {
const decodeHmacPrivateKey = Buffer.from(this.hmacPrivateKey, 'base64');
console.log('generating HMAC signature using private key');
try{
signature = generateHMACSignature(eventBody, decodeHmacPrivateKey);
} catch(error) {
const message = `Error generating HMAC signature: ${error}`;
console.error(message);
// throw error to send error metrics
throw Error(message);
}
} else {
console.log('HMAC private key not provided, skipping HMAC signature generation');
// throw error to send error metrics
throw Error("HMAC private key not provided");
}
const response = await fetch(url, {
method: 'POST',
headers: {
Expand All @@ -64,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}`);
}
Expand Down
87 changes: 63 additions & 24 deletions test/events.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ const FAKE_WEBHOOK_PARAMS_NO_WEBHOOK = {
}
};

let privateKey;
let base64privateKey;
let publicKey;

describe("AssetComputeEvents", function() {
Expand Down Expand Up @@ -185,6 +185,23 @@ describe("AssetComputeEvents", function() {
location: "IOEvents"
}]);
});
});

describe("HMACSignature sendEvent - Webhook events with hmac signature", function() {
before(() => {
const base64pvtkeyFilePath = path.join(__dirname, 'resources/test-private-base64.txt');
const pubkeyFilePath = path.join(__dirname, 'resources/test-public.pem');
base64privateKey = fs.readFileSync(base64pvtkeyFilePath, '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
Expand All @@ -206,6 +223,7 @@ describe("AssetComputeEvents", function() {
}
})
.reply(200, {});
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");
Expand All @@ -229,7 +247,7 @@ describe("AssetComputeEvents", function() {
const receivedMetrics = MetricsTestHelper.mockNewRelic();

process.env.__OW_DEADLINE = Date.now() + 2000;

FAKE_WEBHOOK_PARAMS.hmacPrivateKey = base64privateKey;
const events = new AssetComputeEvents({
...FAKE_WEBHOOK_PARAMS,
metrics: new AssetComputeMetrics(FAKE_WEBHOOK_PARAMS, { sendImmediately: true })
Expand All @@ -246,24 +264,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,{
Expand All @@ -288,11 +288,12 @@ 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");
});

it("sendEvent - Webhook events with hmac signature using pvt-pub keypair", async function() {
let webhookPayload;
let signatureHeader;
Expand Down Expand Up @@ -322,12 +323,13 @@ 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");
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 => {
Expand Down Expand Up @@ -361,8 +363,45 @@ 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"
}]);
});
});
74 changes: 63 additions & 11 deletions test/hmac-signature.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,14 @@ 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() {
Expand All @@ -38,17 +37,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",
Expand Down Expand Up @@ -82,14 +85,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));
});
});
1 change: 1 addition & 0 deletions test/resources/test-private-base64.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQ3M5TW41clRLVFYyelEKZjBVbGMxdWJ1eStROXpNaGFmeXRHc2VZWmFnTERZdktxT2lCMXdlb3RkS05DbHg0b05SMVdQKzVUN0tzVFBPeApmYldUcUFqbWlkbnlRcnFpVlJMQWM3alFHSHlQVmloTWc4OWp2NWxuZE9raHF1KzVWT3MzNnptQzR1ZFRsV1RUClJ1bEtFcWhxc0xmLzJCMXNVRHI5WkxOL0lqSHkvK1VIcTkxYlBldE5xcmdTejBLSUMvYXhoeVJCczg0N3l2UWwKZFFVSGczaTRxbFpMRERHejQxSDRHZHZHYmtBR25yaFpFQnQ5VzNOOHpDUjIyZWJEdi9Vc0cvcTl1d3lhSWpZMQp0MFFNRXBia2lMUk10TGtReWE2N0VPVk5Gb0dVZjdSb2dPUzFWMFM1cUduMjBxRnpVdlVvLzNiVU1OVWorY2ZhClc3bkJBSnh4QWdNQkFBRUNnZ0VBTi80ejJVb1NJcE5lSGczbDg5N3AzSHY0UlVWU1gxSHh4SlR6NDBtZDRwNHIKRUp3b1VjaXNxUmpjSmw4aFdzZ3VvQ01FL3hobkx0TG0zaUpsZzMwV0RKUzZZMnpwQVI3cnFEU09zNGdKNDFtdwpTRDB3Q2dvQWZ2YmhkczNoT0MzZXpNYXhZc2RVdnNHUjE3aVFWRFBBSkhJN3oyOUc3MGIySWd6VTg2YTladjZhCi9LKzA1NlJJMWdsamVVZzg4VDBBanBNN1dDayswL2VZKyt1d1AreGpYTlh3ejJlU0F1THZXSS9OTVhFWWxLa1kKKzhsSDZUakViSHRpZG53UldJOVM4NmtpOURrdE1MTTlwZWExNWdtR2ZEM0ZSZW9VUTVqVzhVWExDdFUrUXBxVApsYVBiS0xJM0hXa2dycHJBb3ArdDFrTHZxQ1N1UVA1ZWJjenFjM1JhUVFLQmdRRFhyVjlPcVF1RHBqVmtWZlJuCkZKNnpzTXVVN1AwcEM4THptckoxZGQvTzQwV3lRSmxyWUY0Q1pPT1dRbzZxSkdHTUMvUm1BMmc1MHk2VmFlL3YKNUxlMXhoMVNjNVBFY0NIWVcwcHJJamxXZFpiNkJWT3ZmZWthU2RCTFNmUnlZYUNwZURHeW9HaTJaWERXWmFOdQovNGZLYXZMaWdsYkJzck0zcW5KcGl1Y1gyUUtCZ1FETlNybk9XZHhrNWpXMXhycHI4Zk1NdkdzdHM2b0dTbVBQCnRKdXV5YTFmZUNnMUhhUHpLMWVnZm8yM0g2enUyUWxnL25LNEVIKzVRVjZLTUlPQTdydmFXUG1Gb2VIY3hKVk4KZDFZbmFxVlpzN1JDSG81ai9KV2U0TUZzb0E2dU9rNUdhSTFBb0Y4ckY2RytCSkIzK0l5K3V1Y1hpTWlLSUNaWApxejVwRE9LaVdRS0JnUUN4VHZESmhDUFpLUzAvdllKZHRBLzhmZDRBSXd6RlQ4d0g1U2ZOZFFoMzExUVhCUkNiCnUrL05YQS9XR2dXYlRxbDdMdURQZFFHY2VFY1Z3cS9rc2sxdGZOL2lSdTQyVWxFZ3czeGNzeGdjUWpQS2U5S2UKdFk2TGRCOXZwZTcya2RnNU9uenc4dnJpNkdacGU4bHVtVmlUU25VL1RoUHd6ZTZVVnVZVWFvTXRrUUtCZ0NmSQpQMUY1b0hmOUZKTnhPMWNIdiszTDVZa3BzOTBjTlZOZTBIeUNvSkpMbXA4UWEyRUlHU2NFM3ptSmJkMnJPV0lPCmJQUzJYY25zblFGeWZVbFMraHhKK2JDbnVqa3A3elRmMTFZMEdHN1ZvQ3pmOXYveFJwMVBPS3h3SitnamJGL0EKaXY3bkZRb292ak13ZHo2SWI5M1dJa294TzR0b3g0VmRyZTUxak1XaEFvR0FEUkxtaS85bFRQTHVCT3VaOXBObQpMOWsvLy9BSXFJcmFTRDBwT016d3RBc29sdVc3WUsvMGszTzdJY04yRXhXQWZHa0RIME1BMy9DNlJrVW12UlUwCkhuOUxOVElkd09JTW83UVdRd3k0NjMxMHprWFRkQVV0TTkvWnRSdXEvL3grV3FFYUdZUTJKYWdDL09TWk1rSkkKZHg0SDl0YWFGdGFvV29YSTc1bGtJNVk9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K