I'm trying to verify a signature I get in the callback of Payconiq (payment-platform)
The signature is put together based on this logic
A JWS represents these logical values separated by dots(.):
- JOSE Header
- JWS Payload (Not included)
- JWS Signature
The signature will be generated as per following instructions:
jws = base64URLEncode(JOSE Header)..base64URLEncode(alg(base64URLEncode(JOSE Header).base64URLEncode(Request Body)))
The data I have available is:
MIIE1zCCBH2gAwIBAgIQHzgeQOjemgrfp6IwTS5XfzAKBggqhkjOPQQDAjCBjzELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQDEy5TZWN0aWdvIEVDQyBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENBMB4XDTIxMTEyMzAwMDAwMFoXDTIyMTIyNDIzNTk1OVowKDEmMCQGA1UEAxMdZXMuc2lnbmF0dXJlLmV4dC5wYXljb25pcS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARIpLe02lsuMs6G1lQQRw3Zo4GlBwxi1h7EDD6GC9MxYRkkxOQMrJ1UKD3ni4dXcCZjHyv2GGvWhNICOaCso9Elo4IDHzCCAxswHwYDVR0jBBgwFoAU9oUKOxGG4QR9DqoLLNLuzGR7e64wHQYDVR0OBBYEFHUsvJY0jGLPbsoGZeOmkk09+ADEMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjBJBgNVHSAEQjBAMDQGCysGAQQBsjEBAgIHMCUwIwYIKwYBBQUHAgEWF2h0dHBzOi8vc2VjdGlnby5jb20vQ1BTMAgGBmeBDAECATCBhAYIKwYBBQUHAQEEeDB2ME8GCCsGAQUFBzAChkNodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29FQ0NEb21haW5WYWxpZGF0aW9uU2VjdXJlU2VydmVyQ0EuY3J0MCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTBLBgNVHREERDBCgh1lcy5zaWduYXR1cmUuZXh0LnBheWNvbmlxLmNvbYIhd3d3LmVzLnNpZ25hdHVyZS5leHQucGF5Y29uaXEuY29tMIIBewYKKwYBBAHWeQIEAgSCAWsEggFnAWUAdQBGpVXrdfqRIDC1oolp9PN9ESxBdL79SbiFq/L8cP5tRwAAAX1MZuRtAAAEAwBGMEQCIErmMHlQjPe/aNTo08NiFGS2hlKeBU5Ubrl9OG7myLWcAiB4bWXL8HOl2oNVci3Cv0RMnNTyMHIrAm8Lw9QQq/UxTQB1AEHIyrHfIkZKEMahOglCh15OMYsbA+vrS8do8JBilgb2AAABfUxm5DUAAAQDAEYwRAIgNEbgqCHIAjLqhRGBmiHRAqNwX5qI1GSlfAbqVq4V/W0CIHRCmucjmXpbVKzPsOfJ6RBPHWSUJJSjiGLf1QTtvliDAHUAKXm+8J45OSHwVnOfY6V35b5XfZxgCvj5TV0mXCVdx4QAAAF9TGbj/QAABAMARjBEAiAlPQGU1X34G+wtrYEpGFodWifIfxfeOwKx9o3qjVr4LAIgUQenz7z8a0zIC5XATCAwEG3uXnbATrl+ss5cu6YqvPowCgYIKoZIzj0EAwIDSAAwRQIhAN5vKyEhzWAj6Wc6bhr8l9YXIGn4e4dNVSYeHcRoK0AkAiAhhXJkG+SzWyp/bFJeCfXbnWw59mww9GOOkoNizKCG6w==
{
"PaymentId": "8016ab30f89882a72c6827e6",
"TransferAmount": 100,
"TippingAmount": 0,
"TotalAmount": 100,
"Currency": "EUR",
"Amount": 100,
"Description": "betaling Webshop Patisserie Stefan",
"Reference": "5902",
"CreatedAt": "2022-06-28T09: 50: 58.298Z",
"ExpireAt": "2022-06-28T10: 10: 58.298Z",
"Status": "SUCCEEDED",
"Debtor": {
"Name": "Nathan",
"Iban": "***51944"
}
}
eyJ0eXAiOiJKT1NFK0pTT04iLCJraWQiOiJlcy5zaWduYXR1cmUuZXh0LjIwMjIiLCJhbGciOiJFUzI1NiIsImh0dHBzOi8vcGF5Y29uaXEuY29tL2lhdCI6IjIwMjItMDYtMjhUMDk6NTE6MTQuNzE0MjU0WiIsImh0dHBzOi8vcGF5Y29uaXEuY29tL2p0aSI6ImU0OWIzNmNhM2EzM2I4ODIiLCJodHRwczovL3BheWNvbmlxLmNvbS9wYXRoIjoiaHR0cHM6Ly90ZXN0LnBhdGlzc2VyaWVzdGVmYW4ubmV0L2FwaS93ZWJzaG9wY29udHJvbGxlci9DYWxsYmFja1BheWNvbmlxIiwiaHR0cHM6Ly9wYXljb25pcS5jb20vaXNzIjoiUGF5Y29uaXEiLCJodHRwczovL3BheWNvbmlxLmNvbS9zdWIiOiI2MjVlN2ZmMDFlMjRiNzA0NDI5MWNkYzUiLCJjcml0IjpbImh0dHBzOi8vcGF5Y29uaXEuY29tL2lhdCIsImh0dHBzOi8vcGF5Y29uaXEuY29tL2p0aSIsImh0dHBzOi8vcGF5Y29uaXEuY29tL3BhdGgiLCJodHRwczovL3BheWNvbmlxLmNvbS9pc3MiLCJodHRwczovL3BheWNvbmlxLmNvbS9zdWIiXX0..SIG71tYh8l0rRn7n7Bg3e1goWIloBlSwdkkXhXjIHZlelhNgKM4GJcFbimk-sIpdNl8XEOtKHVx_Tf93P3V-GA
I have tried multiple things including:
var verified = false;
byte[] dataToBeVerifiedByteArray =
Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(body));
//this gets the string of the certificate mentioned above
byte[] cerBytes = Convert.FromBase64String(jwk.X5cValues.First().Waarde);
X509Certificate2 cer = new(cerBytes);
ECDsa ECDKey = cer.GetECDsaPublicKey();
ECParameters ECDsaPublicParam = ECDKey.ExportParameters(false);
using (var ecdsa = ECDsa.Create())
{
ecdsa.ImportParameters(ECDsaPublicParam);
verified = ecdsa.VerifyData(dataToBeVerifiedByteArray,
Encoding.UTF8.GetBytes(signature), HashAlgorithmName.SHA256);
};
return verified;
Can anyone see what I'm doing wrong I can't find any solution. There is a documentation where they just referr to the IETF regarding the verification of the signature -> https://datatracker.ietf.org/doc/html/rfc7515#section-5.2
Payconiq documentation -> https://developer.payconiq.com/online-payments-dock/#the-callback-signature
EDIT: I have not found a solution but just check the headers and fetch the status fysically instead of verifying the signature
Here is a working sample
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Microsoft.IdentityModel.Tokens;
public class Program
{
public static void Main()
{
var signature =
"eyJ0eXAiOiJKT1NFK0pTT04iLCJraWQiOiJlcy5zaWduYXR1cmUuZXh0LjIwMjMiLCJhbGciOiJFUzI1NiIsImh0dHBzOi8vcGF5Y29uaXEuY29tL2lhdCI6IjIwMjMtMDktMjlUMTM6MDk6NTEuNDUwMTAyWiIsImh0dHBzOi8vcGF5Y29uaXEuY29tL2p0aSI6ImVjMzliMWE3NzBlMzE0ZGMiLCJodHRwczovL3BheWNvbmlxLmNvbS9wYXRoIjoiaHR0cHM6Ly93ZWJob29rLnNpdGUvODQ4OTBlZmYtZTc4OC00ZGQyLThlNDYtNzRjOGY2NzRhODFiIiwiaHR0cHM6Ly9wYXljb25pcS5jb20vaXNzIjoiUGF5Y29uaXEiLCJodHRwczovL3BheWNvbmlxLmNvbS9zdWIiOiI2MjdhMjJhNWRmMDcyYTU2NWYyNjQxNmEiLCJjcml0IjpbImh0dHBzOi8vcGF5Y29uaXEuY29tL2lhdCIsImh0dHBzOi8vcGF5Y29uaXEuY29tL2p0aSIsImh0dHBzOi8vcGF5Y29uaXEuY29tL3BhdGgiLCJodHRwczovL3BheWNvbmlxLmNvbS9pc3MiLCJodHRwczovL3BheWNvbmlxLmNvbS9zdWIiXX0..FxLuOg-96-4jApaZEv7AfqIt5OZzIrHXvKaYg150dBcXDc871iCHtZGHQTusZNdvnXx6yD7PcAdQkCMZNAj3Bw";
var payloadRaw =
"""{"paymentId":"493342bd1ce7fbdc42e90a4d","transferAmount":1,"tippingAmount":0,"amount":1,"totalAmount":1,"description":"Test payment 12345","reference":"12345","createdAt":"2023-09-29T13:09:36.276Z","expireAt":"2023-09-29T13:11:36.276Z","status":"PENDING_MERCHANT_ACKNOWLEDGEMENT","debtor":{"name":"Kaveya","iban":"***********49713"},"currency":"EUR"}""";
//needs to be downloaded from payconiq. Hardcoded here for simplicity
var x5C =
"MIIE2jCCBIGgAwIBAgIQdSIb11T2uTW0huJ5dv1/TzAKBggqhkjOPQQDAjCBjzELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQDEy5TZWN0aWdvIEVDQyBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENBMB4XDTIyMTIwMTAwMDAwMFoXDTIzMTIwMTIzNTk1OVowKDEmMCQGA1UEAxMdZXMuc2lnbmF0dXJlLmV4dC5wYXljb25pcS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARIpLe02lsuMs6G1lQQRw3Zo4GlBwxi1h7EDD6GC9MxYRkkxOQMrJ1UKD3ni4dXcCZjHyv2GGvWhNICOaCso9Elo4IDIzCCAx8wHwYDVR0jBBgwFoAU9oUKOxGG4QR9DqoLLNLuzGR7e64wHQYDVR0OBBYEFHUsvJY0jGLPbsoGZeOmkk09+ADEMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjBJBgNVHSAEQjBAMDQGCysGAQQBsjEBAgIHMCUwIwYIKwYBBQUHAgEWF2h0dHBzOi8vc2VjdGlnby5jb20vQ1BTMAgGBmeBDAECATCBhAYIKwYBBQUHAQEEeDB2ME8GCCsGAQUFBzAChkNodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29FQ0NEb21haW5WYWxpZGF0aW9uU2VjdXJlU2VydmVyQ0EuY3J0MCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTBLBgNVHREERDBCgh1lcy5zaWduYXR1cmUuZXh0LnBheWNvbmlxLmNvbYIhd3d3LmVzLnNpZ25hdHVyZS5leHQucGF5Y29uaXEuY29tMIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdgCt9776fP8QyIudPZwePhhqtGcpXc+xDCTKhYY069yCigAAAYTNAAkNAAAEAwBHMEUCIE0AnY89A1e8tFlYCvH8TXVIl/tiLlIOwMImTRmYXsscAiEAuxJKE1m3VpB6PNPtmUV9b94pnBCvbLkliK/ZekJ0sh8AdwB6MoxU2LcttiDqOOBSHumEFnAyE4VNO9IrwTpXo1LrUgAAAYTNAAjRAAAEAwBIMEYCIQCkzfgYg4tWlDnIK2MeuMwOjFprd9wF4IYfmpC245e1hwIhAJT1QFyPwzjPUKcN7GQZnEpW2me1pFi5ZV2t9vfivj5mAHYA6D7Q2j71BjUy51covIlryQPTy9ERa+zraeF3fW0GvW4AAAGEzQAIpAAABAMARzBFAiEAmxBqRdzZXC1FjrZldKSKJlEijGJNypWsBGnU7ZC6lzECIDdZeg6Xv1JPzZuke5O4Ml50Z96XIP6CqGCnkRcdcEFlMAoGCCqGSM49BAMCA0cAMEQCIEW8t/Vi5bs+vnSiluCHqofw0k34q4XE6NnfNAI4mf5xAiAEZ+FNTw+lr2kujzG3VB4HrpiMrH1TcJtNa3pfZImsRg==";
var result = Verify(signature, payloadRaw, x5C);
Console.WriteLine(result);
}
private static bool Verify(string jws, string payload, string x5C)
{
string[] parts = jws.Split("..");
var encodedJoseHeader = parts[0];
JwtHeader jwtHeader = JwtHeader.Base64UrlDeserialize(encodedJoseHeader);
//var kid = jwtHeader.Kid; Use it to get x5c
var signature = Base64UrlEncoder.DecodeBytes(parts[1]);
using (var cert = new X509Certificate2(Convert.FromBase64String(x5C)))
{
var publicKey = new ECDsaSecurityKey(cert.GetECDsaPublicKey());
var data = Encoding.UTF8.GetBytes($"{encodedJoseHeader}.{Base64UrlEncoder.Encode(payload)}");
var isValid = publicKey.ECDsa.VerifyData(data, signature, HashAlgorithmName.SHA256);
return isValid;
}
}
}
JOSE Header here is
{
"typ":"JOSE+JSON",
"kid":"es.signature.ext.2023",
"alg":"ES256",
"https://payconiq.com/iat":"2023-09-29T13:09:51.450102Z",
"https://payconiq.com/jti":"ec39b1a770e314dc",
"https://payconiq.com/path":"https://webhook.site/84890eff-e788-4dd2-8e46-74c8f674a81b",
"https://payconiq.com/iss":"Payconiq",
"https://payconiq.com/sub":"627a22a5df072a565f26416a",
"crit":[
"https://payconiq.com/iat",
"https://payconiq.com/jti",
"https://payconiq.com/path",
"https://payconiq.com/iss",
"https://payconiq.com/sub"
]
}