Skip to content

Microservice using eIDAS certificates for signing Open Banking / PSD2 API requests and accessing banks’ APIs over mTLS

License

Notifications You must be signed in to change notification settings

enablebanking/open_banking_eidas_broker

Repository files navigation

CAUTION: This project works with highly sensitive data, thus carefully consider its configuration when using in production

Open Banking eIDAS broker

The broker service provides possibility to use eIDAS certificates (in practice any X.509 certificates) for generating signatures and sending HTTP requests over mTLS connections without need to expose private keys of the certificates with the service client.

The web API of the broker service consists of 3 endpoints:

  1. POST /sign -- for signing received data with a QSealC certificate and returning this signature back;
  2. POST /makeRequest -- for making HTTP request over mutual TLS connection established with a QWAC certificate and returning response back;
  3. GET /health -- for health checks to make sure that application is up and running.

For more information please refer to the API specification.

Access to the broker service APIs is provided over mTLS and authentication of the client is done based on the client certificate. The client certificate and the broker server certificate shall be signed using the same CA certificate.

The broker service is primarily designed to be called from Enable Banking aggregation core, which provides special BrokerPlatform class offloading signing and mTLS funtionality to the broker.

Accessing ASPSP APIs through eIDAS broker

The flow of the calls between client, broker service and ASPSP looks like this:

   [Client premises]            --   [Broker service holding eIDAS keys]   --   [Open banking API (ASPSP)]

1. OB API request to be signed  ->   Signing the data using a QSealC
                                     certificate named by the client
   Request signature            <-   and returning the signature

                                     Forwarding the request to an ASPSP 
2. OB API request to be sent    ->   over mTLS established with a QWAC     ->   ASPSP gets complete API
                                     certificate named by the client            request, verifies the
                                                                                signature, and responses
   Response from the ASPSP      <-   Returning the response back to the    <-   to the broker service
                                     initiating party

The client may request to use different certificates (identified by URI) and to forward arbitrary requests (to different ASPSPs).

Generation of certificates for client - broker interaction

As mentioned earlier mTLS connector is used for securing interactions between the client (aggregation SDK using BrokerPlatform) and the broker service. This provides adequate level of security even when the client and the broker use are connected through Internet (the same mechanism is used for securing open banking APIs).

The most important thing in ensuring security of the interaction is keeping private keys securely stored and not transferring them over insecure channels. Thus CA and server private keys shall be generated at the broker site (assuming that client and broker are located in different networks and secure communication between them can not be guaranteed), while client private key is generated at the client site. The client certificate is signed with CA key at the broker site; for this certificate signing request (CSR) is transferred from the client site to the broker, which can be done over insecure channels.

CA

First of all CA private key shall be generated (this is done at the broker site); in the examples below we are using OpenSSL command line utility.

openssl genrsa -out ca.key 4096

And CA certificate shall be generated (if necessary, replace values under -subj parameter with your values).

openssl req -new -x509 -days 1825 -key ca.key -out ca.crt \
    -subj "/C=FI/ST=Uusimaa/L=Helsinki/O=ExampleOrganisation/CN=ca.example.com"

NB: In the example above the CA certificate validity period is set to 5 years, which allows to rotate server and client certificates without the need to replace CA certificate itself.

Server

Then server (broker) private key can be generated (again at the broker site).

openssl genrsa -out server.key 4096

And server CSR is to be generated. Make sure the CN value in the subj parameter matches the host name, which will be used by the broker (in the example below localhost is used for the case when we are testing broker service locally, i.e. running on the same machine, which is used for accessing it).

openssl req -new -key server.key -out server.csr \
    -subj "/C=FI/ST=Uusimaa/L=Helsinki/O=ExampleOrganisation/CN=localhost"

The server certificate is to be signed with ca.key. To ensure security DO NOT use md5 message digest. In the examples below we use sha256.

openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt -sha256

Client

Finally a private key for the client certificate can be generated (this is done at the client site).

openssl genrsa -out client.key 4096

And client CSR is generated the same way how it's done for the server (please note that the subject should be different from the CA's subject).

openssl req -new -key client.key -out client.csr \
    -subj "/C=FI/ST=Uusimaa/L=Helsinki/O=ExampleOrganisation/CN=client.example.com"

Finally client.csr can be transferred to the site where ca.key is available and signing of the client certificate with ca.key can be done.

openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 02 -out client.crt -sha256

The generated client certificate (client.crt) can be shared back with the client.

Verification of the certificates (optional)

You can verify server and client certifiactes against CA certificate using the following commands.

