# QWAC vs QSealC : qui fait quoi, et pourquoi vous vous trompez de couche

> Le QWAC est lu par la couche TLS, une fois, au handshake. Le QSealC est lu par la couche applicative, à chaque requête. Où vit chaque clé privée, ce que STET exige, et comment lire une « erreur de certificat ».

_Publié le: 2026-08-24 · Category: sécurité · Tags: #dsp2 #psd2 #open-banking #sécurité #eidas #mtls #stet_

---

Les deux certificats sortent du même [eIDAS TSP](https://openfinanceguide.com/fr/glossary/eidas-tsp), portent le même numéro d'agrément et les mêmes rôles [DSP2](https://openfinanceguide.com/fr/glossary/dsp2), et arrivent souvent sur le même bon de commande. La différence n'est pas dans ce qu'ils contiennent. Elle est dans **la couche de la pile qui les lit**. Le [QWAC](https://openfinanceguide.com/fr/glossary/qwac) est lu par la couche TLS, une fois, au handshake. Le [QSealC](https://openfinanceguide.com/fr/glossary/qsealc) est lu par la couche applicative, à chaque requête, dans un en-tête HTTP.

Un ticket qui dit « erreur de certificat » sans dire à quelle couche n'est pas diagnosticable. Cet article ne redéfinit pas les deux objets — les fiches QWAC, QSealC et [mTLS](https://openfinanceguide.com/fr/glossary/mtls) le font. Il place chacun dans la pile, dans l'infrastructure et dans les logs, avec le texte de [STET](https://openfinanceguide.com/fr/glossary/stet) 1.6.3 en regard. Pour le panorama complet de la stack, lisez d'abord [Architecture technique d'une API DSP2](/fr/blog/architecture-api-dsp2).

## En une image : deux couches, deux certificats

Une requête DSP2 descend la pile. Chaque certificat n'intervient qu'à un étage.

```mermaid
flowchart LR
    subgraph APP["Couche applicative"]
        BIZ["Logique métier du TPP\nGET /accounts\nPOST /payment-requests"]
    end
    subgraph MSG["Couche message HTTP — à chaque requête"]
        SIG["Digest + Signature\nou x-jws-signature\n🔏 clé privée du QSealC"]
        TOK["Authorization: Bearer …\ntoken lié au certificat\nclient (RFC 8705)"]
    end
    subgraph TLS["Couche transport — par connexion"]
        MT["Handshake mTLS, TLS 1.2\n🪪 QWAC du TPP\n⇄ QWAC de l'ASPSP"]
    end
    APP --> MSG --> TLS -->|"Internet"| ASPSP["ASPSP"]
```

| | QWAC | QSealC |
|---|---|---|
| Base eIDAS | certificat qualifié d'authentification de site web, art. 3(39) | certificat qualifié de cachet électronique, art. 3(30) |
| Couche | transport (TLS) | message (HTTP) |
| Lu par | la pile TLS des deux côtés, au handshake | le vérificateur de signature de l'[ASPSP](https://openfinanceguide.com/fr/glossary/aspsp), à chaque requête |
| Fréquence | une fois par connexion | une fois par requête — et par réponse si l'ASPSP signe |
| Survit à un proxy qui termine TLS | non | oui |
| Valeur de preuve après coup | aucune : rien n'est conservé | la signature reste dans la requête, vérifiable plus tard |
| Clé privée, côté [TPP](https://openfinanceguide.com/fr/glossary/tpp) | là où la connexion sortante est ouverte | là où la requête est construite |
| Symptôme quand ça casse | handshake rompu, aucun code HTTP | HTTP 400 (STET §3.5) |
| Ce que STET en dit | §3.1, §3.2, §3.9 | §3.5 |

> **Info:**
> La RTS (UE) 2018/389, art. 34, autorise l'un ou l'autre pour l'identification. Les
>   standards d'API ont tranché en les empilant : STET et Berlin Group exigent le QWAC
>   pour le canal et le QSealC pour la signature. Deux certificats, donc deux clés
>   privées, deux dates d'expiration et deux procédures de rotation.

## Ce que STET exige, texte à l'appui

Trois passages du Framework 1.6.3 (Part 1) suffisent. Les pages sont celles du PDF officiel, que le manuel affiche en marge de chaque paragraphe.

### §3.1 et §3.2 — le QWAC, pour TLS et rien d'autre

« Each actor must be provided with at least one eIDAS certificate (QWAC), for TLS 1.2 purpose » ([§3.1, p. 17](/fr/stet/1.6.3/framework/3-prerequisites-and-technical-details#s-3.1)). Le TPP vérifie le QWAC serveur de l'ASPSP et présente le sien ; les deux respectent ETSI TS 119 495 ; « in case of authentication failure, on one side or the other, the connection must be closed » ; et « no additional encrypting or authenticating feature is required » ([§3.2, p. 17–18](/fr/stet/1.6.3/framework/3-prerequisites-and-technical-details#s-3.2)). Le QWAC ne remonte jamais dans la requête : tout ce qu'il a à dire, il le dit au handshake.

Le même paragraphe fixe le format de l'`organizationIdentifier` du sujet du certificat : `PSD`, le code pays de la [NCA](https://openfinanceguide.com/fr/glossary/nca), un tiret, l'identifiant de la NCA, un tiret, le numéro d'agrément — `PSDFR-ACPR-12345` pour un acteur agréé par l'[ACPR](https://openfinanceguide.com/fr/glossary/acpr). C'est ce champ, avec les rôles du QC statement ETSI, que l'ASPSP lit dans le certificat client pour savoir qui appelle, et en quelle qualité.

### §3.5 — le QSealC, pour signer chaque requête

« Each request sent by the TPP has to be signed and ASPSP might also sign their responses. If the ASPSP notes that the signature is either absent or invalid for a given request, it shall reject this request with HTTP400 » ([§3.5, p. 53](/fr/stet/1.6.3/framework/3-prerequisites-and-technical-details#s-3.5)).

Le mécanisme historique est [HTTP Signature](https://openfinanceguide.com/fr/glossary/http-signature) dans sa version draft-cavage : un `Digest` SHA-256 du corps, puis une signature RSA-SHA256 faite avec le QSealC sur `(request-target)`, `Date`, `Content-Type`, `Content-Length`, `X-Request-Id`, les en-têtes `PSU-*` et le `Digest` lui-même, transportée dans l'en-tête `Signature` avec `keyId`, `algorithm` et la liste des en-têtes signés ([§3.5.1.1, p. 53–55](/fr/stet/1.6.3/framework/3-prerequisites-and-technical-details#s-3.5.1.1)). Le `keyId` est soit l'identifiant attribué lors de l'enregistrement [OAuth2](https://openfinanceguide.com/fr/glossary/oauth2), soit l'URL du QSealC suffixée d'un `_` et de son empreinte SHA-256 ([§3.5.1.2, p. 55](/fr/stet/1.6.3/framework/3-prerequisites-and-technical-details#s-3.5.1.2)).

Depuis la 1.6.3, le Framework conseille de remplacer ce mécanisme par le profil JSON Web Signature d'Open Banking Europe : un JWS détaché (RFC 7515) dans l'en-tête `x-jws-signature`, dont l'en-tête protégé porte `x5t#S256`, l'empreinte du QSealC signataire, et `sigD` la liste des en-têtes couverts ([§3.5.2, p. 55–56](/fr/stet/1.6.3/framework/3-prerequisites-and-technical-details#s-3.5.2)). Le draft-cavage, lui, a été remplacé côté IETF par la RFC 9421 — que STET ne cite pas comme cible.

### §3.9 — le QWAC revient une troisième fois, dans OAuth2

« Based on MTLS, the identity of the TPP is provided by its eIDAS certificate during OAuth2 procedures », avec renvoi à la RFC 8705 ([§3.9, p. 59](/fr/stet/1.6.3/framework/3-prerequisites-and-technical-details#s-3.9)). Le serveur d'autorisation authentifie donc le client par `tls_client_auth` — le QWAC présenté au handshake du `POST /token` — et peut lier le token émis à ce certificat, par sa claim `cnf` et l'empreinte `x5t#S256`. Un token lié à une empreinte qui n'est plus celle présentée est refusé, même s'il n'a pas expiré.

## Une requête, trois vérifications

```mermaid
sequenceDiagram
    autonumber
    participant TPP as TPP (client HTTP)
    participant TLS as ASPSP — terminaison TLS
    participant AS as ASPSP — serveur d'autorisation
    participant API as ASPSP — API et vérificateur de signature

    TPP->>TLS: ClientHello
    TLS-->>TPP: Certificate (QWAC de l'ASPSP) + CertificateRequest
    TPP->>TLS: Certificate (QWAC du TPP) + CertificateVerify
    alt QWAC inconnu, expiré, révoqué, sans le rôle
        TLS-->>TPP: alert TLS — connexion fermée, aucun code HTTP
    end
    TLS-->>TPP: Finished — canal mTLS établi

    TPP->>AS: POST /token (tls_client_auth, sur le canal mTLS)
    AS-->>TPP: access_token lié au QWAC (cnf.x5t#35;S256)

    TPP->>API: GET /accounts<br/>Authorization: Bearer …<br/>Digest + Signature (QSealC)
    API->>API: token valide, lié à ce certificat client ?
    alt token absent, expiré, lié à un autre certificat
        API-->>TPP: 401
    end
    API->>API: Digest recalculé, Signature vérifiée avec le QSealC du keyId ?
    alt signature absente ou invalide
        API-->>TPP: 400 (STET §3.5)
    end
    API-->>TPP: 200 + corps, signé par l'ASPSP le cas échéant
```

Trois vérifications, trois couches, trois symptômes qui ne se ressemblent pas. L'ordre des deux vérifications applicatives est propre à chaque banque ; le symptôme, lui, ne change pas.

## Où vivent les clés privées : l'erreur de couche

C'est ici qu'on se trompe. Les deux clés privées ne vivent pas au même endroit, parce que les deux certificats ne sont pas lus par le même composant.

### Côté TPP, en sortie

```mermaid
flowchart LR
    subgraph TPP["Infrastructure du TPP"]
        APP["Service applicatif\nconstruit la requête"]
        SIGN["Service de signature\n🔏 clé privée du QSealC (HSM / KMS)"]
        EGRESS["Client HTTP ou proxy sortant\n🪪 clé privée du QWAC\nouvre la connexion mTLS"]
        APP -->|"requête + Digest"| SIGN
        SIGN -->|"requête + Signature"| EGRESS
    end
    EGRESS -->|"mTLS"| BANK["ASPSP"]
```

La clé du QSealC appartient au composant qui **construit la requête**, parce que la signature couvre des en-têtes — `Date`, `Content-Length`, `X-Request-Id` — qui doivent être figés avant l'envoi. La clé du QWAC appartient au composant qui **ouvre la connexion**. Si un proxy sortant, un NAT applicatif ou un service mesh ouvre les connexions à la place de l'application, c'est lui qui présente le QWAC, et l'application n'a pas à en tenir la clé. Mettre la clé du QWAC dans l'application alors que le mesh termine et rouvre TLS donne un handshake sans certificat client : une alerte TLS, et aucun code HTTP.

### Côté ASPSP, en entrée

```mermaid
flowchart LR
    TPP["TPP"] -->|"mTLS"| LB
    subgraph ASPSP["Infrastructure de l'ASPSP"]
        LB["Terminaison TLS — LB, WAF, gateway\ndemande et vérifie le QWAC client\n🪪 clé privée du QWAC serveur"]
        GW["Vérificateur de signature\nrecalcule Digest, résout keyId,\nvérifie avec le QSealC du TPP"]
        AUTHZ["Autorisation\ntoken, consentement, rôle"]
        BE["Backend métier"]
        LB -->|"requête + identité extraite du QWAC\n(en-tête interne, signé)"| GW
        GW --> AUTHZ --> BE
    end
```

Côté banque, le QWAC client s'arrête au composant qui termine TLS. Ce qui passe ensuite est une **identité extraite** — `organizationIdentifier`, rôles, empreinte — transmise dans un en-tête interne que le reste de la chaîne doit pouvoir croire, d'où l'intérêt de le signer ou de ne l'accepter que du terminateur. La signature QSealC, elle, traverse tout : un composant situé n'importe où derrière peut la vérifier, **à condition de voir les octets d'origine**. Une gateway qui re-sérialise le JSON, recalcule `Content-Length` ou normalise `Date` avant le vérificateur produit des 400 sur des requêtes valides.

### Les six confusions de couche

| # | Confusion | Ce qui se passe | Symptôme |
|---|---|---|---|
| 1 | Clé du QWAC sur un composant qui n'ouvre pas la connexion | handshake sans certificat client | alerte TLS, pas de HTTP |
| 2 | QSealC présenté comme certificat client TLS, ou QWAC utilisé pour signer | le `QcType` ETSI n'est pas le bon (`web` pour l'un, `eseal` pour l'autre) ; un vérificateur qui le contrôle refuse | alerte TLS, ou 400 |
| 3 | Signature calculée avant un proxy qui réécrit `Date`, `Content-Length` ou le corps | la signature porte sur d'autres octets que ceux reçus | 400, signature invalide |
| 4 | Vérification faite après une transformation de la requête, côté ASPSP | même cause, en miroir | 400 sur des requêtes valides |
| 5 | QWAC renouvelé sans réémettre les tokens liés | `cnf.x5t#S256` ne correspond plus au certificat présenté | 401 sur un token non expiré |
| 6 | AC du QTSP de la banque absente du magasin de confiance du TPP | le TPP refuse le QWAC serveur de l'ASPSP | `unknown_ca`, connexion fermée par le TPP lui-même |

> **Warning:**
> La confusion 2 est la plus coûteuse, parce qu'elle est invisible tant que la banque
>   ne contrôle pas le type de certificat — et visible d'un coup le jour où elle le fait.
>   Les deux certificats sont souvent vendus ensemble ; ils ne sont pas interchangeables.

## Matrice de diagnostic

| Symptôme | Couche | Certificat en cause | Où regarder |
|---|---|---|---|
| Connexion fermée pendant le handshake, aucun code HTTP ; `handshake_failure`, `bad_certificate`, `certificate_expired`, `unknown_ca` | TLS | QWAC — le vôtre, ou celui de la banque que vous ne reconnaissez pas | logs du terminateur TLS ; `openssl s_client -cert -key` |
| 401 | token | QWAC, via le binding RFC 8705 ; ou simplement un token expiré | réponse du serveur d'autorisation ; `cnf` du token contre l'empreinte du certificat présenté |
| 400 mentionnant la signature (STET §3.5) | message HTTP | QSealC | `Digest` recalculé ; liste des en-têtes signés ; `keyId` résoluble ; octets réellement envoyés |
| 403, ou code métier de la banque | autorisation | aucun des deux | rôles PSD2 du certificat contre l'endpoint appelé ; état du consentement |

```mermaid
flowchart TD
    Q0["« Erreur de certificat »"] --> Q1{"Un code HTTP\nest-il revenu ?"}
    Q1 -->|"Non"| TLSX["Couche TLS → QWAC\nchaîne, révocation, rôle,\nmagasin de confiance, qui ouvre la connexion"]
    Q1 -->|"Oui"| Q2{"Lequel ?"}
    Q2 -->|"400"| SIGX["Couche message → QSealC\nDigest, en-têtes signés, keyId,\noctets réécrits en chemin"]
    Q2 -->|"401"| TOKX["Couche token → binding RFC 8705\nle token est-il lié au certificat présenté ?"]
    Q2 -->|"403 ou autre"| ROLEX["Autorisation → ni l'un ni l'autre\nrôle PSD2, consentement, périmètre"]
```

## Deux horloges

Les deux certificats expirent chacun à leur date, sont révocables chacun de leur côté, et se renouvellent séparément. Trois conséquences :

- **Deux alertes d'expiration**, pas une. Un QWAC valide ne dit rien de l'état du QSealC, et réciproquement.
- **La rotation du QWAC touche les tokens.** L'enregistrement OAuth2 survit si le DN du sujet ne change pas — c'est lui que `tls_client_auth_subject_dn` déclare — mais les access tokens liés à l'ancienne empreinte (RFC 8705) ne passent plus. Réémettre les tokens fait partie de la rotation.
- **La rotation du QSealC touche le `keyId`** — l'URL suffixée de l'empreinte change, ou le `x5t#S256` du profil JWS — et elle ne doit pas retirer l'ancien certificat : les requêtes déjà signées doivent rester vérifiables. C'est tout l'objet de la non-répudiation. On retire la clé privée, on laisse le certificat publié.

```mermaid
gantt
    title Deux certificats, deux calendriers
    dateFormat YYYY-MM-DD
    axisFormat %m/%Y
    section QWAC
    QWAC n                                  :done, q1, 2026-01-01, 2027-01-01
    QWAC n+1                                :active, q2, 2026-11-15, 2027-11-15
    Réémission des tokens liés              :crit, milestone, m1, 2026-11-15, 0d
    section QSealC
    QSealC n                                :done, s1, 2026-04-01, 2027-04-01
    QSealC n+1                              :active, s2, 2027-02-15, 2028-02-15
    Ancien QSealC encore publié             :s3, 2027-04-01, 2027-10-01
```

## Pour résumer

1. **QWAC = couche TLS.** Lu au handshake, par le composant qui ouvre ou termine la connexion. Sa clé vit là, nulle part ailleurs.
2. **QSealC = couche message.** Lu à chaque requête, par un vérificateur qui doit voir les octets d'origine. Sa clé vit là où la requête est construite.
3. **STET** : QWAC pour TLS 1.2 (§3.1–3.2), chaque requête signée avec le QSealC et 400 sinon (§3.5), profil JWS recommandé depuis la 1.6.3 (§3.5.2), identité OAuth2 portée par le QWAC (§3.9, RFC 8705).
4. **Pas de code HTTP → QWAC. 400 → QSealC. 401 → binding du token. 403 → ni l'un ni l'autre.**
5. **Deux horloges** : deux expirations, deux rotations, et l'ancien QSealC reste publié pour vérifier le passé.

Pour le reste de la stack — standards, flux [SCA](https://openfinanceguide.com/fr/glossary/sca), consentement — voir [Architecture technique d'une API DSP2](/fr/blog/architecture-api-dsp2). Les deux mécanismes de signature, octet par octet, et les pannes de handshake les plus fréquentes font l'objet des deux articles suivants. Le texte intégral de la section citée est dans le [manuel STET 1.6.3](/fr/stet/1.6.3/framework/3-prerequisites-and-technical-details), avec le PDF original en regard.

---

Source: https://openfinanceguide.com/fr/blog/qwac-vs-qsealc
