Signing Service — Relying Party Developer Guide

The signing service gives your customers a way to conveniently sign digital documents using qualified electronic signatures based on the identity data and credentials associated with their online banking accounts.

1. Overview

The following sequence diagram depicts the message flow for a remote signing process with a focus on the messages flowing from the RP to IDP or QTSP and back.

We will go through the details of these messages in the following!

qes_flow_puml_rp_view

You need to implement the following steps to create signatures with the identity service:

  1. Document Preparation: First, you have to prepare the document(s) that the user is about to sign. Once the document is ready for signing, you need to show them to the user and calculate the hash(es) of the document(s).

  2. OP (Bank) Selection: Then, just as in the identity flow and as explained in the base document, you send the user to the account chooser to select a bank. You will receive an issuer URL from that process. The issuer URL identifies the user’s bank.

  3. Service Configuration Retrieval: You then contact the Service Configuration Service to obtain endpoint URLs for the involved QTSP (Qualified Trust Service Provider) that will generate the signature on the user’s behalf.

  4. Authorization Process: Afterwards, you send the user to the previously selected bank to authorize the signature using OAuth 2.0. You do not pass the document(s) to the bank, but just the hash(es). When the user consents to sign the document(s), you will receive an access token from the bank.

  5. Signature Creation: You then use this access token to create and retrieve the signatures at the QTSP’s API.

  6. Document Finalization Phase: You finally add the received signature(s) to the documents and present them to the user.

Those steps are described in detail in the rest of this document.

1.1. Technologies Used

OAuth 2.0 (grant type authorization code) is the technical foundation of the protocol. It is combined with further OAuth extensions, such as PKCE and OAuth 2.0 Mutual TLS Client Authentication and Certificate Bound Access Tokens, to achieve an appropriate protection level.

The API for creating electronic signatures is provided by a Qualified Trust Service Provider (QTSP). Those remote signatures are based on on-demand, short-term certificates created by the QTSP using data asserted by the IDP. This API is based on ETSI TS 119 432.

Most parameter names and JSON keys used in this flow use snake_case notation (as usual in the ecosystem). Some parameters, however, are taken from or aligned with other specifications by external entities and use other notations, such as camelCase.

1.2. QES, QESID, and QID

Besides signing just signing PDF or text files, you can use this service for other use cases as well:

  • QESID: You can ask the QTSP to embed user identity information into the short-lived certificate that is created to sign the document. Some user information like the user’s name is always included in the certificate, but using QESID you can request that a certain set of data is included on top. This is useful if you need to identify the user using the qualified electronic signature.

  • QID: Some regulated use cases require an electronic signature, but don’t require that a specific document is signed. With QID, you can request that a simple pre-defined text string, a default document is signed instead of a custom document. This is cheaper for you and often times easier to handle, since you don’t need to process a PDF file and signature.

Table 1. Overview: QES Product Variants

Product

Documents to sign

ID claims in certificate

Pricing

Standard QES

Custom PDF or text documents

Base set by QTSP

Standard

QESID

Custom PDF or text documents

Base set + requested ID data

Standard plus any additional claims

QID

Pre-defined default text documents

Base set + requested ID data

Reduced plus any additional claims

As always, please refer to your contract for pricing information.

On a technical level, you can decide for each request if you want the user to sign a custom PDF or text document or a default document (QES/QESID vs. QID) and if you want to include additional user claims. You can also mix default documents with custom documents in the same flow.

The following table shows the additional user claims ("identity assurance claims") that are defined and the location where the data will end up in the X.509 signer certificate:

Claim name X.509 container X.509 attribute name OID

family_name

SubjectDN

surName

2.5.4.4

given_name

SubjectDN

givenName

2.5.4.42

address

SubjectDN

countryName

postalCode

streetAddress

2.5.4.6

2.5.4.17

2.5.4.9

birthdate

Subject Directory Attributes

dateOfBirth

1.3.6.1.5.5.7.9.1

place_of_birth

