I am trying to validate SHA1 signed data sent to Salesforce through webhook json message. I am getting the error System.SecurityException: Invalid Crypto Key
I have PEM format public key. According to the solution documentation: "publicKey: the public key that you need to verify a webhook signature. The signature is a hex representation of the binary value so in order to verify it, you need to convert it back to a binary value."
Public key returned:
{
"publicKey": "-----BEGIN PUBLIC KEY-----\nMFkwEwadawrgRGWSrgIKoZIzj0DAQcDQgAEPWrE9xyDDHHqm9KgjnO2kXfFPXkn\nJbB3LhUm+08AU18hWV4OL31xA4sinrBP4UtUjxWUyUekbbG1tjchuaHzWw==\n-----END PUBLIC KEY-----\n"
}
So what I did is take the public key value from there and get the blob value:
//Convert public key to base64-encoded string
String base64PublicKey = pemPublicKeyStr.replace('-----BEGIN PUBLIC KEY-----\n', '').replace('\n-----END PUBLIC KEY-----\n', '').replaceAll('\\s', '');
// Convert base64-encoded string to Blob
Blob publicKeyBlob = EncodingUtil.base64Decode(base64PublicKey);
Json data that the webhook sends is something like this with X-Signature header.
{
"passId": "54b4d891-4454-435d-b07f-308d01b187d3",
"uniqueIdentifier": "zcmx0ajw2u8eo2ut3p6220a38a6c856",
"createdOn": "2022-03-03 11:16:26",
"passTemplate": "Example",
"userProvidedId": "",
"bulkCreationId": "",
"identifier": "54b4d891-4454-435d-b07f-308d01b187d3",
"barcodeValue": "zcmx0ajw2u8eo2ut3p6220a38a6c856",
"barcodeAlternativeText": "",
"buyerEmail": "",
"buyerFirstName": "",
"buyerLastName": "",
"buyerStreet": "",
"buyerCity": "",
"buyerZipCode": "",
"passTemplateGuid": "a11bd546-94fe-4346-9d41-d26c59a1b4c2",
"linkToPassPage": "https://app.passcreator.com/en/p/dzy1md7urtpg56220a38a6c842",
"620cf34ddf3797.66657500": "test",
"620cf34ddf3887.77530082": "abcdef",
"620cf34ddf38a6.75099306": "",
"620cf34ddf38b5.84124968": "",
"620cf9aea1bdf1.44735370": "",
"620cf9aea1bf14.91206312": "",
"620cf9aea1bf27.81912696": "",
"genericProperties": [],
"signature": "3046022100c0c9338610b47b29aaa037cb98a97835835a2ef322cce70df5b8c5dc42934db3022100fdbcbd9436b57c12fea530de9d1a33c8098f7b52a1a1f9e9a951ab1457848861",
"signedData": "{\"passId\":\"54b4d891-4454-435d-b07f-308d01b187d3\",\"uniqueIdentifier\":\"zcmx0ajw2u8eo2ut3p6220a38a6c856\",\"createdOn\":\"2022-03-03 11:16:26\",\"passTemplate\":\"Example\",\"userProvidedId\":\"\",\"bulkCreationId\":\"\",\"identifier\":\"54b4d891-4454-435d-b07f-308d01b187d3\",\"barcodeValue\":\"zcmx0ajw2u8eo2ut3p6220a38a6c856\",\"barcodeAlternativeText\":\"\",\"buyerEmail\":\"\",\"buyerFirstName\":\"\",\"buyerLastName\":\"\",\"buyerStreet\":\"\",\"buyerCity\":\"\",\"buyerZipCode\":\"\",\"passTemplateGuid\":\"a11bd546-94fe-4346-9d41-d26c59a1b4c2\",\"linkToPassPage\":\"https:\\/\\/app.passcreator.com\\/en\\/p\\/dzy1md7urtpg56220a38a6c842\",\"620cf34ddf3797.66657500\":\"test\",\"620cf34ddf3887.77530082\":\"abcdef\",\"620cf34ddf38a6.75099306\":\"\",\"620cf34ddf38b5.84124968\":\"\",\"620cf9aea1bdf1.44735370\":\"\",\"620cf9aea1bf14.91206312\":\"\",\"620cf9aea1bf27.81912696\":\"\",\"genericProperties\":[]}"
}
I get the signedData string from the response and get the blob value:
// Create Blob of data to verify
String signedData = (String)jsonResponse.get('signedData');
System.debug('signedData: ' + signedData);
Blob dataToVerify = Blob.valueOf(signedData);
Do similar for the signature from the header:
// Create Blob of signature
String signatureString = request.headers.get('X-Signature');
Blob signature = EncodingUtil.convertFromHex(signatureString);
Than I call the Crypto.verify function https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_classes_restful_crypto.htm#apex_System_Crypto_verify
// Verify signature with public key using SHA1 algorithm
Boolean isVerified = Crypto.verify('SHA1', dataToVerify, signature, publicKeyBlob);
The webhook is coming from passcreator that have documentation for PHP and C# here: https://developer.passcreator.com/space/API/837320711/Verify+the+signature+of+a+webhook
Can anyone see what I am missing here ?
SHA1withECDSA
, that's not the same scheme as SHA1 aka RSA-SHA1. TheCrypto
class supports ECDSA with SHA256/384/512 so you'd have to change yours to one of these to even be in the same neighborhood. Even if you do so, attempting to use elliptic curves in native Apex is a dark and twisty road. Think about doing this in another tech stack with well-known crypto libraries. Salesforce's serverless stack (Functions) is a good fit for this use case.