---
jupytext:
  text_representation:
    extension: .md
    format_name: myst
    format_version: 0.13
    jupytext_version: 1.16.0
kernelspec:
  name: python3
  display_name: Python 3
---

# TLS 1.3 en profondeur

**Prérequis :** `linux/15_cryptographie.md` couvre TLS basique : HTTPS, certificats X.509, commandes OpenSSL. Ce chapitre plonge dans le protocole lui-même — comment le handshake fonctionne cryptographiquement, pourquoi TLS 1.3 est supérieur à ses prédécesseurs, et comment configurer TLS correctement en production.

```{code-cell} python
:tags: [hide-input]

import os
import secrets
import struct

import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np
import seaborn as sns
from matplotlib.patches import FancyBboxPatch

from cryptography.hazmat.primitives.asymmetric.ec import (
    ECDH, generate_private_key, SECP256R1
)
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
```

## Handshake TLS 1.3 — un seul aller-retour

TLS 1.3 (RFC 8446, publié en 2018) réduit le handshake à **1 RTT** (*Round Trip Time*), contre 2 en TLS 1.2. Cette optimisation est possible car l'échange de clé et la négociation des paramètres cryptographiques sont fusionnés dans les deux premiers messages.

### Messages du handshake

**1. ClientHello** (client → serveur)

- Versions supportées : `supported_versions` = `TLS 1.3`
- Groupes de courbes supportés : `supported_groups` = `x25519, secp256r1, secp384r1`
- Clés publiques éphémères du client (*key_shares*) pour les groupes préférés
- Suites cryptographiques : `cipher_suites` = `TLS_AES_256_GCM_SHA384, TLS_AES_128_GCM_SHA256, TLS_CHACHA20_POLY1305_SHA256`
- Extensions : `server_name` (SNI), `signature_algorithms`, `alpn`

**2. ServerHello** (serveur → client)

- Suite choisie et groupe ECDHE choisi
- Clé publique éphémère du serveur (*key_share*)
- À ce stade, **les deux parties calculent le `handshake_secret`** via ECDHE + HKDF.
- Tous les messages suivants sont **chiffrés** avec des clés dérivées du `handshake_secret`.

**3. EncryptedExtensions** (serveur → client, chiffré)

- Extensions non liées à la négociation de clé (ALPN sélectionné, max_fragment_length…)

**4. Certificate** (serveur → client, chiffré)

