BGL BNP PARIBAS

BGL BNP Paribas XS2A (STET)

Version:  1.1413.037.004
State:  Prototyped
Environment: Sandbox
Base URI: https://bglllull-psd2api-sbx.luxhub.com/stet/v1
Authorization Endpoint: https://bglllull-cb-sbx.luxhub.com/api/oauth/authorize
Token Endpoint: https://bglllull-psd2api-sbx.luxhub.com/api/oauth/token
Categories: PSD2
Passport :

Security

eIDAS Certificates considerations

The eIDAS certificates referred to in this guide are SSL certificates with dedicated protection profiles that allows to be used in the PSD2 context. To achieve the PSD2 security requirements, banks and PSD2 service providers will use Qualified Certificates for Websites and Qualified Certificates for Electronic Seals. Those certificates will be issued by Qualified Trust Service Providers (QTSPs) based on the new technical standard, ETSI TS 119 495, which was published in May 2018. Qualified Certificates enable identification and verification of the payment institution by a third party. Identification will be based on the legal name of an organization, registration number and its main role(s) in the payments space.

There are two types of such certificates:

QWAC (Qualified Website Authentication Certificate): used as Client Certificates in MA-TLS

QSeal (Qualified Certificate for Seals) : used to sign requests using http-signature

All PSD2 APIs require both types of certificates, QWAC to access the API and QSeal for http-signature, i.e. message signing.

LUXHUB implementation of certificates

Our PSD2 APIs are protected by Mutual TLS protocols based on eIDAS Certificates, as required by the PSD2 European Directive. This means that if you want to access one of our PSD2 APIs, you need to use an eIDAS TLS Client Certificate for your requests. If you don’t have such a certificate, you can download a mock eIDAS Certificate from our Developer Portal (Log in, then go to Applications -> select Application -> Download QWAC Certificate). Our mock certificate nevertheless only allow access to Sandbox APIs. To access Production, you need to get your own PSD2 eIDAS Certificate from a Qualified Trust Service Provider.

At the application level, the PSD2 APIs require message signing following http-signature specification, with the signing QSeal certificate (different from the one used as client Certificate). To access the Sandbox, we also provide you such a mock certificate for message signing. The "download QSeal certificate button" is located just next to the one for downloading a QWAC Certificate.

Access Management

Our APIs use OAuth2 as authorization mechanism for access management to the endpoints. The main points related to the implementation of OAuth2 in the context of PSD2 APIs are presented below.

API Authorization

Berlin Group specifications do not impose any specific authorization framework to be used, whereas STET specifications specifically mentioned OAuth2 as authorization mechanism. Therefore, LUXHUB decided to employ state of the art authorization by protecting all endpoints via OAuth2 authorization. Please consult RFC 6749 for more details about this authorization framework.

Authorization Code Grant