Subject Directory Attributes

placeOfBirth

1.3.6.1.5.5.7.9.2

nationalities

Subject Directory Attributes

countryOfCitizenship

1.3.6.1.5.5.7.9.4

2. Document Preparation

Document Handling

This service can create signatures according to CAdES and PAdES as defined in ETSI EN 319 122 and ETSI EN 319 142. Before starting, please make sure that you know which type of signature you need, and, in particular for PDF documents, how to prepare a document for signing and how to embed the finished signature into the document.

2.1. Custom Documents

In case of a QES or QESID, you present the user one or more documents (e.g., a contract proposal PDF document) and ask whether the user accepts and wants to sign the document(s) electronically.

You are contractually bound to show all custom document(s) the user is about to sign in their original form to the user and to use only these documents in the following.

2.2. Default Documents

In case of QID, you take one of the default documents for the selected QTSP from the service configuration, described below. This default document must not be modified and must be handled like a text document in the following. The AS will show the text of the document to the user and ask the user if they want to sign this text.

Default documents are available in various languages. You can select the language when selecting the default document from the service configuration, as explained later.

2.3. Hashing Documents

You need to calculate the hash(es) of the PDF or text document(s). To this end, you take the raw bytes of the document, hash them using one of the hash algorithms listed in the following, and encode the hash bytes in Base64, as defined in RFC4648, Section 4.

Any changes to the document need to be made before the hashing. In particular, embedding the signature into a PDF may require that the structure of the document is prepared beforehand to define a placeholder for the signature.

The hash algorithm SHA-256 is supported by all QTSPs, the use of other hash algorithms depends on the QTSP. The following algorithms are defined (based on ETSI TS 119 432):

Hash algorithm OID

Hash algorithm name

2.16.840.1.101.3.4.2.1

SHA-256

2.16.840.1.101.3.4.2.2

SHA-384

2.16.840.1.101.3.4.2.3

SHA-512

2.16.840.1.101.3.4.2.8

SHA3-256

2.16.840.1.101.3.4.2.9

SHA3-384

2.16.840.1.101.3.4.2.10

SHA3-512

Example 1. Encoding Documents

For a text document, the following python code produces the hash:

from base64 import b64encode
from hashlib import sha256

document = "Hiermit bestätige ich, dass ich die angegebenen Daten sorgfältig geprüft habe und diese auf meine Person zutreffen."
document_bytes = document.encode("utf-8")

hash = b64encode(sha256(document_bytes).digest()).decode("ascii")

This example outputs hhZiIQste6V0dvTsil1RMY07EIMgPEHFLQE2GvebWf4=.

Instead of document_bytes, the raw bytes from a prepared PDF file can be used as well.

The raw bytes produced by the hashes must not be Hex encoded, but Base64 encoded! If your hash library outputs Hex encoded strings, you must request 'raw' output or decode the hex representation to bytes first and then re-encode using Base64.
This online tool can demonstrate the calculation of hashes suitable for the signing service. Obviously, this should not be used in production.
Example 2. Proper Encoding

For the same text as above, compare the following outputs:

1. valid: MJPleish2cowx7NHhBpzniGG/SlYb61qXhpTTZWiDr70Gyifuu7708aqmITBrnYlAPsAdMGyAjuA8ZYNhvN8sw== (1)
2. wrong: 3093e57a2b21d9ca30c7b347841a739e2186fd29586fad6a5e1a534d95a20ebef41b289fbaeefbd3c6aa9884c1ae762500fb0074c1b2023b80f1960d86f37cb3 (2)
3. wrong: MJPleish2cowx7NHhBpzniGG_SlYb61qXhpTTZWiDr70Gyifuu7708aqmITBrnYlAPsAdMGyAjuA8ZYNhvN8sw (3)
4. wrong: MzA5M2U1N2EyYjIxZDljYTMwYzdiMzQ3ODQxYTczOWUyMTg2ZmQyOTU4NmZhZDZhNWUxYTUzNGQ5NWEyMGViZWY0MWIyODlmYmFlZWZiZDNjNmFhOTg4NGMxYWU3NjI1MDBmYjAwNzRjMWIyMDIzYjgwZjE5NjBkODZmMzdjYjM= (4)
1 This is a valid hash using SHA-512.
2 This is not properly encoded (it is hex encoded).
3 This is not properly encoded (it uses Base64 URL-safe).
4 This is not properly encoded (the hex string was encoded, not the raw bytes). Note that it is much longer than the correct hash above, and it uses just a limited set of characters. Decoding this hash shows the original hex string, not the bytes of the hash.