- Certificat X.509 du serveur (chaîne jusqu'à la CA racine de confiance)

**5. CertificateVerify** (serveur → client, chiffré)

- Signature de la transcription du handshake avec la clé privée du serveur
- Prouve que le serveur possède bien la clé privée correspondant au certificat

**6. Finished** (serveur → client, chiffré)

- MAC sur la transcription complète du handshake

**7. Finished** (client → serveur, chiffré)

- MAC du client sur la transcription

Dès l'étape 7, les deux parties dérivent les **clés de session applicative** (`application_traffic_secret`) et la communication chiffrée peut commencer.

```{admonition} Pas de négociation de version
:class: note
En TLS 1.3, le champ `legacy_version` de ClientHello vaut toujours `0x0303` (TLS 1.2) pour la rétrocompatibilité avec les middleboxes. La vraie version est dans l'extension `supported_versions`. Les serveurs TLS 1.3 ignorent `legacy_version`.
```

## Différences TLS 1.2 / TLS 1.3

### Ce qui a été supprimé dans TLS 1.3

| Élément supprimé | Raison |
| --- | --- |
| RSA key exchange statique | Pas de forward secrecy : une fuite de la clé privée déchiffre toutes les sessions passées |
| DHE sans courbes elliptiques | Lent, paramètres historiquement faibles |
| Cipher suites avec RC4 | RC4 cassé (BEAST, invariants de biais) |
| Cipher suites avec 3DES | Vulnérable à SWEET32 (birthday attack sur les blocs de 64 bits) |
| Cipher suites avec MD5 et SHA-1 dans HMAC | Fonctions de hachage faibles |
| Compression TLS | CRIME attack (compression + oracle) |
| Renégociation | Complexité, vecteur d'attaque |
| Cipher suites exportées | Fragilisation intentionnelle des clés à 40 bits (loi américaine des années 90) |

### Suites cryptographiques TLS 1.3

TLS 1.3 ne définit que **5 cipher suites** (toutes sûres) :

- `TLS_AES_128_GCM_SHA256`
- `TLS_AES_256_GCM_SHA384`
- `TLS_CHACHA20_POLY1305_SHA256`
- `TLS_AES_128_CCM_SHA256`
- `TLS_AES_128_CCM_8_SHA256`

Contre plus de 300 suites en TLS 1.2 (dont beaucoup obsolètes).

### 0-RTT (Early Data)

TLS 1.3 introduit le mode **0-RTT** : si le client et le serveur ont déjà communiqué, le client peut envoyer des données applicatives *dès le premier message* du handshake, en utilisant un *resumption secret* issu de la session précédente.

**Risque : attaques de rejeu** (*replay attacks*). Les données 0-RTT ne sont pas protégées contre le rejeu : un attaquant qui capture le message initial peut le rejouer sur le serveur. La règle : **utiliser 0-RTT uniquement pour des requêtes idempotentes** (GET), jamais pour des opérations avec effets de bord (POST d'un virement).

## ECDHE et forward secrecy systématique

TLS 1.3 impose que **toutes les sessions utilisent ECDHE** (*Elliptic Curve Diffie-Hellman Ephemeral*). Le qualificatif *éphémère* est fondamental :

- Une nouvelle paire de clés DH est générée **à chaque handshake**.
- Ces clés sont détruites une fois la session établie.
- Conséquence : si la **clé privée long terme** du serveur (celle du certificat) est compromise, elle ne permet pas de déchiffrer les sessions passées — il aurait fallu aussi compromettre les clés éphémères, qui n'existent plus.

C'est la **perfect forward secrecy** (PFS).

```{code-cell} python
# Simulation ECDHE sur P-256 : échange Diffie-Hellman, dérivation du shared secret

from cryptography.hazmat.primitives.asymmetric.ec import generate_private_key, ECDH, SECP256R1
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes

curve = SECP256R1()

# Côté client : génération de clé éphémère
client_private_key = generate_private_key(curve)
client_public_key  = client_private_key.public_key()

# Côté serveur : génération de clé éphémère
server_private_key = generate_private_key(curve)
server_public_key  = server_private_key.public_key()

# Échange des clés publiques (transmises en clair dans ClientHello/ServerHello)
# Calcul du shared secret (ECDH)
shared_secret_client = client_private_key.exchange(ECDH(), server_public_key)
shared_secret_server = server_private_key.exchange(ECDH(), client_public_key)

print(f"Shared secret (côté client) : {shared_secret_client.hex()}")
print(f"Shared secret (côté serveur): {shared_secret_server.hex()}")
print(f"Identiques : {shared_secret_client == shared_secret_server}")

# Dérivation des clés de session via HKDF (comme dans TLS 1.3)
def derive_session_key(shared_secret: bytes, label: str, length: int = 32) -> bytes:
    hkdf = HKDF(
        algorithm=hashes.SHA256(),
        length=length,
        salt=None,
        info=label.encode(),
    )
    return hkdf.derive(shared_secret)

client_write_key = derive_session_key(shared_secret_client, "tls13 client write key")
server_write_key = derive_session_key(shared_secret_client, "tls13 server write key")

print(f"\nClé d'écriture client (dérivée) : {client_write_key.hex()}")
print(f"Clé d'écriture serveur (dérivée): {server_write_key.hex()}")
```

```{code-cell} python
# Simulation de la forward secrecy
# Démonstration : compromettre la clé long terme ne déchiffre pas les sessions passées

import secrets as sec_module

# Session 1 : clés éphémères générées pour cette session
ephemeral_1_client = generate_private_key(curve)
ephemeral_1_server = generate_private_key(curve)
shared_1 = ephemeral_1_client.exchange(ECDH(), ephemeral_1_server.public_key())
session_key_1 = derive_session_key(shared_1, "session-1")

# Session 2 : nouvelles clés éphémères indépendantes
ephemeral_2_client = generate_private_key(curve)
ephemeral_2_server = generate_private_key(curve)
shared_2 = ephemeral_2_client.exchange(ECDH(), ephemeral_2_server.public_key())
session_key_2 = derive_session_key(shared_2, "session-2")

# Clé long terme du serveur (certificat)
long_term_private = generate_private_key(curve)
long_term_public  = long_term_private.public_key()

# Simulation d'une "fuite" de la clé long terme
leaked_long_term = long_term_private  # l'attaquant l'obtient

# L'attaquant tente de retrouver les clés de session à partir de la clé long terme
# Il ne peut pas : les clés éphémères ont été détruites après chaque session
# La clé long terme ne permet pas de recalculer shared_1 ni shared_2

print("=== Démonstration de la forward secrecy ===")
print(f"Clé session 1  : {session_key_1.hex()[:32]}...")
print(f"Clé session 2  : {session_key_2.hex()[:32]}...")
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
lt_hex = long_term_public.public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo).hex()
print(f"\nClé long terme fuité (clé publique, DER hex) : {lt_hex[:40]}...")
```

```{code-cell} python
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.0)

# Version corrigée de la démonstration forward secrecy

from cryptography.hazmat.primitives.asymmetric.ec import generate_private_key, ECDH, SECP256R1
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes

curve = SECP256R1()

def derive_key(shared_secret: bytes, label: str) -> bytes:
    return HKDF(algorithm=hashes.SHA256(), length=32, salt=None, info=label.encode()).derive(shared_secret)

# Session 1 : clés éphémères
e1c = generate_private_key(curve); e1s = generate_private_key(curve)
session_key_1 = derive_key(e1c.exchange(ECDH(), e1s.public_key()), "session-1")

# Session 2 : clés éphémères différentes (nouvelle session)
e2c = generate_private_key(curve); e2s = generate_private_key(curve)
session_key_2 = derive_key(e2c.exchange(ECDH(), e2s.public_key()), "session-2")

# Clé long terme du serveur (utilisée pour signer le CertificateVerify)
long_term = generate_private_key(curve)

# Après les sessions, les clés éphémères sont "détruites" (hors de portée)
# L'attaquant compromet la clé long terme APRÈS les sessions
# Il ne peut pas recalculer session_key_1 ni session_key_2 car les clés éphémères sont perdues

print("Forward secrecy — Résumé :")
print(f"  Clé session 1 : {session_key_1.hex()[:32]}...")
print(f"  Clé session 2 : {session_key_2.hex()[:32]}...")
print(f"  Les deux sont indépendantes et ne peuvent pas être recalculées")
print(f"  depuis la clé long terme compromise.")
print()
print("  → Même avec la clé privée du certificat serveur,")
print("    un attaquant passif qui a capturé le trafic passé")
print("    ne peut pas déchiffrer les sessions précédentes.")
```

## mTLS — Mutual TLS

Dans TLS standard, seul le **serveur** s'authentifie (via son certificat). Dans **mTLS** (*mutual TLS*), le **client s'authentifie également** avec son propre certificat X.509.

### Handshake mTLS

Après `EncryptedExtensions`, le serveur envoie un message `CertificateRequest` spécifiant les CAs acceptées. Le client répond avec son `Certificate` et `CertificateVerify`.

### Cas d'usage

**Microservices et service mesh :** dans Istio ou Linkerd, les sidecars Envoy gèrent automatiquement le mTLS entre services. Les certificats sont émis par une CA interne (SPIFFE/SPIRE), renouvelés automatiquement toutes les heures. Le développeur n'a rien à faire : le mesh garantit que toute communication inter-service est mutuellement authentifiée.

**API machine-to-machine :** les partenaires bancaires, les PSP, ou les services gouvernementaux utilisent souvent mTLS pour authentifier les systèmes appelants sans jeton OAuth.

```{admonition} mTLS et rotation des certificats
:class: warning
Le principal défi opérationnel de mTLS est la **rotation des certificats clients** : dans un cluster de 1000 pods, chaque pod a son certificat. SPIFFE/SPIRE automatise ce cycle de vie : les certificats expirent en quelques heures et sont renouvelés avant expiration via le workload API.
```

## SNI, ALPN et Virtual Hosting TLS

### SNI — Server Name Indication

Sans SNI, un serveur HTTPS ne peut héberger qu'un seul certificat par adresse IP : au moment du handshake, le client envoie sa requête HTTP chiffrée avant que le serveur ait pu sélectionner le bon certificat.

**SNI** (RFC 6066) résout ce problème en ajoutant une extension `server_name` dans le **ClientHello** (en clair, avant chiffrement) indiquant le hostname demandé. Le serveur sélectionne le certificat correspondant avant de répondre.

**Limitation :** le SNI est visible pour les observateurs réseau passifs. **ESNI** puis **ECH** (*Encrypted Client Hello*, RFC en cours de finalisation) chiffrent le ClientHello entier pour masquer le SNI.

### ALPN — Application-Layer Protocol Negotiation

ALPN (RFC 7301) permet de négocier le protocole applicatif pendant le handshake TLS, évitant un aller-retour supplémentaire. Exemples :

- `h2` → HTTP/2
- `h3` → HTTP/3 (QUIC)
- `http/1.1` → HTTP/1.1
- `acme-tls/1` → ACME challenge TLS-ALPN

## Certificate Transparency

**Certificate Transparency** (CT, RFC 9162) est un mécanisme d'audit public des certificats TLS émis. Les CAs sont tenues de **journaliser** chaque certificat émis dans des logs CT publics avant qu'il ne soit accepté par les navigateurs (depuis Chrome 68, 2018).

**Fonctionnement :**

1. La CA émet un certificat et le soumet à ≥2 logs CT.
2. Chaque log retourne un *Signed Certificate Timestamp* (SCT).
3. Les SCTs sont incorporés dans le certificat (ou envoyés via TLS ou OCSP).
4. Le navigateur vérifie la présence de SCTs valides avant d'accepter le certificat.

**Intérêt :** un certificat frauduleux pour votre domaine (émis par une CA compromise) est visible publiquement dans les logs CT. Des outils comme `crt.sh` permettent de surveiller les émissions pour son domaine.

## Attaques historiques et pourquoi TLS 1.3 les élimine

```{code-cell} python
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.0)

# Heatmap : comparaison TLS 1.0/1.1/1.2/1.3 × propriétés de sécurité

versions = ["TLS 1.0", "TLS 1.1", "TLS 1.2", "TLS 1.3"]
properties = [
    "Forward secrecy\ndisponible",
    "Forward secrecy\nobligatoire",
    "0-RTT disponible",
    "Cipher suites\nsûres uniquement",
    "Handshake 1-RTT",
    "Résistant BEAST",
    "Résistant POODLE",
    "Résistant HEARTBLEED",
    "Résistant DROWN",
    "Résistant CRIME",
]

# Matrice : 1 = oui, 0 = non, 0.5 = partiel
# rows = propriétés, cols = versions
matrix = np.array([
    [0.5, 0.5, 0.5, 1.0],  # Forward secrecy disponible
    [0.0, 0.0, 0.0, 1.0],  # Forward secrecy obligatoire
    [0.0, 0.0, 0.0, 1.0],  # 0-RTT disponible (TLS 1.3)
    [0.0, 0.0, 0.5, 1.0],  # Cipher suites sûres uniquement
    [0.0, 0.0, 0.0, 1.0],  # Handshake 1-RTT
    [0.0, 1.0, 1.0, 1.0],  # Résistant BEAST (patché en 1.1)
    [0.0, 0.0, 0.5, 1.0],  # Résistant POODLE (SSLv3 → TLS fallback)
    [0.0, 0.0, 0.5, 1.0],  # Résistant HEARTBLEED (implem., pas protocole)
    [0.0, 0.0, 0.5, 1.0],  # Résistant DROWN (SSLv2 cross-protocol)
    [0.5, 0.5, 1.0, 1.0],  # Résistant CRIME (compression désactivée)
])

fig, ax = plt.subplots(figsize=(9, 7))
sns.heatmap(
    matrix,
    xticklabels=versions,
    yticklabels=properties,
    annot=True, fmt=".1f",
    cmap="RdYlGn",
    linewidths=0.5,
    linecolor="#ecf0f1",
    vmin=0, vmax=1,
    cbar_kws={"label": "0 = non  /  0.5 = partiel  /  1 = oui"},
    ax=ax
)
ax.set_title("Propriétés de sécurité par version TLS", fontsize=12, fontweight="bold")
ax.set_xlabel("Version TLS", fontsize=10)
ax.set_ylabel("Propriété", fontsize=10)
ax.tick_params(axis="x", rotation=0)
ax.tick_params(axis="y", rotation=0)
plt.savefig("tls_versions_heatmap.png", dpi=120, bbox_inches="tight")
plt.show()
```

### Les attaques historiques en détail

**BEAST (2011, TLS 1.0)**
Exploitation du mode CBC avec IV prédictible en TLS 1.0 : un attaquant actif pouvait décrypter des cookies session. Corrigé en TLS 1.1 (IV aléatoire par enregistrement). TLS 1.3 supprime CBC.

**POODLE (2014, SSLv3)**
*Padding Oracle On Downgraded Legacy Encryption* : exploit du padding CBC de SSLv3. L'attaquant forçait un downgrade de TLS vers SSLv3 via un "dance of the protocols". TLS 1.3 supprime les downgrades via `supported_versions` et le mécanisme de Downgrade Sentinel.

**HEARTBLEED (2014, OpenSSL)**
Pas une faille de protocole mais d'implémentation : le bug dans l'extension Heartbeat d'OpenSSL (CVE-2014-0160) permettait de lire 64 Ko de mémoire du processus serveur — potentiellement la clé privée. TLS 1.3 ne rend pas Heartbleed impossible en théorie (c'est un bug d'implémentation) mais la forward secrecy limite les dégâts : même avec la clé privée extraite, les sessions passées restent protégées.

**DROWN (2016, SSLv2)**
*Decrypting RSA with Obsolete and Weakened eNcryption* : un serveur supportant encore SSLv2 avec le même certificat/clé qu'un serveur TLS exposait ce dernier. Un attaquant pouvait déchiffrer des sessions TLS modernes via des oracles SSLv2. TLS 1.3 supprime le RSA key exchange et impose ECDHE.

```{admonition} Chiffres sur HEARTBLEED
:class: important
Selon les estimations de 2014, environ **17 % des serveurs HTTPS** étaient vulnérables à Heartbleed lors de sa divulgation. La clé privée extraite permettait non seulement de déchiffrer les futures sessions mais aussi — sans forward secrecy — toutes les sessions passées capturées. C'est l'argument le plus fort pour l'adoption de TLS 1.3 avec ECDHE obligatoire.
```

## Configuration sécurisée

### nginx — TLS 1.3 uniquement

```nginx
server {
    listen 443 ssl;
    server_name app.example.com;

    ssl_certificate     /etc/ssl/certs/app.example.com.fullchain.pem;
    ssl_certificate_key /etc/ssl/private/app.example.com.key;

    # TLS 1.3 uniquement (TLS 1.2 uniquement si compatibilité requise)
    ssl_protocols TLSv1.3;

    # Cipher suites TLS 1.3 — nginx les gère automatiquement
    # Pour TLS 1.2 fallback éventuel :
    # ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    # ssl_prefer_server_ciphers off;  # TLS 1.3 : le client choisit

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/ssl/certs/ca-chain.pem;
    resolver 1.1.1.1 8.8.8.8 valid=300s;

    # Session tickets désactivés pour la forward secrecy stricte
    ssl_session_tickets off;
    ssl_session_cache off;

    # HSTS
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

    # Autres en-têtes de sécurité
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;
}
```

### Vérification avec openssl s_client

```bash
# Tester TLS 1.3
openssl s_client -connect app.example.com:443 -tls1_3 < /dev/null

# Vérifier le certificat CT (SCTs)
openssl s_client -connect app.example.com:443 -status < /dev/null 2>&1 | grep -A 10 "OCSP Response"

# Afficher la chaîne de certificats complète
openssl s_client -connect app.example.com:443 -showcerts < /dev/null

# Tester que TLS 1.2 est refusé (si TLS 1.3 only)
openssl s_client -connect app.example.com:443 -tls1_2 < /dev/null
# Résultat attendu : "no protocols available" ou handshake failure

# Vérifier le cipher suite négocié
openssl s_client -connect app.example.com:443 < /dev/null 2>&1 | grep -E "Protocol|Cipher"
```

### HSTS — HTTP Strict Transport Security

HSTS (`max-age=63072000; includeSubDomains; preload`) indique au navigateur de **refuser toute connexion HTTP** vers le domaine pendant 2 ans (63 072 000 secondes). Le flag `preload` permet d'inscrire le domaine dans la liste HSTS preload des navigateurs, éliminant la vulnérabilité lors de la toute première visite (*TOFU — Trust On First Use*).

```{admonition} HSTS et sous-domaines
:class: warning
`includeSubDomains` applique HSTS à **tous les sous-domaines**. Vérifiez que tous vos sous-domaines supportent HTTPS avant d'activer ce flag — un sous-domaine HTTP deviendrait inaccessible depuis les navigateurs ayant mémorisé l'en-tête.
```

### OCSP Stapling

OCSP (*Online Certificate Status Protocol*) permet de vérifier si un certificat est révoqué. Sans stapling, le navigateur interroge le serveur OCSP de la CA lors de chaque connexion — lenteur et vie privée. Avec **OCSP stapling**, le serveur interroge lui-même le serveur OCSP et inclut la réponse signée dans le handshake TLS. Le client obtient la preuve de validité sans requête externe.

### Certificate Pinning

Le *certificate pinning* consiste à n'accepter qu'un ensemble précis de certificats ou de clés publiques (*HPKP — HTTP Public Key Pinning*, désormais déprécié dans les navigateurs, ou pinning dans les applications mobiles).

**Risques :**

- Si le certificat piqué expire ou est révoqué et que le pinning n'est pas mis à jour, l'application devient **inaccessible** — risque opérationnel majeur.
- HPKP a été retiré de Chrome en 2018 après plusieurs incidents de DoS auto-infligé.

**Recommandation actuelle** : préférer **Certificate Transparency** (surveillance des émissions) plutôt que le pinning pour les applications web. Le pinning reste pertinent dans des applications mobiles ou des clients embarqués avec un cycle de mise à jour maîtrisé.

## Résumé

1. **TLS 1.3 réduit le handshake à 1 RTT** en fusionnant la négociation des paramètres et l'échange de clé ECDHE dans les deux premiers messages, réduisant la latence par rapport à TLS 1.2.
2. **TLS 1.3 supprime tous les algorithmes faibles** : RSA key exchange, RC4, 3DES, CBC avec HMAC MD5/SHA-1, compression — ne restent que 5 cipher suites, toutes sûres.
3. **ECDHE éphémère est obligatoire en TLS 1.3**, ce qui garantit la forward secrecy systématique : la compromission de la clé long terme du serveur ne permet pas de déchiffrer les sessions passées.
4. **Le mode 0-RTT** est une optimisation de latence pour les reconnexions, mais il expose aux attaques de rejeu — à limiter aux requêtes idempotentes.
5. **mTLS authentifie les deux parties** via des certificats X.509 mutuels ; il est utilisé dans les service meshes (Istio, Linkerd) pour sécuriser les communications inter-microservices sans configuration applicative.
6. **SNI** permet le virtual hosting TLS (plusieurs domaines sur une même IP) ; ECH (Encrypted Client Hello) va plus loin en chiffrant le ClientHello pour masquer le SNI aux observateurs réseau.
7. **Certificate Transparency** rend visible toute émission de certificat dans des logs publics auditables, permettant de détecter rapidement des certificats frauduleux pour son domaine.
8. **Les attaques historiques** (BEAST, POODLE, DROWN) exploitaient des algorithmes ou des modes aujourd'hui supprimés de TLS 1.3 ; HEARTBLEED était un bug d'implémentation dont la forward secrecy limite les conséquences.
9. **La configuration nginx recommandée** active TLS 1.3 uniquement, désactive les session tickets pour la forward secrecy stricte, active l'OCSP stapling et impose HSTS avec preload.
10. **Le certificate pinning est déconseillé** pour les applications web (risque de DoS auto-infligé) ; Certificate Transparency et la surveillance des logs CT (`crt.sh`, alertes automatiques) constituent une alternative plus robuste.