The most used flow is Authorization Code grant, which allows PSU to authorize (give consent) to its resources in a secure manner. The authorization code provides a few important security benefits, such as the ability to authenticate the client, as well as the transmission of the access token directly to the client without passing it through the resource owner’s user-agent and potentially exposing it to others, including the resource owner. [https://tools.ietf.org/html/rfc6749#page-24]

Client Credentials Grant

There are few endpoints where the Client Credentials flow is also allowed, in which cases the respective resources to which it is granted access are considered completely detached from the PSU until they are authorized by him - they are considered to belong to the TPP up to this moment.

Client credentials are used as an authorization grant typically when the client is acting on its own behalf (the client is also the resource owner) or is requesting access to protected resources based on an authorization previously arranged with the authorization server. [https://tools.ietf.org/html/rfc6749#page-40]

Proof Key for Code Exchange

As an additional security measure to protect the Authorization Code Grant in OAuth2 flow, LUXHUB is implementing Proof Key for Code Exchange (PKCE) as described in [https://tools.ietf.org/html/rfc7636]. This mitigates the threat of having the authorization code intercepted. The technique involves the client first creating a secret, and then using that secret again when exchanging the authorization code for an access token. This way if the code is intercepted, it will not be useful since the token request relies on the initial secret.

Registering your application for API access

To access an API you need to obtain your OAuth2 credentials (client_id and client_secret) and use them to get tokens within the scope needed for the endpoint you are trying to access. There are two options to do so:

  • Dynamic registration based on eIDAS Certificate

For all the PSD2 APIs, we provide OAuth2.0 Dynamic Client Registration endpoints. You just need to call the /register endpoint of the Third Party Management API, using your QWAC eIDAS certificate. In the request, you will have to provide the required redirect URI to your application, where the customer will be redirected after authentication. In the response, you will receive the credentials that you will need to use in order to get details about the API exposed on the LUXHUB platform and register access for your application to the ones that you deem interesting.

This Dynamic Client Registration for TPPs will be available, for now, only in Production environment. Details about how it is to be used are available in Onboarding via API paragraph in this document.

  • Use our Developer Portal

If you don’t have an eIDAS Certificate yet or if you want to create more than one set of OAuth Credentials, you can use the Developer Portal for authorizing your application. If you don’t have an account yet, you need to register first (see How to register section above). Once you are logged in, you can go to the Applications section and create a new application.

Credentials management and API access is managed at application level, i.e. each application will receive a pair of (client_id, client_secret) credentials and will have to register the APIs to which it requires access.

You can change the APIs supported by your application at any time, but keep in mind that you have to do two important - immutable - choices:

Environment: You cannot mix Production APIs with Sandbox APIs

Category: As of now ONLY applications using PSD2 API can be created, in the near future non-PSD2 APIs will be offered as well. The current documentation will be updated to reflect this change once available.

When creating an application you also have to provide the redirect URLs of your application. This is where your application’s users (named Payment Service User - PSU - in PSD2 lingo) will be redirected after doing strong customer authentication (SCA) in the bank realm. Depending on your implementation, you may implement several redirect URLs (for example different redirect URls for account information consent approval and for payment authorization).

The redirect URLs defined here will have to match the URLs provided when the application calls/api/oauth/authorize endpoint (details in sections below).

Finally you will have to upload an X.509 Certificate, which will help us generate a PSD2-compliant OAuth2 client_id. In Production, you have to use your own eIDAS Certificate granted by a QTSP - the same certificate used to call the bank’s API. In Sandbox, we will provide you with a mock certificate that you can use. You can download your certificate from the Applications page. Binding a real eIDAS certificate to your application via the portal is also supported in Sandbox, in which case the said certificate needs to be uploaded.

Message signing 

STET standard

STET specification enforces message signing, based on http-signature protocol as defined in [https://tools.ietf.org/html/draft-cavage-http-signatures-10], for requests and makes it optional for responses. Please see chapter 3.5 Applicative authentication in STET PSD2 API Documentation Part 1: Framework for details on how the message signing should be implemented.

The electronic signature of the TPP has to be based on a qualified certificate for electronic seals. This qualified certificate has to be issued by a qualified trust service provider according to the eIDAS regulation. The content of the certificate has to be compliant with the requirements of EBA RTS on SCA and CSC. The certificate of the TPP has to indicate all roles the TPP is authorized to use. The public key needed for signature verification is requested to be an URL aiming to provide the relevant Qualified Certificate.

As per the STET standard Documentation Framework, chapter 3.5, the http-signature should be computed in the following way:

1. Compute a SHA256 digest of the HTTP body and adding this digest as an extra HTTP header.

2. Use a specific Qualified Certificate (QSealC), respecting the ETSI/TS119495 Technical Specification, in order to apply a RSA-SHA256 signature on:

  • all the following headers that are present within the HTTP request sent by the TPP, including the previously computed digest:
    • Date (if available)
    • Content-Type
    • Content-Length (when there is a payload)
    • X-Request-Id
    • All available "PSU"-prefixed Headers
  • all the following headers that are present within the HTTP response given by the ASPSP, including the previously computed digest
    • Date (if available)
    • Content-Type
    • Content-Length (when there is a payload)
    • X-Request-Id
    • on the specific “(request-target)” field which is specified by the IETF draft-paper

3. Add this signature within an extra HTTP header embedding

  • The key identifier which must specify the way to get the relevant qualified certifi cate. It is requested that this identifier is an URL aiming to provide the relevant Qualified Certificate.
  • The algorithm that has been used
  • The list of headers that have been signed
  • The signature itself.
  • In order to assure an easy discrimination of the certificate among others, it is requested that the last part of the URL to the certificate be suffixed by an underscore followed by the fingerprint of the certificate. E.g.: https://path.to/myQsealCertificate_612b4c7d103074b29e4c1ece1ef40bc575c0a87e

Request example

GET /accounts
Headers:
Date: Mon, 14 Jan 2019 15:02:24 +0100
PSU-Date: 2017-06-08T09:33:55.954+02:00
Accept: application/hal+json; charset=utf-8
PSU-GEO-Location: GEO:52.506931,13.144558
Digest:
X-Request-ID: GGF3YUD3BDJK
PSU-Referer: http://en.wikipedia.org/wiki/Main_Page
PSU-IP-Port: 12345
PSU-Accept: text/plain
Authorization: Bearer 1234567890AZERTYUIOP
PSU-Accept-Charset: utf-8
PSU-Accept-Encoding: gzip, deflate
PSU-IP-Address: 10.10.10.10
PSU-User-Agent: Mozilla
PSU-HTTP-Method: POST
PSU-Accept-Language: en-US
Content-Type: application/json
User-Agent: Swagger-Codegen/1.0.0/java
Signature: keyId="https://path.to/myQsealCertificate_612b4c7d103074b29e4c1ece1ef40bc575c0a87e",algorithm="rsasha256",headers="date psu-date psu-geo-location digest x-request-id psu-referer psu-ip-port psu-accept psu-accept-charset psuaccept-encoding psu-ip-address psu-user-agent psu-http-method psu-accept-language content-type (requesttarget)",signature="ny8dUFiW9B0NBhylZa+NQf3tHVkjtMRvExfbnXps5NkN+dVDqetfp5MBmmwXIS4/eNNYxXp4m/1ct5AdDulCNyu9 PeVDii2a2Nv2NXE5YGN8SF/R/io4tmIpoYTDUxPqR6IrhUGB2Jcso1ibx0bwGXgFjp9EPniAFaLBBAipasY="

 

 

http-signature code snippets

Below code snippets are provided "as-is" with no express guarantee or licence and free of charge, as a help to third parties that would like to connect to the APIs available on LUXHUB platform. They are known to work at the moment of being presented in this guide. Each third party using these should take it's own due diligence if they plan to use them in production applications.

1. Java
- the Java code below can be used as well from Scala and Groovy

 

//berlin group version
import org.tomitribe.auth.signatures.PEM;
import org.tomitribe.auth.signatures.Signature;
import org.tomitribe.auth.signatures.Signer;...

public class HttpSignatureCalculatorBG implements SignatureCalculator {
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("EEE, dd MMM YYYY HH:mm:ss");
private static final List<String> ALLOWED_HEADERS = Arrays.asList("X-Request-ID", "Date", "Digest");

private String certPath;
private String keyPath;

public HttpSignatureCalculatorBG(String certPath, String keyPath) {
this.certPath = certPath;
this.keyPath = keyPath;
}

@Override
public void sign(Request request) throws Exception {

String date = DATE_FORMAT.format(new Date()) + " GMT";

request.getHeaders().set("Date", date);

if(request.getBody() != null) {
String requestBody = new String(request.getBody().getBytes());
request.getHeaders().set("Digest", generateDigest(requestBody));
} else {
request.getHeaders().set("Digest", generateDigest(""));
}

BufferedReader reader = new BufferedReader( new InputStreamReader(loadFile(certPath) ) );
String certificate = reader.lines().collect( Collectors.joining( System.lineSeparator() ) );

request.getHeaders().set("TPP-Signature-Certificate", formatCertificate(certificate));
request.getHeaders().set("Signature", generateSignature(request));

}

private String formatCertificate(String certificate) {
return certificate.replaceAll("\n", "")
.replaceAll("\r", "")
.replace("-----BEGIN CERTIFICATE-----", "")
.replace("-----END CERTIFICATE-----", "");
}

private String generateDigest(String payload) {
try {
byte[] digest = MessageDigest.getInstance("SHA-256").digest(payload.getBytes());
return "SHA-256=" + new String(Base64.getEncoder().encode(digest));
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException("Wrong algorithm", e);
}
}

private String generateSignature(Request request) throws CertificateException, InvalidKeySpecException, IOException {

Map<String, String> headersMap = new HashMap<String, String>();
List<String> signatureHeaders = new ArrayList<String>();

request.getHeaders().forEach(entry -> {
if(!ALLOWED_HEADERS.contains(entry.getKey())) {
return;
}
headersMap.put(entry.getKey(), entry.getValue());
signatureHeaders.add(entry.getKey());
});

CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(loadFile(certPath));

String keyId = String.format("SN=%s,CA=%s", cert.getSerialNumber(), cert.getIssuerDN());
String algorithm = "rsa-sha256";

Signature signature = new Signature(keyId, algorithm, null, signatureHeaders);

PrivateKey privateKey = PEM.readPrivateKey(loadFile(keyPath));

Signer signer = new Signer(privateKey, signature);

String method = "method";
String uri = "uri";

Signature signed = signer.sign(method, uri, headersMap);

return signed.toString();

}

private InputStream loadFile(String content) throws FileNotFoundException {
return new ByteArrayInputStream(content.getBytes());
}

}

// stet version
import org.tomitribe.auth.signatures.PEM;
import org.tomitribe.auth.signatures.Signature;
import org.tomitribe.auth.signatures.Signer;...

public class HttpSignatureCalculatorStet implements SignatureCalculator {

    private static final List<String> ALLOWED_HEADERS = Arrays.asList("X-Request-ID", "psu-date", "Digest",
            "PSU-IP-Address", "(request-target)", "content-length", "content-type");

    private String keyPath;
    private String certificateUrl;

    public HttpSignatureCalculatorStet(String keyPath, String certificateUrl) {
        this.keyPath = keyPath;
        this.certificateUrl = certificateUrl;
    }

    @Override
    public void sign(Request request) throws Exception {

        String date = String.valueOf(System.currentTimeMillis());
        String requestTarget = request.getMethod().toString() + " " + request.getUri().getPath();

        if (request.getBody() != null) {
            String requestBody = new String(request.getBody().getBytes());
            request.getHeaders().set("Digest", generateDigest(requestBody));
            request.getHeaders().set("content-length", requestBody.length());
        } else {
            request.getHeaders().set("Digest", generateDigest(""));
        }

        request.getHeaders().set("psu-date", date);
        request.getHeaders().set("PSU-IP-Address", InetAddress.getLocalHost().getHostAddress());
        request.getHeaders().set("(request-target)", requestTarget);

        request.getHeaders().set("Signature", generateSignature(request));

    }

    private String generateDigest(String payload) {
        try {
            byte[] digest = MessageDigest.getInstance("SHA-256").digest(payload.getBytes());
            return "SHA-256=" + new String(Base64.getEncoder().encode(digest));
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalArgumentException("Wrong algorithm", e);
        }
    }

    private String generateSignature(Request request)
            throws CertificateException, InvalidKeySpecException, IOException {

        Map<String, String> headersMap = new HashMap<String, String>();
        List<String> signatureHeaders = new ArrayList<String>();

        request.getHeaders().forEach(entry -> {
            if (!ALLOWED_HEADERS.contains(entry.getKey())) {
                return;
            }

            headersMap.put(entry.getKey(), entry.getValue());
            signatureHeaders.add(entry.getKey());
        });

        String algorithm = "rsa-sha256";

        Signature signature = new Signature(certificateUrl, algorithm, null, signatureHeaders);

        PrivateKey privateKey = PEM.readPrivateKey(loadFile(keyPath));

        Signer signer = new Signer(privateKey, signature);

        String method = request.getMethod().toString();
        String uri = request.getUri().getPath();

        Signature signed = signer.sign(method, uri, headersMap);

        return signed.toString();

    }

    private InputStream loadFile(String content) throws FileNotFoundException {
        return new ByteArrayInputStream(content.getBytes());
    }

}

 

2. Python

- definition of signing string functions:

#berlin group version
def set_bg_signature(self, http_signature_cert, http_signature_key):
    key_id = get_cert_key_id(http_signature_cert)
    algorithm = 'rsa-sha256'
    headers = 'x-request-id date digest'
    signing_string = 'x-request-id: ' + self.x_request_id + '\ndate: ' + self.date + '\ndigest: ' + self.digest
    signature = sign_string(http_signature_key, signing_string, 'sha256')
    signature_header = 'keyId="' + key_id + '",algorithm="' + algorithm + '",headers="' + headers + '",signature="' + signature + '"'
    self.signature = signature_header

#stet version
def set_stet_signature(self, http_signature_cert_url, method, url, query_params, http_signature_key, body=None):
    key_id = http_signature_cert_url
    algorithm = 'rsa-sha256'
    headers = 'psu-ip-address psu-date x-request-id (request-target) digest'
    #if body is empty
    headers = headers + ' content-length content-type'
    request_target = method.lower() + ' ' + url.replace(API_Test_Variables.env, '') + params_str
    signing_string = 'psu-ip-address: ' + self.psu_ip_address + '\npsu-date: ' + self.psu_date + '\nx-request-id: ' + self.x_request_id + '\n(request-target): ' + request_target + '\ndigest: ' + self.digest
    #if body is not empty
    signing_string = signing_string + '\ncontent-length: ' + str(len(json.dumps(body))) + '\ncontent-type: ' + headers['Content-Type']
    signature = sign_string(http_signature_key, signing_string, 'sha256')
    signature_header = 'keyId="' + key_id + '",algorithm="' + algorithm + '",headers="' + headers + '",signature="' + signature + '"'
    self.signature = signature_header

where the sign-string function is:

from OpenSSL import crypto
...
def sign_string(key, signing_string, algorithm):
    pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, open(key).read())
    signature = base64.b64encode(crypto.sign(pkey, signing_string, algorithm))
    return signature

 

3. JavaScript

//berlin group version
const crypto = require('crypto');
generateBGSignature(headers, body) {
    let headerCert = QSEALCert.replace(/\r/g, '');
    headerCert = headerCert.replace(/\n/g, '');
    headerCert = headerCert.replace(/-----[^ ]+ CERTIFICATE-----/g, '');
    headers["TPP-Signature-Certificate"] = headerCert;

    const {
        Certificate
    } = require('@fidm/x509');
    const cert = Certificate.fromPEM(QSEALCert);

    const digest = "SHA-256=" + crypto.createHash('sha256').update(body).digest('base64');
    let signingString = "digest: " + digest + "\n" +
        "x-request-id: " + headers["X-Request-ID"] + "\n" +
        "date: " + headers["Date"];

    let signatureHeaders = "Digest X-Request-ID Date";

    if (typeof headers["PSU-ID"] !== "undefined") {
        signingString += "\nPSU-ID: " + headers["PSU-ID"];
        signatureHeaders += " PSU-ID";
    }

    if (typeof headers["PSU-Corporate-ID"] !== "undefined") {
        signingString += "\nPSU-Corporate-ID: " + headers["PSU-Corporate-ID"];
        signatureHeaders += " PSU-Corporate-ID";
    }

    if (typeof headers["TPP-Redirect-URI"] !== "undefined") {
        signingString += "\nTPP-Redirect-URI: " + headers["TPP-Redirect-URI"];
        signatureHeaders += " TPP-Redirect-URI";
    }

    headers["Digest"] = digest;
    headers["Signature"] = "keyId=\"SN=" + cert.serialNumber +
        this.getCertificateIssuerValues(cert.issuer) +
        ", algorithm=\"rsa-sha256\"" +
        ", headers=\"" + signatureHeaders + "\"" +
        ", signature=\"" + crypto.createSign('SHA256').update(signingString).sign(QSEALKey, 'base64') + "\"";

    return headers;
}

//stet version
const crypto = require('crypto');
generateSTETSignature(headers, method, url, body) {
    headers["Digest"] = "SHA-256=" + crypto.createHash('sha256').update(body).digest('base64');

    let signingString = "(request-target): " + method.toLowerCase() + " " + url;
    let signatureHeaders = "(request-target)";

    for (let key in headers) {
        if (headers.hasOwnProperty(key)) {
            signingString += "\n" + key.toLowerCase() + ": " + headers[key];
            signatureHeaders += " " + key;
        }
    }

    headers["Signature"] = "keyId=\"" + my_url_to_certificate + "\"" +
        ", algorithm=\"rsa-sha256\"" +
        ", headers=\"" + signatureHeaders + "\"" +
        ", signature=\"" + crypto.createSign('SHA256').update(signingString).sign(QSEALKey, 'base64') + "\"";

    return headers;
}
 
 

This website uses cookies. By continuing to use our website, you accept the use of these cookies.