Image by Steve Buissinne from Pixabay

JWT claims for PSD2 HTTP message signature

Ernesto Arroyo Ron
eurobits-techblog
Published in
6 min readAug 6, 2019

--

Ernesto Arroyo, David Calleja, Bimal Melwani.

Abstract

To comply with PSD2 Technical Requirements, both Berlin Group and STET have proposed non-standard mechanisms when there are already solid and more useful standard alternatives available.

Here at Eurobits we propose the use of a set of claims signed with JWS (JSON Web Signature) as a more reliable standard to sign PSD2 messages. It allows for a more developer-friendly and useful mechanism that is also PSD2 compliant.

In the rest of this article, we will mention this signature as JWT or JWS.

Fallback mechanism exemption using JWT

This is a mechanism that is easy to implement and to use for both Dedicated Interface access (API’s) as well as for Direct Access (fallback mechanism). It allows for the continuation of HTTPs APIs — incorrectly known as web scrapping(*)- in a safe, secure and PSD2 compliant way using JSON Web Signatures to authenticate the TPP.

When accessing via Berlin Group framework we cannot use JWT at this moment, as this specification relies on the expired and non-standard [cavage-10] draft, but we hope our proposal will be well received by interested parties and can be widely adopted for any PSD2 message (Berlin Group, STET or any other).

(*) Modern web banking interfaces use REST APIs providing structured data to Single Page Applications, for which the parsing of HTML (Web Scrapping) served by old HTTP servers is a deprecated technology.

The Berlin Group mechanism is not the best way!

Berlin Group proposes the use of draft-cavage-http-signatures-10 that has several conceptual and implementation problems. It is not a standard, so we can only rely on the draft text for the actual version:

WARNING: DO NOT IMPLEMENT THIS SPECIFICATION AND PUSH THE CODE INTO PRODUCTION. THIS VERSION OF THE SPECIFICATION IS ONLY FOR EXPERIMENTAL IMPLEMENTATIONS. (from the cavage-11-draft)

The actual draft expires on October 26, 2019. However, Berlin Group specifies using Cavage-10 which is already expired! And cavage11 has non-compatible changes over cavage10…

Cavage adds unneeded headers and the signature calculation and the signature calculation and verification is an odd mechanism when followed by the “include a newline for calculating the signature” requirement.

JWS as the RIGHT alternative

JSON Web Tokens are an open, industry-standard RFC 7519 method for representing claims securely between two parties. For PSD2 implementation we only need to be sure we add the required headers as claims.

It facilitates an easy to implement and standardized approach to using JWT for signing PSD2 requests.

The PSD2 JWT will use RSA-256 or RSA-512 as the signature mechanism, using the PSD2 QSeal issued-certificate, and can add the next claims:

Registered claims

  • iss (Issuer) The CN for the QSeal Certificate issued for the requesting TPP
  • iat (Issued At) Date/time when the token was issued, that is, the HTTP request.
  • exp (expiration) the same Date/time as iat. The JWS is not an access token but a set of signed claims to identify the TPP: the request was made at the same time it was created.
  • sub (subject) for the PSU-ID in the ASPSP web.
  • aud (Audience) for the request target.

Private claims

  • digest We will also add, when needed, (POST/PUT/PATCH) a digest claim with the SHA-256 base64 value for the payload in this HTTP verbs.

Using the hash to ensure we do not expose any critical data.

  • uuid a random UUID that is both a nonce cryptographic mechanism and an index to search logs if required.

The advantage for using JWT as the signature mechanism is that the signature calculation is already a standard (as JWT is, RFC 7519) and there are a lot of implementation libraries for JWT in every language.

There are debugging web and tools to ensure the proper implementation and usage as JWT is an open standard. Open standard means anybody can use it. We can use any public/private key pair by using RSA or ECDSA(Elliptic Curve Digital Signature Algorithm) encryption. It is highly compact and provides a fast data transmission rate.

