So überprüfen Sie die Signatur eines PSS (probabilistisches Signaturschema) Signature in C# (Tillo Webhooks)
Posted: 28 Feb 2025, 08:48
Ich arbeite an einer Integration mit TIMLO und verwende signierte Webhooks, wie hier beschrieben. Ich brauche jedoch eine Implementierung in C# und ich habe daran gearbeitet, das zu produzieren. Ich habe es geschafft, bis zum Code unten zu gelangen, aber ich kann die Signaturüberprüfung nicht zum Laufen bringen, unabhängig von meinen Versuchen, sie zu lösen. Kann mir jemand etwas helfen? Vielen Dank.
Code: Select all
private const string TILLO_CA_CERT_URL = "https://ca.tillo.io/";
void Main()
{
var headers = new Dictionary() {
{ "webhook-id", "6b8aa749707b77c4be" },
{ "webhook-signature", "v1a,fiU3Rd66lIFFqUP4F0/knu33LmhlJrlr+8LHkcVo5wKiLZ23c60Dodi+kAugvObr4sRX+rqOmYJ7+uENl/irhqb+OCBGnvC0ydP/Bo7wu7wFHF8JZFI1diW2fN9IC1jybKAvN+BNQoMMlL/hUceMV3HHWjLMVL742NiEXWHG9eAV7GHfdGFP0US8rJLfPbafwbqDtO+jnIAH/W0ve6jz821Ky2hFHPt1+W+qjYlBN8UC54cYTLpKTxF3ZkaC4WwWWpSXHClgjyxkXSXJ8NJzOx02fO/0RiJZYWSbp8yrURvBvrdTumSl9bQ/a11O6WBaqGASkZBmsyncng9+oa79/Kj2tEsQ/b2SeZEXEdfT28Vhnvlk4uWyWswC965jMfyCp66VJak8KakBBNtZ1gSiHPzUakQagz3bK0/5WBVdUukwx9I/cUzK3kFbTrqpvwW+PvB5MdYb1ove7FG47W3ZEQ2lvF+62NhiNPVyYzyw7y4DyRtHm6KBlZwpE2M6FE/cVmQNilCOYD2ttd99WrXskMYTH8wV3HEu9Xz34TmThQjCtHsRcMX2AmXMUJGEGCbYe//eU7xVvfr8WwUM7gIBnD92t5s8bVkN/y/uT4WIl/OsBvcipBzVFxZxxAb7oiGjPl6SaQ/f0aaa9j7+FIbh8/PIK/VNL7qUxQiM+gfdE80="},
{ "webhook-timestamp", "1740137197"}
};
var payload = "{\"type\":\"brands.status.updated\",\"timestamp\":\"2025-02-21T11:26:34Z\",\"certificate\":\"-----BEGIN CERTIFICATE-----\\r\\nMIIGBzCCA++gAwIBAgIUCMkS5Ti+joHJ4U2y2x4IzqC+0nMwDQYJKoZIhvcNAQEL\\r\\nBQAwgZ0xFDASBgNVBAMMC1RpbGxvQ0FSb290MQswCQYDVQQGEwJHQjEPMA0GA1UE\\r\\nCAwGU3Vzc2V4MRgwFgYDVQQHDA9CcmlnaHRvbiAmIEhvdmUxDjAMBgNVBAoMBVRp\\r\\nbGxvMRYwFAYDVQQLDA1QbGF0Zm9ybSBUZWFtMSUwIwYJKoZIhvcNAQkBFhZwbGF0\\r\\nZm9ybS50ZWFtQHRpbGxvLmlvMB4XDTI0MTIxMDE1MDk0NloXDTI5MTIwOTE1MDk0\\r\\nNlowZzELMAkGA1UEBhMCR0IxDzANBgNVBAgMBlN1c3NleDEOMAwGA1UECgwFVGls\\r\\nbG8xFjAUBgNVBAsMDVBsYXRmb3JtIFRlYW0xHzAdBgNVBAMMFnRpbGxvLXdlYmhv\\r\\nb2tzLXN0YWdpbmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJf16N\\r\\nTnvyIgEg88tl7B+2ZoOsDnYTMTBf12zSRdNre5KhDYWgXQ5Bgd3dx412VSU1eoWN\\r\\nHV7494I\\/Vm1HQBNBj63uCAcivykOZttYayj4tPIv5qL4LzO2rZVzeRkJMe1bK6E9\\r\\nwoABGhLQDO9QUnxzQykPIBTjt0lE0PY1uGiJxzrn99jcDMKgM1p9AfQrJQIV3BzQ\\r\\nz27dS4vQzcIvVgqbVRt1ZPU8jlbo6HuEexOM47NVToll83T8hLJG1FzDkbJ4HyCm\\r\\n3UFEEp20fMnxMtRgipp5OttjOjfVKUHaUQEawf9B8LZM9WRxIIcQHV1LvQ0BYIRO\\r\\n5PoWd6dV9khakAPMlynHu68FnJPF5xbMaURW9mcp++s8r4OvVBP2d07jh9pkze45\\r\\nHo0TTza3F1vu2eMq72rETQhEwjeR0gAhT\\/87dJDTKgiI+jPfsh6NxH4PVsvqXl8h\\r\\nKa5cRpOOvFn0oI4OKMovuQ1FA5Q+Q9Ttj1QAnfUqj2mYm2PmIcH3ljUExjtsgfXG\\r\\nxf67neF\\/lum5ZYD0njeWqynkbEBr61VKXnCdNGfSfsgytbTeGPbrlAXlJ8Fa4JSp\\r\\nNGQOi99DDnyR1QRiVjESoqME+ps1GkZB7Za5f28fbcOb3clL9YHEo8PDmUwfsm5J\\r\\nt1CfrpUeQrI6xmX6HWkprp5yBXY63ZIEQcIUtQIDAQABo3QwcjAwBgNVHR8EKTAn\\r\\nMCWgI6Ahhh9odHRwczovL2NhLnRpbGxvLmlvL2NybC9jcmwucGVtMB0GA1UdDgQW\\r\\nBBQyNSvOXcnhPIdncEPJSdXJtZdcsjAfBgNVHSMEGDAWgBSkN2+VN1dBCA8ay0zz\\r\\nxK1dw8K+RjANBgkqhkiG9w0BAQsFAAOCAgEAEmn8EknSOTrH90b3ec1yKJ5EeHKY\\r\\nqY5OeoFS4NHWpVcJ4wpo80r+zzxFJjD4kdoUDi4fcsOh4XC8OnFk6QW0fVcJbrp3\\r\\nuSyInD19aom7FNz+qWpPdcIg2ZNsCJ64TR6NR8EjZtqjLMR4+J\\/H7Aqb+VL2mFTC\\r\\nvQlMQsmmTa6fC4IYAd8woyfPF+Y56z81hZPNNaWQhvby52bzO27kLKCJmN2eZN\\/Q\\r\\nmmNIwQZnEb8UdCoEuzS8iElPGGniB4x\\/uXwhkE9kIJhmjHhIFVm1hWTLWYO3hcmP\\r\\nWBnVjplhx3Un9i1gTXeCrmpetCEGtRoHuIFQ8BruS+XFja3eReqHuz4Dhwtd3L6y\\r\\nm+Ni2PkiVe4wTGKCD37h9FTTbKiNt1ImR07+Zpy5edG2i\\/LD3LzH00rs\\/e41IfcB\\r\\nlmxd5QuI7Cjp7kdHzGB6oMtYBawMOFIRYgfGeCNE\\/7EgSFsW6wExfEZz1hNO7Cm1\\r\\nvQe8pe9s\\/b3tGovvLZQf0F6CRBd6VtpyxztmWQRIsUNgXl3LMCnawP0JmnR6wQDi\\r\\nq9H0SIzeK52YoEFxQNnItqvNLLhjOFI1TEd16AaILPyvQaPOKOj0U4KqnBd+BR2s\\r\\njdabaA9YPN09PiTIFbONBkwq1ZyGcF9gHCiRkW6jISuc17tf5w49sdeERzDttmwi\\r\\n5qiGBh41yjM59Ks=\\r\\n-----END CERTIFICATE-----\",\"version\":1,\"data\":[{\"name\":\"Costa\",\"slug\":\"costa\",\"status\":{\"code\":\"DISABLED\",\"reason\":\"TEST\"}},{\"name\":\"Farmfoods\",\"slug\":\"farmfoods\",\"status\":{\"code\":\"DISABLED\",\"reason\":\"TEST\"}},{\"name\":\"HelloFresh\",\"slug\":\"hello-fresh\",\"status\":{\"code\":\"DISABLED\",\"reason\":\"TEST\"}},{\"name\":\"Nike\",\"slug\":\"nike-usa\",\"status\":{\"code\":\"DISABLED\",\"reason\":\"TEST\"}}]}";
var jo = JObject.Parse(payload);
var certificate = jo["certificate"].ToString();
var signingCert = new Org.BouncyCastle.X509.X509CertificateParser().ReadCertificate(Encoding.UTF8.GetBytes(certificate));
using (var httpClient = new HttpClient())
{
var caCertPem = httpClient.GetStringAsync(TILLO_CA_CERT_URL).Result;
var caCert = new Org.BouncyCastle.X509.X509CertificateParser().ReadCertificate(Encoding.UTF8.GetBytes(caCertPem));
signingCert.Verify(caCert.GetPublicKey());
signingCert.CheckValidity();
AsymmetricKeyParameter keyParams = signingCert.GetPublicKey();
var rsaKeyParams = (Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters)keyParams;
var rsa=DotNetUtilities.ToRSA(rsaKeyParams);
string concatenatedData = $"{headers["webhook-id"]}.{headers["webhook-timestamp"]}.{payload}";
byte[] dataBytes = Encoding.UTF8.GetBytes(concatenatedData);
byte[] signature = Convert.FromBase64String(headers["webhook-signature"].Replace("v1a,",""));
bool isValid;
using (var rsaCng = new RSACng())
{
var rsaParameters = rsa.ExportParameters(false);
rsaCng.ImportParameters(rsaParameters);
isValid = rsaCng.VerifyData(dataBytes, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
}
if (!isValid) throw new Exception("isValid should be true");
}
}
< /code>
BefolgeneyJ0eXBlIjoiYnJhbmRzLmRlbm9taW5hdGlvbnMudXBkYXRlZCIsInRpbWVzdGFtcCI6IjIwMjUtMDItMjdUMjM6Mjg6MzBaIiwiY2VydGlmaWNhdGUiOiItLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS1cclxuTUlJR0J6Q0NBKytnQXdJQkFnSVVDTWtTNVRpK2pvSEo0VTJ5Mng0SXpxQyswbk13RFFZSktvWklodmNOQVFFTFxyXG5CUUF3Z1oweEZEQVNCZ05WQkFNTUMxUnBiR3h2UTBGU2IyOTBNUXN3Q1FZRFZRUUdFd0pIUWpFUE1BMEdBMVVFXHJcbkNBd0dVM1Z6YzJWNE1SZ3dGZ1lEVlFRSERBOUNjbWxuYUhSdmJpQW1JRWh2ZG1VeERqQU1CZ05WQkFvTUJWUnBcclxuYkd4dk1SWXdGQVlEVlFRTERBMVFiR0YwWm05eWJTQlVaV0Z0TVNVd0l3WUpLb1pJaHZjTkFRa0JGaFp3YkdGMFxyXG5abTl5YlM1MFpXRnRRSFJwYkd4dkxtbHZNQjRYRFRJME1USXhNREUxTURrME5sb1hEVEk1TVRJd09URTFNRGswXHJcbk5sb3daekVMTUFrR0ExVUVCaE1DUjBJeER6QU5CZ05WQkFnTUJsTjFjM05sZURFT01Bd0dBMVVFQ2d3RlZHbHNcclxuYkc4eEZqQVVCZ05WQkFzTURWQnNZWFJtYjNKdElGUmxZVzB4SHpBZEJnTlZCQU1NRm5ScGJHeHZMWGRsWW1odlxyXG5iMnR6TFhOMFlXZHBibWN3Z2dJaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQ0R3QXdnZ0lLQW9JQ0FRREpmMTZOXHJcblRudnlJZ0VnODh0bDdCKzJab09zRG5ZVE1UQmYxMnpTUmROcmU1S2hEWVdnWFE1QmdkM2R4NDEyVlNVMWVvV05cclxuSFY3NDk0SVwvVm0xSFFCTkJqNjN1Q0FjaXZ5a09adHRZYXlqNHRQSXY1cUw0THpPMnJaVnplUmtKTWUxYks2RTlcclxud29BQkdoTFFETzlRVW54elF5a1BJQlRqdDBsRTBQWTF1R2lKeHpybjk5amNETUtnTTFwOUFmUXJKUUlWM0J6UVxyXG56MjdkUzR2UXpjSXZWZ3FiVlJ0MVpQVThqbGJvNkh1RWV4T000N05WVG9sbDgzVDhoTEpHMUZ6RGtiSjRIeUNtXHJcbjNVRkVFcDIwZk1ueE10UmdpcHA1T3R0ak9qZlZLVUhhVVFFYXdmOUI4TFpNOVdSeElJY1FIVjFMdlEwQllJUk9cclxuNVBvV2Q2ZFY5a2hha0FQTWx5bkh1NjhGbkpQRjV4Yk1hVVJXOW1jcCsrczhyNE92VkJQMmQwN2poOXBremU0NVxyXG5IbzBUVHphM0YxdnUyZU1xNzJyRVRRaEV3amVSMGdBaFRcLzg3ZEpEVEtnaUkralBmc2g2TnhINFBWc3ZxWGw4aFxyXG5LYTVjUnBPT3ZGbjBvSTRPS01vdnVRMUZBNVErUTlUdGoxUUFuZlVxajJtWW0yUG1JY0gzbGpVRXhqdHNnZlhHXHJcbnhmNjduZUZcL2x1bTVaWUQwbmplV3F5bmtiRUJyNjFWS1huQ2ROR2ZTZnNneXRiVGVHUGJybEFYbEo4RmE0SlNwXHJcbk5HUU9pOTlERG55UjFRUmlWakVTb3FNRStwczFHa1pCN1phNWYyOGZiY09iM2NsTDlZSEVvOFBEbVV3ZnNtNUpcclxudDFDZnJwVWVRckk2eG1YNkhXa3BycDV5QlhZNjNaSUVRY0lVdFFJREFRQUJvM1F3Y2pBd0JnTlZIUjhFS1RBblxyXG5NQ1dnSTZBaGhoOW9kSFJ3Y3pvdkwyTmhMblJwYkd4dkxtbHZMMk55YkM5amNtd3VjR1Z0TUIwR0ExVWREZ1FXXHJcbkJCUXlOU3ZPWGNuaFBJZG5jRVBKU2RYSnRaZGNzakFmQmdOVkhTTUVHREFXZ0JTa04yK1ZOMWRCQ0E4YXkwenpcclxueEsxZHc4SytSakFOQmdrcWhraUc5dzBCQVFzRkFBT0NBZ0VBRW1uOEVrblNPVHJIOTBiM2VjMXlLSjVFZUhLWVxyXG5xWTVPZW9GUzROSFdwVmNKNHdwbzgwcit6enhGSmpENGtkb1VEaTRmY3NPaDRYQzhPbkZrNlFXMGZWY0picnAzXHJcbnVTeUluRDE5YW9tN0ZOeitxV3BQZGNJZzJaTnNDSjY0VFI2TlI4RWpadHFqTE1SNCtKXC9IN0FxYitWTDJtRlRDXHJcbnZRbE1Rc21tVGE2ZkM0SVlBZDh3b3lmUEYrWTU2ejgxaFpQTk5hV1FodmJ5NTJiek8yN2tMS0NKbU4yZVpOXC9RXHJcbm1tTkl3UVpuRWI4VWRDb0V1elM4aUVsUEdHbmlCNHhcL3VYd2hrRTlrSUpobWpIaElGVm0xaFdUTFdZTzNoY21QXHJcbldCblZqcGxoeDNVbjlpMWdUWGVDcm1wZXRDRUd0Um9IdUlGUThCcnVTK1hGamEzZVJlcUh1ejREaHd0ZDNMNnlcclxubStOaTJQa2lWZTR3VEdLQ0QzN2g5RlRUYktpTnQxSW1SMDcrWnB5NWVkRzJpXC9MRDNMekgwMHJzXC9lNDFJZmNCXHJcbmxteGQ1UXVJN0NqcDdrZEh6R0I2b010WUJhd01PRklSWWdmR2VDTkVcLzdFZ1NGc1c2d0V4ZkVaejFoTk83Q20xXHJcbnZRZThwZTlzXC9iM3RHb3Z2TFpRZjBGNkNSQmQ2VnRweXh6dG1XUVJJc1VOZ1hsM0xNQ25hd1AwSm1uUjZ3UURpXHJcbnE5SDBTSXplSzUyWW9FRnhRTm5JdHF2TkxMaGpPRkkxVEVkMTZBYUlMUHl2UWFQT0tPajBVNEtxbkJkK0JSMnNcclxuamRhYmFBOVlQTjA5UGlUSUZiT05Ca3dxMVp5R2NGOWdIQ2lSa1c2aklTdWMxN3RmNXc0OXNkZUVSekR0dG13aVxyXG41cWlHQmg0MXlqTTU5S3M9XHJcbi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0iLCJ2ZXJzaW9uIjoxLCJkYXRhIjp7Im5hbWUiOiJIZWxsb0ZyZXNoIiwic2x1ZyI6ImhlbGxvLWZyZXNoIiwiZGVub21pbmF0aW9ucyI6W119fQ==
< /code>
Die mit der Byte -Nutzlast bezogenen Header folgen: < /p>
"webhook-id" : "test_b6b0481330c34693ab139213b3a84c14"
"webhook-signature" : "v1a,QxwQDrh5Y9pEKN8DzdWj7/BzJ7wsbAXzeYHWSrNC6Kk/v3nAqfQpvrK9wF0gW9c9kY2i/8Ya6BBrGhk+w9yheUWaCKAFgMkF2yYuX7fKVRtHdGlK8kQ78XCx+LVWXQWC06ddvbp+Tqj41Qy1VI+OVaAJ9VW09KU61FXBa9oA9B4WtM7K0VIE2wgYDHLC1e7j9ZkLHL6QHbHto/eUMygE44Zrcs5yCvg06ww4Sn4pgQczIXNT90sSwJZnPyPMindPVHW5iUFciUXzzABYlkkM+t1JUjMzk6jnuU0YfnvPOH3i9N42pqiV50yYkRzM8VE3Fqn29IceUrSoVMuXJP4dqWPs+J0HnBpFtanil4FhOJbMCwao0v33DKBitmPe0/Oc+FX1elF8fwsFGCD6Ez47Y7kg0eXpHxaX+1ZRdFoSx3+qDhhmru700yxn1IHKNISwhx//87WZZWc0HGogXQ8EWhLtpGiVMNH7a/7aYV0wXV7XIfmoy8Y23Yl5UiXdOl4IHJ/nGEckA4QfH7OysevCiPo5yWLeCG8LJ2HgQBDu38plNAcYw6nN0LWnXi7K1UjnjP0ok4MobjP1FngVz6GVUrGoIztG8uA2WdWUq8eMuQ5zDIa5NHrLex3jkTyE9kpn7pdonC6Q3SRHjBsKnxhWqL8nXmXNPZbHyNrB/kmF62s="
"webhook-timestamp" : "1740698913"