Digital Signatures
In this section we cover the different ways digital signatures are used for Security in the Open Banking API. There are three main uses :
- Sending authorization requests
- This applies to all Initiating Parties sending requests to the Open Banking Service.
- Sending signed requests and responses (iDEAL 2.0 only)
- This only applies to certain Acquirers in the IDEAL 2.0 service. If not applicable, this section can be skipped.
- Receiving signed requests and responses (iDEAL 2.0 only)
- This refers to the Notification requests that the Initiating Party receives from the Open Banking Service.
- This only applies to certain Acquirers in the IDEAL 2.0 service. If not applicable, this section can be skipped.
Sending authorization requests
On the application level, the Initiating Party is authenticated and authorized by sending a digitally signed request to the POST /token endpoint (see Access Tokens - V1, or Access Tokens - V2). The signature validation allows the Open Banking Service to check the authenticity and integrity of the request.
This is achieved by applying the "Authorization" scheme as described in https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12 and further detailed below.
The 'Authorization' field consists of the following elements, see also the examples below
- 'Signature keyId’ = The thumbprint of the public certificate of the Initiating Party, viewed with the SHA1 algorithm.
- 'algorithm' =
algorithm=”rsa-sha256”
oralgorithm=”SHA256withRSA”
- 'headers' = the headers ”app client id date” which are part of the signature, the sequence which they are placed here should also be used in the 'signature'
- 'signature' = The 'signature' is created with the private key associated with 'keyId' applying the chosen signature 'algorithm'. The string to sign is created by concatenating the lowercased 'headers' field names followed with an ASCII colon `:`, an ASCII space ` `, and the header field value. Leading and trailing optional whitespace (OWS) in the header field value MUST be omitted. If the value is not the last value, then an ASCII newline `\n` is appended. The 'signature' is then created with the private key associated with 'keyId' applying the chosen signature 'algorithm'.
Example: For the request
POST /authorize/token HTTP/1.1
App: IDEAL
Date: Fri, 25 Mar 2022 20:51:35 GMT
Client: idealClient
Id: 434
The concatenated "String to sign" would be:
app: IDEAL
client: idealClient
id: 434
date: Fri, 25 Mar 2022 20:51:35 GMT
The complete 'Authorization' field looks like this:
Authorization: Signature keyId="DCAC7209573D506FC56095B8B23E8555A8F38B29", algorithm="SHA256withRSA", headers="app client id date", signature="guoLSHgl/zGRujqkDnmaWCL8kgCVnDazqkKu7nWU/uAHrS+M9eQsI8ueB4uWgxyPOnZps3vpNgkW1f4aBsdFYLS0jYeup4yhCMN6vis2zfMKxUhZFkjELslQkit9Gwc9pqvcyH0IxUnDLbCQwkiYjf6nGbP1YNfoxVXQpfq6i6CbIXCotLfwH2kbkrnSWwAS5skZY77+znmLDjtP3et2K94C36yPo0EEGqGkQ5xkD7owA7YxzA30xzsvkDvU3hzDzTK5wZmsgVsoyjRvMrokG0HrszUpNTwUtxflukcgs0pH7GuT+JrIpQ55f1dpzULqxeBggnCvD9DRSuKeTakqlw=="
The Initiating Party must upload the used public certificate in the back office portal so that Open Banking Service is able to validate the signature. If the signature can be validated and the sender has a valid subscription, a response containing an access token is returned.
This access token can be used in all follow-up API requests until it expires. After expiration a new access token must be requested via the POST /token endpoint.
Sending signed requests and responses (iDEAL 2.0 only)
Signing requests and responses could be enforced depending on the Acquirer configuration.
Signing would impact the following cases:
- POST /payments API requests
- GET /payments/{paymentId}/status API requests
- GET /preferences/{id}/ API requests
- Notification API requests received by the Initiating Party
- Responses received by the Initiating Party
In this section, we will be looking into signing POST /payments requests and the responses from the Open Banking Service.
If signatures are enforced by the Initiating Party's Acquirer, the following fields will be mandatory in the API requests sent by the Initiating Party to the Open Banking Service:
- Signature
- Digest
The Signature
The digital signing should be done by the Initiating Party by applying the “Signature” scheme as described in https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12.
This is equivalent to the “Authorization” scheme and the same procedure is followed to generate the signature and header parts but it uses the Signature field instead of the Authorization field.
In order to generate the signature string that is signed with a key, the Initiating Party must use the values of following HTTP header fields:
- Digest
- X-Request-ID
- MessageCreateDateTime
The headers used to generate the signature string also have to appear in the `headers` parameter of the Signature field, as shown below:
headers=”digest x-request-id messagecreatedatetime (request-target)”
To include the HTTP request target in the signature calculation, use the special `(request-target)` header field name. You can generate the header field value by concatenating the lowercased :method, an ASCII space, and the :path.
The (request-target) header field value to be used for the different APIs is:
API | (request-target) header field value |
---|---|
POST /payments | post /xs2a/routingservice/services/ob/pis/v3/payments |
GET /payments/{paymentId}/status | get /xs2a/routingservice/services/ob/pis/v3/payments/{paymentId}/status* |
GET /preferences/{Psuid} | get /xs2a/routingservice/services/ob/pis/v3/preferences/{psuId}* |
*The placeholders {paymentId} and {psuId} must be substituted with the values as obtained within the respective flows.
The signature string is created by concatenating the lowercased header field names followed with an ASCII colon `:`, an ASCII space ` `, and the header field value. Leading and trailing optional whitespace (OWS) in the header field value MUST be omitted. If the value is not the last value, then an ASCII newline `\n` is appended.
Example: For the payment request with the following required headers
POST /payments HTTP/1.1
digest: SHA-256=B/O1sG0L8+bEAqWF3aMZn3I0rx5YVi8r5cM6JHlTW7Q=
x-request-id: 1aad5e0f-02d7-aefb-61e3-6f4d3322cf71
messagecreatedatetime: 2023-03-15T10:07:26.264Z
The concatenated "Signature String" would be:
digest: SHA-256=B/O1sG0L8+bEAqWF3aMZn3I0rx5YVi8r5cM6JHlTW7Q=
x-request-id: 1aad5e0f-02d7-aefb-61e3-6f4d3322cf71
messagecreatedatetime: 2023-03-15T10:07:26.264Z
(request-target): post /xs2a/routingservice/services/ob/pis/v3/payments
The resulting signature parameter of the Signature field would be:
signature="N7kFLMMi/2R5hCd1gdO+GYhS70DOLMl+n8hborf42nFuu0HFjreoqU70gvxFWzgTPaWjdmNYY/7sOAUAQWudsM61Vc536XmaOGrrSxOINlH9l9QBk31xZMlJBf/+1+GtPb1BR26PYBjxKDMbN9W7PEVZLCDoObSnVLkvKbkLRWl0U8a39mDkUBu70Jw8yWusDU0g1OVN+5YRfENPNtC2ZnVD80gxih4JoFV6f4WCcX4HXVl229veFNO5joNQyUc7qOkXUGN2g0omgN4iJxVGnzEJ9BCrNe+vK9T25LC0fwSp/W6A9dDfuHQzMZgDJZZKpaX0Gg34i68etmi5oLrM3A=="
The algorithm parameter of the Signature field is:
algorithm=”rsa-sha256”
or algorithm=”SHA256withRSA”
The ‘keyId’ parameter of the Signature field is the thumbprint of the used certificate, viewed with the SHA1 algorithm. The private key associated with `keyId` is used to generate a digital signature on the concatenated signature string applying the chosen algorithm. The complete Signature header looks then like this:
Signature: keyId="DCAC7209573D506FC56095B8B23E8555A8F38B29", algorithm="SHA256withRSA", headers="digest x-request-id messagecreatedatetime (request-target)", signature="N7kFLMMi/2R5hCd1gdO+GYhS70DOLMl+n8hborf42nFuu0HFjreoqU70gvxFWzgTPaWjdmNYY/7sOAUAQWudsM61Vc536XmaOGrrSxOINlH9l9QBk31xZMlJBf/+1+GtPb1BR26PYBjxKDMbN9W7PEVZLCDoObSnVLkvKbkLRWl0U8a39mDkUBu70Jw8yWusDU0g1OVN+5YRfENPNtC2ZnVD80gxih4JoFV6f4WCcX4HXVl229veFNO5joNQyUc7qOkXUGN2g0omgN4iJxVGnzEJ9BCrNe+vK9T25LC0fwSp/W6A9dDfuHQzMZgDJZZKpaX0Gg34i68etmi5oLrM3A=="
In order for the Open Banking Service to validate the signature, the Initiating Party must upload the public certificate to the back office portal. Once the signature is confirmed and the iDEAL Subscription is authenticated, the Initiating Party will receive a successful response.
The Digest header
Calculate the Digest header as follows:
- Step1: Create a SHA-256 hash of the Payload
- Note-1: if the output is hex-encoded, please make sure to convert it to binary data (convert the hex-encoded string to a byte array)
- Note-2: payload formatting is important. If the Digest is generated by using an unformatted JSON payload, then please make sure that it matches with an unformatted request body used in the API request.
- Step 2: Base64-encode the SHA-256 hash
- Step 3: Prepend 'SHA-256=' to the resulting base64-encoded value
Example payload :
{"PaymentProduct":["IDEAL"],"CommonPaymentData":{"Amount":{"Type":"Fixed","Amount":"10.00","Currency":"EUR"},"RemittanceInformation":"Cookie","RemittanceInformationStructured":{"Reference":"iDEALpurchase21"}},"IDEALPayments":{"UseDebtorToken":false,"FlowType":"Standard"}}
Step 1: The SHA-256 hash of this request body is: 0d426d36fca1659980b9e371b25e2f17281bb285a634290d3da04233249b56ca. (Note: this is a hex-encoded representation)
Step 2: The base64-encoded hash (Note: hex to base64 encoding): DUJtNvyhZZmAueNxsl4vFygbsoWmNCkNPaBCMySbVso=
Step 3: The Digest header value: SHA-256=DUJtNvyhZZmAueNxsl4vFygbsoWmNCkNPaBCMySbVso=
Receiving signed requests and responses (iDEAL 2.0 only)
Receiving signed requests and responses could be enforced depending on the Acquirer configuration. In the event the Acquirer enforces Signatures, the following headers will be present in the API requests and responses received by the Initiating Party:
- Signature
- Digest
The digital signing performed by the Open Banking Service is done by applying the same “Signature” scheme as described above.
A notification request or response from Open Banking Service to the Initiating Party may contain the following headers (Signature and Digest included in the example):
Headers: {Authorization=Bearer iDEAL2.0testnotificationtoken, X-Request-ID=7e04be55-f710-4660-8254-a48d0246d56b, MessageCreateDateTime=2024-01-30T17:03:52.111+01:00, Digest=SHA-256=9CfdR8v5UlVl8YHNnpbO4v6uB/1B0EtWGLtnP7t2iVs=, Signature=keyId="3EBEF6033C00730D9C6DA05165A3CAA1F31036FB",algorithm="rsa-sha256",headers="messagecreatedatetime x-request-id digest",signature="v+IzPw8RKwGD3GWgLyuy/4RbA25PVwJxpvzs8QbqfAGLUSvOLhEL9dpQwvZi05DDbC80Z+1H7Kdyh3DumXRdayY7XYnunA05tcirszq1fOmESP5S6iw0It9XoV5u/L8EPTgMvOXYECuDT+zVKDsB0PXRIyfT1p+kS1iKc7kckPvDycVGRYMyfXHATmcrlHY6lSjMuw7WMlBOUo9Ac+dU8AQeqWzpzFjMa2Nd5XZkhd1vyKeVqh5cmWapJ2tZDk4/FwDZnpH3Po9PWKXwX/s+UolR/vlIUcRw+avIhU7L6Qme7JDQDpZlcAgJfj/OpF8ZDlb6yfW32yFFzYnkMyGdYQ==", Content-Type=application/json}
In order to ensure that the contents of the sent messages are correct and have not been altered during transmission or storage, the Initiating Party can validate the received signature. For the validation of the signature, the Initiating Party can use the public certificate of the Open Banking Service which can be downloaded from the back office portal.
The high-level steps that a Initiating Party needs to take in order to validate the Signatures are:
- Digest calculation (as Digest is a part of the Signature)
- Signature validation by re-creating the signature string
The above steps (1) and (2) are detailed below to describe the process behind signature validation.
These steps are by default followed if standard signature validation libraries are used by the Initiating Party.
Digest header calculation for Notification requests and API responses
To verify the digest header, calculate the value and ensure you receive the same value as what is in the header of the Request/Response from the Open Banking Service. This is demonstrated below using a Notification request body.
{"PaymentProductUsed":"IDEAL","CommonPaymentData":{"PaymentStatus":"Expired","PaymentId":"141110","AspspPaymentId":"0001115682120510","AspspId":"10002","DebtorInformation":{"Name":"Edsger Wybe Dijkstra - Callback","Agent":"ABNANL2AXXX","Account":{"SchemeName":"IBAN","Identification":"NL44RABO0123456789"}}}}
Step 1 : The Open Banking Service always sends unformatted JSON payloads in the Requests/Reponses towards the Initiating Party. However, in case the Initiating Party sees a pretty-printed JSON object (e.g. by viewing logs via a tool), the payload should be converted to an unformatted JSON. All blank spaces should be removed unless they are part of the field value (Example: the debtor name has spaces which can remain).
Step 2 : Create a SHA 256 hash of this payload: b1219370189b7c7d67f64fd6f72168187343d639f3aeada8ea3a2b36e0fac297
Note that this string is hex encoded.
Step 3 : Convert this hex encoding to a base64 encoding : sSGTcBibfH1n9k/W9yFoGHND1jnzrq2o6jorNuD6wpc=
Step 4 : Finally prepend "SHA-256=" to the value. The final value is : SHA-256=sSGTcBibfH1n9k/W9yFoGHND1jnzrq2o6jorNuD6wpc=
This value should match the Digest as sent by the Open Banking Service.
Signature validation in Notification requests and API responses
The digital signing is done by the Open Banking Service by applying the “Signature” scheme as described in https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12.
In order to verify the signature header, the Initiating Party should recreate the signature with the steps outlined below:
Re-create signature string
In order to generate the signature string that is signed with a key, the Initiating Party must use the values of HTTP header fields:
- MessageCreateDateTime
- X-Request-ID
- Digest
The headers used to generate the signature string also have to appear in the `headers` parameter of the Signature header, in the fixed order appearing below:
headers=”messagecreatedatetime x-request-id digest”
The signature string is created by concatenating the lowercased HTTP header field names followed with an ASCII colon `:`, an ASCII space ` `, and the HTTP header field value. Leading and trailing optional whitespace (OWS) in the HTTP header field value MUST be omitted. If the value is not the last value, then an ASCII newline `\n` is appended.
Example: For the notification request with the following required headers
POST /notification/status HTTP/1.1
Digest: SHA-256=9CfdR8v5UlVl8YHNnpbO4v6uB/1B0EtWGLtnP7t2iVs=
X-Request-ID: 7e04be55-f710-4660-8254-a48d0246d56b
MessageCreateDateTime: 2024-01-30T17:03:52.111+01:00
The concatenated "Signature String" would be:
messagecreatedatetime: 2024-01-30T17:03:52.111+01:00
x-request-id: 7e04be55-f710-4660-8254-a48d0246d56b
digest: SHA-256=9CfdR8v5UlVl8YHNnpbO4v6uB/1B0EtWGLtnP7t2iVs=
The string to verify is now defined and it can be validated using the public key of the Open Banking Service which can be downloaded from the Open Banking Service GUI.
Validate signature
The signature parameter of the Signature header sent by the Open Banking Service was:
Base64 encoded signature
signature="v+IzPw8RKwGD3GWgLyuy/4RbA25PVwJxpvzs8QbqfAGLUSvOLhEL9dpQwvZi05DDbC80Z+1H7Kdyh3DumXRdayY7XYnunA05tcirszq1fOmESP5S6iw0It9XoV5u/L8EPTgMvOXYECuDT+zVKDsB0PXRIyfT1p+kS1iKc7kckPvDycVGRYMyfXHATmcrlHY6lSjMuw7WMlBOUo9Ac+dU8AQeqWzpzFjMa2Nd5XZkhd1vyKeVqh5cmWapJ2tZDk4/FwDZnpH3Po9PWKXwX/s+UolR/vlIUcRw+avIhU7L6Qme7JDQDpZlcAgJfj/OpF8ZDlb6yfW32yFFzYnkMyGdYQ=="
The signature algorithm used is also contained in the Signature header: algorithm=”SHA256withRSA”
In order to verify a signature, a server MUST:
- Re-create the signature string (see step above)
- Hash the signature string with SHA256
- Verify the signature with the public key of the Open Banking Service.