openssl verify -purpose sslserver -CAfile ca.crt server.crt
openssl verify -purpose sslclient -CAfile ca.crt client.crt

Building an image and starting a container

In order to build an image you need to:

  1. Have docker installed

  2. Go to the directory with Dockerfile

  3. Run docker build -t <image_name> . (probably you need to prepend this command with sudo)

  4. Put broker certificates you generated earlier into broker_tls/ directory under following names:

    • server.key # private key of the server (broker) certificate
    • server.crt # public server (broker) certificate
    • ca.crt # public CA certificate
  5. Put your eIDAS certificates and their private keys, which will be used when accessing ASPSPs' APIs, i.e. QWAC (mTLS) and QSealC (signature) into open_banking_certs/ directory, which will be mounted to the container.

    You can put certificates in an arbitrary order/names. Later you will have to provide paths to those certificates.

    All certificates must be in the PEM format.

    • qwac.key # QWAC private key. Needed for establishing mTLS
    • qwac.crt # QWAC public certificate. Needed for establishing mTLS
    • qwac_chain.crt (optional) # QWAC certificate chain. Some ASPSPs require it
    • qsealc.key # QSealC private key. Used for creating signatures

    If private keys are encrypted, it is possible to provide passwords for decrypting the keys by setting environment variables that contain the passwords. Environment variables should be named after the key file names, with special characters replaced by underscore symbols, and suffixed with _PASSWORD. For example, the password for qwac.key should be set in the environment variable named QWAC_KEY_PASSWORD.

    It is also possible to provide keys and certificates as environment variables. In order to enable such functionality, pass KEY_LOADER=ENV environment variable when starting a container. In this case, the environment variables should be named with _CRED suffix. For example, the QWAC private key should be provided in the QWAC_KEY_CRED environment variable.

  6. Start built image:

    docker run -d \
        --name <container_name> \
        -p 443:443 \
        --mount type=bind,source="$(pwd)"/open_banking_certs/,target=/app/open_banking_certs/ \
        --mount type=bind,source="$(pwd)"/broker_tls/,target=/app/broker_tls/ \
        <image_name>
    
  7. You can verify that the service is running correctly by running the following command:

    curl --location 'https://localhost:443/' --key client.key --cert client-chain.crt --cacert ca.crt
    

    You should received {"result":"eIDAS broker"} in response.

Overriding the list of trusted CA certificates

Although this is not in line with regulations and security best practices, some ASPSPs may use self-signed certificates or certificates that do not have commonly recognised CAs in their chains. In order to accept connections when such certificates are used, this repository (and pre-built images) contains several not commonly recognised certificates in the trust list.

It is also possible to override the list of trusted CA certificates by mounting an external folder containing such certificates and overriding the environment variable SSL_CERT_FILE to point to this folder. For example:

docker run -d \
    --name <container_name> \
    -p 443:443 \
    --mount type=bind,source="$(pwd)"/open_banking_certs/,target=/app/open_banking_certs/ \
    --mount type=bind,source="$(pwd)"/broker_tls/,target=/app/broker_tls/ \
    --mount type=bind,source="$(pwd)"/trusted_ca_certs/,target=/app/trusted_ca_certs/ \
    -e SSL_CERT_FILE='/app/trusted_ca_certs'
    <image_name>

API specification

Full API specification of in the OpenAPI format is available in the openapi.json file.

When running the service locally, endpoints documentation is available at /docs or /redoc:

  • http(s)://localhost:<host_port>/docs (Swagger UI)
  • http(s)://localhost:<host_port>/redoc (Redoc)

Examples

For the service invocation examples using requests library, please refer to example.py. The example makes "proxied" calls (using POST /makeRequest) to https://postman-echo.com and signs test string (using POST /sign) with example.key.

RS256 signing (RSASSA-PKCS1-v1_5 + SHA256)

POST /sign

Request payload:

{
    "params": {
        "data": "a string to sign",
        "key_id": "example.key",
        "hash_algorithm": "SHA256",
        "crypto_algorithm": "RS"
    }
}

Response payload:

{
    "result": "EK148de5pvmRoHpsd1HplHLjS4KjMrfJK4RrGyeyhourddCAXJP+7+ZBfVZYdzf8/B/KhyYgY657RyHtTy33Am9xbtJQpIr3q4xXN4VYuwnHHaqMg9GgnmUC9Cze9OCeXdo7w+TVVf6B+vDp6tFWvTnZDfd1pe+JhGIAeDDVqNvNZu+MQ8zjfO3Y/8XHFrPmfLMge6WZLCNJTHmqiJEMIEWVJXgjG1OPnZzadax+lAEL4hm/fA/biLh6etNyiwlNx7mUYCEc4gOuKjBuzNwEwc5Yp8RW/ibiX6n0UJhIlpShxag0+Lv2uanSsxna9NhMYgJuf+jjNLhTDsFwBvhMYduMCHCeO2T0d3k1VZoj0MAhT8Luc8iAWT8oJL4qqEAU6A6TqNV/pmuJfFnlyeTwxTHauDb/UtLmXErp3khu2z/yD+Y/TVFSrHjZ2QaKoXf4xsLdbFLHyzG4OwV13Pl9fK4x40oKM84i1Di4oxkAdwM4UuhEK33QCh/x5fKbq8SB2qSQOyh99/w0XoAOIviuh+U/ibLxQqDku8jyKj8Zp8femRr81cgjZonRX3uFbqOnUhjHiTpIZAZVUhhPkPM2tzkVixCp9tKRevurK8ZfJy/ZJEhMwfPgGRQn3Cn3wG09Dr9OFXHmz0cmJnAV0ZVXJnD0U4tupYGI18Vgdixdtog="
}

The above example uses example.key. In case you want to verify the result you get, you can simply compare it to the result from above, it should match exactly.

PS256 signing (RSASSA-PSS + SHA256)

POST /sign

Request payload:

{
    "params": {
        "data": "a string to sign",
        "key_id": "example.key",
        "hash_algorithm": "SHA256",
        "crypto_algorithm": "PS"
    }
}

Response payload:

{
    "result": "I90i3W+JdWlt21titsP14N2DUnrM5lTZtiLTQGWRMM5gBent25ktWmOaxNTQUdD0Nt8PEu9YNMKVCQ9nbWVzYYPM9Vto59hnBRD6Eb2xPQ1T0v7ecBTrkI42+1mrZ3eZbbTCLCIseWtVJXpTz34kW5kQRueTgPlTAwLzL13gQWLwzSpq4ENX4IL9EqczTnyBeOdQuIaIE7yj2bdiCsqF2M/N8Sdo1R2kcQoUGuVeBe3A3XfLtLzPvTDoyiQhHDVtxv/tnb0CmGWGm3/fm0Eu6Vr3KmO5AOAWeh01erQA4NZ88oJkexNt+IN5LoNZ2jofCu4k7uOnpPOkSizWeF4c8i/LekcwySH0DDyAMkjriGtJx0y+r7RC3zAqSdh+aWWRSpbQVOcQp32zSs4F0LsaFM1fL5JjdbyjrhHO8ymW1/coQP63hGjYvlAMAB0g+gx0Ue7IIDJmcTGcZf3o8fag9BuqZo3QKgVmS85alHs/yIJDCuNTnX83NNwgbZdrsQr3Oc4k3bdQ5CCR8zo4CNhVkS/fTXSNn1fMicTxRXcL5JRFKCQAuWM9I3p/YUb0yGN3xvL7XMXw2sbkt+RrkNsRmM9UKs54eKkIPdPeZ0Zcn5AzyKMIE9DTqwkSaqID3nKIXAaR73o9diK9WiOACim2FZ2Na2m0kz67+xfgWW+OLlw="
}

The above example uses example.key. In case you want to verify the result you get, it is necessary to generate a corresponding public key and verify result using it, because the result will be always different due to probabilistic behavior of RSASSA-PSS.

Implementation

The service is implemented in Python 3.11. RESTful API of the service is implemented using the FastAPI framework.

ServicePlatform class from server_platform.py contains sign_with_key and make_request methods, which correspond to POST /sign and POST /makeRequest API endpoints.

Cryptographic operations (necessary for implementation of the POST /sign endpoint) use cryptography library (which itself depends on the OpenSSL C library).

Making HTTP request (for the POST /makeRequest endpoint) is done with the Python's standard urllib library.

Certificates and their private keys used for making mTLS connections and cryptographic signing are read from the file system, which can be changed if necessary by changing corresponding functionality in the ServicePlatform class.

Implementation of secured access to the service (using client TLS certificate verification) relies on nginx. Please refer to nginx.conf. Please notice that this configuration uses port 443 for secured connections.

Running service locally without Docker

Setting up environment:

pyenv install 3.11.1
pyenv virtualenv 3.11.1 open-banking-eidas-broker-3.11.1
pyenv activate open-banking-eidas-broker-3.11.1
pip install -r requirements.txt

Running the service:

gunicorn app.main:app -c gunicorn_conf.py -k uvicorn.workers.UvicornWorker --bind=:8888 --chdir=app

--

Copyright 2021 Enable Banking Oy