You need to store the documents, the hashes, and the hash algorithm OIDs for later use, securely bound to the user’s browser session.

At this point, you must show a button to the user (as explained in the base document) to start the signature process.

2.4. OP (Bank) Selection

The next step, just as in other flows, is to send the user through the OP (Bank) selection at the account chooser. Please refer to the base document for details.

2.5. Service Configuration Retrieval

As a result from the previous step, you will be provided with the Issuer URL of the chosen Bank. You now need to obtain the service configuration from the Service Configuration service.

This part MUST take place in the backend, not in the user’s browser.

To do that, send an HTTP GET request from your backend to the service configuration service URL, attaching the issuer URL in the iss parameter.

For the sandbox, the URL is as follows (here shown with an example issuer): https://api.sandbox.openbanking.verimi.cloud/service-configuration/v1/?iss=https://testidp.sandbox.openbanking.verimi.cloud/issuer/10000001

For production, the URL is the following: https://api.openbanking.verimi.de/service-configuration/v1/?iss={issuer_url}

As with all URL parameters, make sure to properly URL-encode the iss parameter.
Example 3. Service Configuration Request
GET /service-configuration/v1/?iss=https://
   testidp.sandbox.openbanking.verimi.cloud/issuer/10000001 HTTP/2
Host: api.sandbox.openbanking.verimi.cloud
Accept: */*

The API returns an HTTP status code and the service configuration document.

  • If the issuer is found and active, the returned status code is 200 (found).

  • If the issuer is not a valid URI, the returned status code is 400 (bad request).

  • If the issuer is not found, the returned status code is 404 (not found).

  • If the issuer is found but not active, the returned status code is 423 (locked).

If an error is indicated, a JSON document explaining the error is contained in the response body, e.g.: {"error":"invalid_request","error_description":"The issuer 'https://accounts.inactivetestbank.de' is not active"}

When a service configuration document is returned, it is of the following form:

Example 4. Service Configuration with Remote Signature Creation endpoints
{
    "identity": {
        "iss": "https://testidp.sandbox.openbanking.verimi.cloud/issuer/10000001" (1)
    },
    "remote_signature_creation": [ (2)
        {
            "qtsp_id": "sp:sandbox.yes.com:811b0c29-4556-05e9-ad2f-17248ad946d9", (3)
            "signDoc": "https://example.com/signDoc", (4)
            "conformance_levels_supported": [ (5)
                "AdES-B-B",
                "AdES-B-T"
            ],
            "identity_assurance_claims_supported": [ (6)
                "place_of_birth",
                "birthdate",
                "address",
                "nationalities",
                "given_name",
                "family_name"
            ],
            "default_signing_documents": [ (7)
                {
                    "lang": "de",
                    "text": "Hiermit bestätige ich, dass ich die angegebenen Daten sorgfältig geprüft habe und diese auf meine Person zutreffen."
                },
                {
                    "lang": "en",
                    "text": "I hereby confirm that I have carefully checked the data provided and that they apply to my person."
                }
            ]
        },
        {
            "qtsp_id": "sp:sandbox.yes.com:f38d2a84-48e8-26bf-bc6d-378a090b09ca",
            "signDoc": "https://other.example/signDoc",
            "conformance_levels_supported": [
                "AdES-B-B",
                "AdES-B-T"
            ],
            "identity_assurance_claims_supported": [
                "birthdate",
                "nationalities",
                "given_name",
                "family_name"
            ],
            "default_signing_documents": [
                {
                    "lang": "de",
                    "text": "Hiermit bestätige ich, dass ich die angegebenen Daten sorgfältig geprüft habe und diese auf meine Person zutreffen."
                },
                {
                    "lang": "it",
                    "text": "Confermo di aver controllato attentamente i dati forniti e che si riferiscono alla mia persona."
                }
            ]
        }
    ],
    (...)
}
1 This element indicates the OAuth issuer URL that is to be used in the following. Note that this can be different from the issuer URL used so far.
2 The section designated remote_signature_creation contains the one or more sets of configuration information for different QTSPs that can be used with this issuer (bank). It is up to you to select one of the QTSPs.
3 The qtsp_id is a unique identifier for the QTSP.
4 signDoc is the endpoint where you will be able to retrieve the signature from.
5 conformance_levels_supported indicates the signature conformance levels supported by the specific QTSP (see ETSI EN 319 142).
6 identity_assurance_claims_supported indicates the identity assurance claims supported by the QTSP (cf. QESID and QID described above)
7 default_signing_documents is a list of default signing documents in different languages defined by the QTSP. If you want to use QID, you need to select one of these documents.

You need to select one of the QTSPs, store its configuration, and store the OAuth issuer URL obtained from the identity element.

If the section remote_signature_creation is missing or empty, this bank does not support the signing service. You must test for this and guide the user to select a different bank or signing method.

3. Authorization Process

Using OAuth Libraries

Using an existing OAuth library is the best way to implement the following steps. You save a lot of implementation work and get battle-proven, secure code.

Before deciding for a library, we recommend to check for the following features:

  • Does the library support Pushed Authorization Requests?

  • Does the library support RFC8414: OAuth Authorization Server Metadata?

  • Does the library support PKCE?

  • Ideally: Does the library support Rich Authorization Requests?

  • Can the issuer URI be configured during run-time?
    (Some implementations only support one or a number of fixed issuer URIs, while the issuer URI in the identity service is determined dynamically, as described before.)

  • Does the library support TLS Client Authentication using a self-signed certificate at the Token Endpoint as defined in RFC8705?

The list of OpenID-certified client libraries is a good starting point to find suitable implementations.

3.1. Retrieving the OAuth Configuration (Authorization Server Metadata)

First, you (or your OAuth library) need to retrieve the OAuth Configuration, or Authorization Server Metadata document. This document will tell you about the endpoints that you need to use in the following.

The URL of this document is created as follows:

{oauth_issuer_url}/.well-known/oauth-authorization-server

Recall that from here on, only the issuer URL retrieved in the last step is used, not the one returned from the authorization endpoint.
Example 5. OAuth Configuration (Authorization Server Metdata)

The OAuth Configuration document contains, among others, the following keys:

{
   "issuer": "https://testidp.sandbox.openbanking.verimi.cloud/issuer/10000003",
   "pushed_authorization_request_endpoint": "https://testidp.sandbox.openbanking.verimi.cloud/issuer/10000003/par",
   "authorization_endpoint": "https://testidp.sandbox.openbanking.verimi.cloud/services/authz/10000003",
   "token_endpoint": "https://testidp.sandbox.openbanking.verimi.cloud/issuer/10000003/token",
   ...
}
You need to check that the issuer in the configuration document matches the issuer URI which you received from the service configuration!

The pushed authorization request endpoint, authorization endpoint, and token endpoint URLs are used in the following.

3.2. Pushed Authorization Request

The authorization request is sent as a Pushed Authorization Requests to the authorization server. That means that most of the parameters usually sent in the OAuth authorization request are sent in an MTLS-protected POST request to the backend of the authorization server instead.

This ensures that the data contained in the authorization request remains confidential and cannot be altered by the user or an attacker.

The parameters in the pushed authorization request are shown in the following:

Example 6. Pushed Authorization Request

Note that you MUST use your TLS certificate to authenticate at the authorization server for this request.

POST /par HTTP/1.1
Host: as.example-bank.com
Content-Type: application/x-www-form-urlencoded

response_type=code (1)
&client_id=s6BhdRkqt3 (2)
&state=af0ifjsldkj (3)
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM (4)
&code_challenge_method=S256 (5)
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb (6)
&purpose=Kaufvertrag (7)
&authorization_details=%5B%7B%22type%22:%22sign%22,%22locations (8)
      %22:%5B%22sp:yes.com:6a256bca-1e0b-4b0c-84fe-c9f78e0cb4a3
      %22%5D,%22credentialID%22:%22qes_eidas%22,%22hashAlgorith
      mOID%22:%222.16.840.1.101.3.4.2.1%22%22documentDigests%22
      :%5B%7B%22hash%22:%22sTOgwOm+474gFj0q0x1iSNspKqbcse4Ieiql
      Dg/HWuI=%22,%22label%22:%22Kreditvertrag%22%7D,%7B%22hash
      %22:%22HZQzZmMAIWekfGH0/ZKW1nsdt0xg3H6bZYztgsMTLw0=%22,%2
      2label%22:%22VertragRestschuldversicherung%22%7D,%7B%22ha
      sh%22:%22hhZiIQste6V0dvTsil1RMY07EIMgPEHFLQE2GvebWf4=%22,
      %22label%22:%22%22%7D%5D%7D%5D
1 response_type MUST be code.
2 client_id is your client ID.
3 state is an optional value to carry session state. Note that because PKCE or nonce are used, state is not needed for protection against Cross-Site Reqest Frogery and is therefore optional.
4 code_challenge is the PKCE code challenge. Note the rules in PKCE, Section 4.1. You may additionally use the nonce parameter if this flow is used together with OpenID Connect.
5 code_challenge_method must be S256
6 redirect_uri MUST be one of your pre-registered redirect URIs.
7 purpose is an optional parameter to inform the user about the purpose of the transaction.
8 authorization_details: see below.

3.2.1. Authorization Details

The authorization_details parameter is an array of objects as described in Rich Authorization Requests. Each object has a type. The array MUST NOT contain more than one object of type sign, but may contain other objects, e.g., to combine the signature with a payment, see Advanced Use Cases - Combined Flows.

Example 7. Signature Creation Information in authorization_details
[
   {
      "type": "sign", (1)
      "locations": [ (2)
         "sp:yes.com:6a256bca-1e0b-4b0c-84fe-c9f78e0cb4a3"
      ],
      "credentialID": "qes_eidas", (3)
      "hashAlgorithmOID": "2.16.840.1.101.3.4.2.1", (4)
      "documentDigests": [  (5)
         {
            "hash": "sTOgwOm+474gFj0q0x1iSNspKqbcse4IeiqlDg/HWuI=",
            "label": "Kreditvertrag"
         },
         {
            "hash": "HZQzZmMAIWekfGH0/ZKW1nsdt0xg3H6bZYztgsMTLw0=",
            "label": "Vertrag Restschuldversicherung"
         },
         {
            "hash": "hhZiIQste6V0dvTsil1RMY07EIMgPEHFLQE2GvebWf4=",
            "label": "" (6)
         }
      ],
      "identity_assurance_claims": { (7)
        "given_name": null,
        "family_name": null,
        "birthdate": null,
        "place_of_birth": {
           "essential": true
        },
        "nationalities": null,
        "address": null
      }
   }
]
1 type is always sign.
2 locations is an array with exactly one element: the qtsp_id selected previously.
3 credentialID determined the signature to be created; currently only qes_eidas is available.
4 hashAlgorithmOID is the OID for the hash algorithm that you used to hash the documents, as shown above.
5 documentDigests is an array containing an object for each document to be signed. Each document object must contain the document hash and a user-friendly, descriptive label. The authorization server will show the label to the user.
6 For default documents (cf. QID), the label MUST remain empty!
7 Optional: For QESID and QID, you may list identity assurance claims here. null means that the element is requested on a best-effort basis (default). Marking a claims as {"essential":true} means that the transaction should be aborted when data for the claim is not available.

3.2.2. Authorization Server Response

The authorization server will respond with a JSON object containing a request_uri parameter representing the newly created pushed authorization request information:

Example 8. Authorization Server Response with request_uri
HTTP/1.1 201 Created
Cache-Control: no-cache, no-store
Content-Type: application/json

{
   "request_uri": "urn:example:bwc4JK-ESC0w8acc191e-Y1LTC2",
   "expires_in": 90
}

The request_uri serves as an identifier that will be used in the next step, the authorization request.

3.2.3. Error Response

In case of an error the response will be structured as described in Pushed Authorization Requests and shown in the following example:

Example 9. Error Response
HTTP/1.x 400 Bad Request

Content-Type: application/json

{
    "error": "invalid_request",
    "error_description": "the field credentialID must not be empty"
}

Possible errors:

HTTP status code

error

error_description (examples)

400

invalid_request

the field credentialID must not be empty

400

invalid_request

the field locations contains an unknown id

400

invalid_request

the field locations contains more than one entry

400

invalid_request

the field locations contains a service whose service_type is not "remote_signature_creation"

415

invalid_request

wrong media type

422

invalid_request

request input validation failed

3.3. Authorization Request

In the next step, you need to redirect the user’s browser to the authorization endpoint of the bank, passing the request_uri and client_id parameters.

Example 10. Redirection to Authorization Endpoint

Usually, the redirection is done via a Location header and an HTTP status code 302:

HTTP/1.1 302 Found
Location: https://testidp.sandbox.openbanking.verimi.cloud/services/authz/10000003?
      request_uri=urn%3Aexample%3Abwc4JK-ESC0w8acc191e-Y1LTC2&
      client_id=sandbox.yes.com:6a256bca-1e0b-4b0c-84fe-c9f78e0cb4a3`
As always, remember to encode URI parameters properly. Also, the authorization endpoint URL retrieved from the OAuth configuration may already contain parameters that must not be removed. Please make sure that your code or the library used supports parameters in endpoint URIs so that you don’t run into problems because of parameters being cut off or a duplicate ? being used resulting in an invalid URI.

Screen control is with the OP now which will guide your user through login and consent. The OP requires information about your app in the course of this flow. You therefore have to provide a privacy policy and may provide an optional terms of service document and label upon registration.

3.4. Authentication Response

After successful authentication, authorization, and consent, the OP will redirect (HTTP status code 302) your user to the {redirect_uri} given in the Authentication Request including the following request parameters:

  • state: if you provided a state value in the pushed authorization request, you MUST check that this value is equal to the state parameter passed to the OP.

  • code: the authorization code you need to get the access token

  • iss: the issuer URL of the OP that created this response. You app MUST check that this value is equal to the issuer URL used in the previous steps. This is a countermeasure against mix-up attacks.

The state value must now be invalidated; i.e., double usage of the same state value MUST result in an error.
The check and the token endpoint request MUST be implemented in the backend logic of the application as this does not expose the results to client side attacks and allows to use MTLS to authenticate the client.
Example 11. Authentication Response
GET /yes/oidccb?code=rDx7qadXAgCrkTGxF7WjrA.gs8gNEgMhH6Ww-VBZbz04w&
        iss=https://testidp.sandbox.openbanking.verimi.cloud/issuer/10000001&state=V1F
        4gV3fDx6y110UzQJpk HTTP/1.1
Host: rpbackend.acme.com

3.5. Authentication Error Response

Errors according to OAuth 2.0 Authorization Error Response and OAuth 2.0 Error Response may occur and must be handled. In case of an error the parameter error will deliver an error code with optional details in the parameter error_description.

There is a identity service specific error code account_selection_requested which is returned by the OP if the user clicks 'Select another bank' during the online banking login and shall cause a forced bank selection in the account chooser. You MUST handle this error as described here.

The error unable_to_meet_service_requirements is sent in case the user’s data is not sufficient to create the signature.

3.6. Token Request

If your app received a successful authentication response, your backend at {redirect_uri} needs to exchange the authorization code for an access token at the token_endpoint . The token request is a POST request with content type application/x-www-form-urlencoded and the following parameters:

  • grant_type: must be set to authorization_code.

  • code: authorization code returned in the authorization response.

  • client_id: your client_id.

  • redirect_uri: same redirect_uri as used in the authorization request. This is a security countermeasure against client impersonation.

  • code_verifier: the PKCE code verifier.

Your RP backend has to perform client authentication with the OP via mutual TLS (aka. TLS client authentication): Your RP has to be configured so that it uses the self-signed certificate with which you registered your RP for the TLS handshake with the OP.
Example 12. Token Request
POST testidp.sandbox.openbanking.verimi.cloud/issuer/10000001/token
Accept: application/json
Accept-encoding: gzip, deflate
Content-type: application/x-www-form-urlencoded
Content-length: 261
Host: testidp.sandbox.openbanking.verimi.cloud

grant_type=authorization_code&code=rDx7qadXAgCrkTGxF7WjrA.gs8gNEgMhH6W
w-VBZbz04w&redirect_uri=http%3A%2F%2Frpbackend.acme.com%2Fyes%2Foidccb
&client_id=sandbox.yes.com%3Ae85ff3bc-96f8-4ae7-b6b1-894d8dde9ebe&code
_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

3.7. Token Response

The IDP will respond with a JSON object, i.e., the response will contain the header Content-Type: application/json;charset=UTF-8.

The JSON object has the following attributes (and may have others)

  • access_token: the access token

  • token_type: always set to bearer

  • expires_in: seconds until expiration (optional)

  • authorization_details: the authorization details object(s) as in the authorization request

Example 13. Token Response
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8

{
   "access_token":"eyJraWQiOiJDWHVwIiwiYWxnI...",
   "token_type":"bearer",
   "expires_in":3600,
   "authorization_details": [ ... ]
}

3.8. Token Error Response

Errors according to OAuth 2.0 Error Response, or OIDC 1.0 Token Error Response may occur and must be handled. In case of an error the returned JSON object will normally contain an error attribute.

4. Creating the Signature

After obtaining the access token, you can request the actual signature from the QTSP. Before this request, you should ask the user one final time for a confirmation of the signature, as this is the "point of no return" for the user.

Example 14. Example Button
Sign document now?

4.1. Signature Request

The signature request is authenticated using mutual TLS and is a POST request containing a JSON body. The contents of the request follow the definition given in ETSI TS 119 432, as can be seen in the following annotated example:

Example 15. Example Signing Request
POST 5634/csc/v1/signatures/signDoc
Content-Type: application/json
Host: qtsp.com

{
   "SAD":"eyJraWQiOiJDWHVwIiwiYWxnI...", (1)
   "credentialID":"qes_eidas", (2)
   "documentDigests":{ (3)
      "hashes":[
         "sTOgwOm+474gFj0q0x1iSNspKqbcse4IeiqlDg/HWuI=",
         "HZQzZmMAIWekfGH0/ZKW1nsdt0xg3H6bZYztgsMTLw0="
      ],
      "hashAlgorithmOID":"2.16.840.1.101.3.4.2.1"
   },
   "profile":"http://uri.etsi.org/19432/v1.1.1#/creationprofile#", (4)
   "signature_format":"P", (5)
   "conformance_level":"AdES-B-T" (6)
}
1 SAD: The request authorization data. Here: the access token obtained in the previous steps.
2 credentialID: Denotes the kind of signature the RP wants the QTSP to create. MUST be qes_eidas (only supported value currently). MUST match the credentialID value as specified in the corresponding consent resource.
3 documentDigests is an object encapsulating an array of Base64-encoded hash values (hashes) and the OID of the hash algorithm (hashAlgorithmOID) used to generate the respective hash values, as above. The hashes array contains one hash value per document to be signed and MUST NOT be empty. This MUST be a subset of the hashes in the consent resource.
4 profile: String that identifies the protocol being used by the client to communicate to the signature creation endpoint. The value MUST be http://uri.etsi.org/19432/v1.1.1#/creationprofile#.
5 signature_format: See below.
6 conformance_level: See below, optional.

Additionally, the optional key signAlgo may be used to specify the OID of the signing algorithm to use. Some algorithms require additional parameters, which can be specified in the key signAlgoParams.

Creating the signature can take same time. ⌛ Please ensure that your HTTP library waits at least 60 seconds until a timeout is triggered. Some HTTP libraries have a default of 15 seconds. A loading/waiting indicator should be shown to the user in the meantime.

4.1.1. Signature Format and Conformance Level

The signature formats and corresponding conformance levels relate to ETSI EN 319 122 and ETSI EN 319 142.

signature_format determines the required signature format, possible values are:

  • C shall be used to request the creation of a CAdES signature;

  • P shall be used to request the creation of a PAdES signature.

conformance_level determines the required baseline level format:

  • AdES-B-B shall be used to request the creation of a baseline level B signature;

  • AdES-B-T shall be used to request the creation of a baseline level T signature;

  • AdES-B-LT shall be used to request the creation of a baseline level LT signature.

The parameter conformance_level is optional. The values supported by the QTSP are returned in the service configuration (see above) in the section conformance_levels_supported. The default baseline level according to ETSI EN 319 142 is AdES-B-B in case it is omitted.

If a timestamp is needed, its request and inclusion is managed by the signature creation service according to its configuration and policies. Since the identity service utilizes on-demand, short-lived certificates for signature creation, you should request signature creation using a conformance level that includes a timestamp into the signature, e.g., AdES-B-T. This ensures successful signature validation even after certificate expiration.

4.2. Signature Response

If an error occurs while processing the request, the QTSP returns an HTTP status code 400 along with error information as specified in ETSI TS 119 432, Section 7.24.2.

Otherwise, the QTSP returns a document as follows:

Example 16. Signature Response
HTTP/1.1 200 OK +
{
   "SignatureObject":[ (1)
      "KedJuTob5gtvYx9qM3k3gm7kbLBwV…bEQRl26S2tmXjqNND7MRGtoew==",
      "AedJuTob5gtvYx9qM3k3gm7kbLBwV…bEQRl26S2tmXjqNND7MRGtoes=="
   ],
   "revocationInfo":{ (2)
      "ocsp":[
         "MIIJg...jSc="
      ],
      "crl":[
         "MIIC4...X7M="
      ]
   }
}
1 SignatureObject is an array containing Base64-encoded signature elements detached from the documents (see below). If multiple hash values are signed in a single request, the order of the elements in the SignatureObject array follows the order of the entries in the documentDigests parameter of the request.
2 revocationInfo is a JSON Object containing revocation data that MUST be included in the signing response in case of signature_format P and conformance_level AdES-B-LT (see below).

4.2.1. Signature Elements

The structure of every signature element is built in accordance with RFC 5652.

In case the request parameter signature_format was set to C, every object conforms to ETSI EN 319 122.

In case signature_format was P, every object conforms to ETSI EN 319 142. The result can be embedded in a PDF signature object according to ETSI EN 319 142.

4.2.2. Revocation Information

The element revocationInfo is composed of the following sub-elements:

  • ocsp: array of base64 encoded strings containing the DER-encoded ASN.1 data structures of type OCSPResponse according to RFC 6960.

  • crl: array of base64 encoded strings containing the DER-encoded ASN.1 data structures of type CertificateList according to RFC 5280.

4.3. Document Finalization

You can now add the signature data to the signed document and present the signed document to the user.