Here is a C# method that validates a webhook signature. The signature is generated by the Ubble API.
private bool SignatureValid(string rawBody, string signatureHeader, string pubKey)
{
string[] signatureParts = signatureHeader.Split(':');
if (signatureParts.Length is not 3)
{
logger.LogError("Webhook signature not split into 3 parts: {sig}", signatureHeader);
return false;
}
string timestamp = signatureParts[0];
string signature = signatureParts[2];
string signedPayload = $"{timestamp}:{rawBody}";
byte[] signedPayloadBytes = Encoding.UTF8.GetBytes(signedPayload);
using ECDsa ecdsa = ECDsa.Create();
ecdsa.ImportFromPem($"-----BEGIN PUBLIC KEY-----\n{pubKey}\n-----END PUBLIC KEY-----".ToCharArray());
byte[] signatureBytes = Convert.FromBase64String(signature);
bool isValidSignature = ecdsa.VerifyData(signedPayloadBytes, signatureBytes, HashAlgorithmName.SHA512);
if (!isValidSignature) logger.LogError("Unable to verify signature for body and sig header: {body}, {sig}", rawBody, signatureHeader);
return isValidSignature;
}
The problem is that isValidSignature
is always false. This method accepts three parameters - the body of the webhook, the signature the issuer places in the header and the public key. I will share them as well. Can you spot what is wrong the logic given the three inputs?
rawBody
:
{"specversion": "2.0", "type": "identity_verification_created", "subject": "idv_01j1zgha0pa5cwhxfhcw08da7p", "id": "evnt_01j1zgha43jd06kgzs1xe1hy96", "time": "2024-07-04T18:36:32Z", "datacontenttype": "application/json", "data": {"applicant_id": "aplt_01j1zgh9z21hjwk045ednvxbs9", "status": "pending", "identity_verification_id": "idv_01j1zgha0pa5cwhxfhcw08da7p", "response_codes": [], "user_journey_id": "usj_01j19zhh6bjwfad1w464vbzhg0"}}
signatureHeader
:
1720118192.350353:2530-test-v1:MIGHAkEMjhZbBcB6RxOhXURr0+VV8j66Wgml+UpITG3sE54nVju7K2oFOWSIX/4W1kp0jfyY1DmOu8rVpPCFjnikRB/jWAJCAQhStnif+xLIOYavKos3c8kF+0nAOMJdVQAI3fY/mPfjD7Kqru2JxRaFPcz0FJPUekL+6x9nhNAx3v23ByuDRPrg
pubKey
:
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBJKgbekd2Q01oTmu7FK8m8hASky+O
ssU7lFAuTIVd5/tA+h8Cjh2ieK+gjv8bHLov6b+3eegWvp+aq25nge/nCHcBCrfi
7gJDMuZ5Yg5kG4+mEwADC8caLhejF3ITs1n01dwuWHgD2/jniqvhVwITNRta00+K
PRCYbQjw32/kti8WV50=
Please note I have a Java sample that is known to work. I can share that as well if needed.
The signature has the ASN.1/DER format. .NET uses P1363 by default, ASN.1/DER must be explicitly specified in the fourth parameter of VerifyData()
:
...
bool isValidSignature = ecdsa.VerifyData(signedPayloadBytes, signatureBytes, HashAlgorithmName.SHA512, DSASignatureFormat.Rfc3279DerSequence);
...
With this fix, verification is successful.