0

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:

  1. Certificates -> https://ext.payconiq.com/certificates actual certificate ->

MIIE1zCCBH2gAwIBAgIQHzgeQOjemgrfp6IwTS5XfzAKBggqhkjOPQQDAjCBjzELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQDEy5TZWN0aWdvIEVDQyBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENBMB4XDTIxMTEyMzAwMDAwMFoXDTIyMTIyNDIzNTk1OVowKDEmMCQGA1UEAxMdZXMuc2lnbmF0dXJlLmV4dC5wYXljb25pcS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARIpLe02lsuMs6G1lQQRw3Zo4GlBwxi1h7EDD6GC9MxYRkkxOQMrJ1UKD3ni4dXcCZjHyv2GGvWhNICOaCso9Elo4IDHzCCAxswHwYDVR0jBBgwFoAU9oUKOxGG4QR9DqoLLNLuzGR7e64wHQYDVR0OBBYEFHUsvJY0jGLPbsoGZeOmkk09+ADEMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjBJBgNVHSAEQjBAMDQGCysGAQQBsjEBAgIHMCUwIwYIKwYBBQUHAgEWF2h0dHBzOi8vc2VjdGlnby5jb20vQ1BTMAgGBmeBDAECATCBhAYIKwYBBQUHAQEEeDB2ME8GCCsGAQUFBzAChkNodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29FQ0NEb21haW5WYWxpZGF0aW9uU2VjdXJlU2VydmVyQ0EuY3J0MCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTBLBgNVHREERDBCgh1lcy5zaWduYXR1cmUuZXh0LnBheWNvbmlxLmNvbYIhd3d3LmVzLnNpZ25hdHVyZS5leHQucGF5Y29uaXEuY29tMIIBewYKKwYBBAHWeQIEAgSCAWsEggFnAWUAdQBGpVXrdfqRIDC1oolp9PN9ESxBdL79SbiFq/L8cP5tRwAAAX1MZuRtAAAEAwBGMEQCIErmMHlQjPe/aNTo08NiFGS2hlKeBU5Ubrl9OG7myLWcAiB4bWXL8HOl2oNVci3Cv0RMnNTyMHIrAm8Lw9QQq/UxTQB1AEHIyrHfIkZKEMahOglCh15OMYsbA+vrS8do8JBilgb2AAABfUxm5DUAAAQDAEYwRAIgNEbgqCHIAjLqhRGBmiHRAqNwX5qI1GSlfAbqVq4V/W0CIHRCmucjmXpbVKzPsOfJ6RBPHWSUJJSjiGLf1QTtvliDAHUAKXm+8J45OSHwVnOfY6V35b5XfZxgCvj5TV0mXCVdx4QAAAF9TGbj/QAABAMARjBEAiAlPQGU1X34G+wtrYEpGFodWifIfxfeOwKx9o3qjVr4LAIgUQenz7z8a0zIC5XATCAwEG3uXnbATrl+ss5cu6YqvPowCgYIKoZIzj0EAwIDSAAwRQIhAN5vKyEhzWAj6Wc6bhr8l9YXIGn4e4dNVSYeHcRoK0AkAiAhhXJkG+SzWyp/bFJeCfXbnWw59mww9GOOkoNizKCG6w==

  1. body -> body of the callback post call
{
    "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"
    }
}
  1. signature which is a header of the callback call

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

Nathan T.
  • 277
  • 1
  • 8

1 Answers1

0

Your spec/scenario says that the signature is encoded with base64url, but instead of decoding it with base64 you re-encode the text to UTF-8.

You also unnecessarily clone the public key (and don't dispose the clone you originally got).

...

using (ECDsa ecdsa = cer.GetECDsaPublicKey())
{
    return ecdsa.VerifyData(
        dataToBeVerifiedByteArray,
        Base64UrlDecode(signature),
        HashAlgorithmName.SHA256);
}

Your example "signature" appears to be two concatenated pieces of data still, a JSON object that looks like it could be a JOSE header, and 64 bytes that could plausibly be a IEEE P1363A-formatted signature from ECDSA on the secp256r1 curve.

bartonjs
  • 30,352
  • 2
  • 71
  • 111
  • Indeed like mentioned in start of my question, the signature is built like this ```jws = base64URLEncode(JOSE Header)..base64URLEncode(alg(base64URLEncode(JOSE Header).base64URLEncode(Request Body)))```. But I'll try to get it working with the first changes – Nathan T. Jun 28 '22 at 16:30
  • I changed my code like you suggested and also changed the signature so that I only take the last part of my signature because it gave an error one the split character in the signature. `using (ECDsa ecdsa = cer.GetECDsaPublicKey()) { return ecdsa.VerifyData( dataToBeVerifiedByteArray, WebEncoders.Base64UrlDecode(signature.Split("..")[1]), HashAlgorithmName.SHA256); }` But unfortunately it still returns that my signature ain't valid. – Nathan T. Jun 28 '22 at 16:43
  • @NathanT. Your toBeVerified is probably the Base64Url-decoded value, but you’re using the UTF-8 of the encoded value. – bartonjs Jun 28 '22 at 16:46
  • So instead of using `Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(body));` i need to decode that as base64url? – Nathan T. Jun 28 '22 at 16:51
  • but the data of my ToBeVerified is just a serialized Json object. Even if i wanted i won't be able to Base64Url-decode it – Nathan T. Jun 28 '22 at 16:53