Comparing cavage11 and JWT PSD2 claims

Let’s think about sending the next HTTP request:

POST https://ing.ingdirect.es/genoma_login/rest/session
Accept”: “*/*”,
Content-Type”: “application/json; charset=utf-8”
{”birthday”:”12/12/1984",”device”:”desktop”,”loginDocument”:{“document”:”12345678W”,”documentType”:0}

This request is about the ING Genoma REST API; so, to be PSD2 compliance we should add a signature, let’s compare cavage-10 (expired, but the one mandated by Berlin Group) with a JWT.

To build a BG compliant signature we should:

  1. Calculate the keyId that must be formatted as SN={serial},CA={issuer}
  2. Calculate a digest (SHA-256) for the message payload
echo -n “{”birthday”:”12/12/1984",”device”:”desktop”,”loginDocument”:{“document”:”12345678W”,”documentType”:0}” | openssl dgst -binary -sha256 | openssl base64
FBaMNP31AnqAwLuzF6O78cEWw8n0xwZDCnpUu0PkXHc=

2. Calculate the date

date -u “+%a, %d %b %Y %H:%M:%S GMT”
Tue, 06 Aug 2019 12:22:30 GMT

3. Create a string to be signed built using a set of request headers: date, digest, x-request-id, psu-id, psu-corporate-id and tpp-redirect-uri,… in this example, we will just use the mandatory ones: digest and x-request-id

Here the order of these fields in the signing string matters and they need to be on separate lines, which is a problem — if the meaning of the message is not related to the order of the headers, why is the order critical for the signature?

You should generate a signature value that must be base-64 encoded

printf “$signingString” | openssl dgst -sha256 -sign eurobits_qsealc.pem | openssl base64 -Aqz0Ae1Wh39yaX8tgHv2up8JRSAjPqI6EGJtd5vf4R88FEE4T2C+fEOyb6Nct0c2e54eqpOIToZYBBWlT4PdQVjU7vVoxTf3ISk604v24fyAi5KtSVF+r46ogiKTZwuKU8iNJHdLziXOSr891ikYZwAfZlfHG+8mVexgqfUEDKW1p1Jez4KaCz+KI6TikKy77lD4fdVepSvk+P1kFkg5EmnFylujMDZYl0abes9MRI3nHhkEOrmZVcxxzGqrl0JQlsWqtVpCWJKmpqrd770BbefQmjhoMHIRFX6bdOqwvlZMZveU3CnrmXamrFaauAtsXCm+61tkZFaJC0a1Y3==

Then you should calculate a signature header with several values to be able to validate it.

The signed request should be as:

POST https://ing.ingdirect.es/genoma_login/rest/session
Accept”: “*/*”
Content-Type”: “application/json; charset=utf-8”
Digest: FBaMNP31AnqAwLuzF6O78cEWw8n0xwZDCnpUu0PkXHc=
X-Request-Id: 83a47cee-f23b-4766-aabb-930e6d969ec6
Signature: keyId=“SN=B83852160,CA=FIRMA PROFESIONAL”,algorithm=“rsa-sha-256”,headers=“digest x-request-id”,signature=“qz0Ae1Wh39yaX8tgHv2up8JRSAjPqI6EGJtd5vf4R88FEE4T2C+fEOyb6Nct0c2e54eqpOIToZYBBWlT4PdQVjU7vVokTf3ISk604v24fyAi5KtSVF+r46ogiKTZwuKU8iNJHdLziXOLSr891ikYZwAfZl+8mVexgqfUEDKW1p1Jez4KaCz+KI6TikKy77lD4fdVeP1kFkg5EmnFylujMDZYl0xxes9MRI3nHhkEOrmZVcDpzGqrl0JQlsWqtVpCWJKmpqrd770BbefQmjhoMHIRFX6bdOqwvlZMZveU3CnrmXamrFaauAtsXCm+61tkZFaJC0a1Y3w==”
TPP-Signature-Certificate: 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{”birthday”:”12/12/1984",”device”:”desktop”,”loginDocument”:{“document”:”12345678W”,”documentType”:0}

(Don’t try to verify this message we have removed random chars from the data when copying here)

This strange mechanism also generates a long message because Berlin Group specifies that the certificate value is included in each request!

JWS is fun

JWT is much simpler, compact and easy as we just need to build the claims; JWT/JWS for HTTP Messages will use the claims:

{
“iss”: “C=ES, ST=MADRID, O=EUROBITS TECHNOLOGIES, S.L./2.5.4.97=PSDES-BE-6887/serialNumber=B83852160, CN=EUROBITS TECHNOLOGIES, S.L.”,
“iat”: 1564998879,
“exp”: 1564998879,
“aud”: “POST https://ing.ingdirect.es/genoma_login/rest/session",
“sub”: “12345678W”,
“digest”: “FBaMNP31AnqAwLuzF6O78cEWw8n0xwZDCnpUu0PkXHc=”,
“uuid”: “83a47cee-f23b-4766-aabb-930e6d969ec6”
}

And use a library among the myriad of available options to get a JWT

try {
Algorithm algorithm = Algorithm.RS256(“secret”);
String token = JWT.create()
.withIssuer(“auth0”)
.sign(algorithm);
} catch (JWTCreationException exception){
//Invalid Signing configuration / Couldn’t convert Claims.
}
// (source: https://github.com/auth0/java-jwt)

The only needed header is (well, plus the annoying TPP-Signature-Certificate):

Authorization: Signature eyJhbGciOiJSUxxxNiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.POstGetfAytaZS82wHcjoTyoqhMyxXiWdR7Nn7A29DNSl0EiXLdwJ6xC6AfgZWF1bOsS_TuYI3OG85AmiExREkrS6tDfTQ2B3WXlrr-p5Aokixxz3_oB4OxG-9KcEEbDRcZc0nH3L7LzYptiy1PtAylQGxHTWZXtGz4ht0bAecBgmpdgXMguEIcoqPJ1n3pIWk_dUZegpqx0Lka21H6XxUTxiy8OcaarA8zdnPUnV6AmNP3ecFawIFYdvJB_cm-GvpCSbr8G8y_Mllj8f4x9nBH8pQux89_6gUY618iYv7tuPWBFfxxLxtF2pZS6YC1aSfLQxeNe8djT9YjpvRZA

(again, the data is an example string)

The TPP-Signature-Certificate in Berlin Group API

TPP-Signature-Certificate header is something we cannot avoid in PSD2 APIs with JWT because it is specified by Berlin Group even if it not really necessary. The iss claim should be enough, and when invoking not-Berlin-Group APIs we recommend to not include this long string.

ASPSP using JST claims for PSD2

With the claims structure of a JWT we are also adding a clear meaning to our message:

“The PSD2 licensed claims that is making this request to the indicated resource is with the consent, and on behalf, of the user (PSU).”

Of course, this is not soo different from the cavage signature but this subtle difference clearly expresses the intention and allows the ASPSP to take advantage of the JWT format and check these claims with modern and robust existing libraries:

  • To check the PSU has granted consent to the TPP, checking subject against issued claims.
  • To filter and allow the PSD2 calls of their REST APIs while providing the full API only to their own front-ends.

This filtering can be easily implemented by just asking tokens claims.

Finally, is easy to add additional claims if needed without any impact on the signing mechanism.

References

About Eurobits

In 2004 we set out to transform the way in which people and organisations relate to financial services by helping our customers — Banks, Fintech and Central Governments, create products and services that promote the digital economy.

We share this accumulated experience with our clients and partners, because their success is also our success. Therefore, the products and services we develop aim to be the best in each of the markets that we operate, while remaining true to our values: perform with quality, honesty and respect.

--

--