TLS 1.3 0-RTT: miért nem elég csak a GET-et engedni?

A TLS 1.3 0-RTT gyorsíthatja a visszatérő látogató első kérését, de cserébe megismételhetővé teszi. Ezért önmagában az sem elég, ha csak GET kéréseket engedünk át.

A TLS 1.3 0-RTT módja egy teljes hálózati kört takaríthat meg a visszatérő látogatóknál, mert a kliens már a kézfogás befejezése előtt elküldheti az első HTTP-kérést, így távoli régióból vagy mobilhálózaton hamarabb érkezhet meg a válasz.

A gyorsításnak azonban ára van, mivel a korán elküldött kérés megismételhető, ezért nem elég annyit mondani, hogy 0-RTT-ben csak GET kéréseket engedünk át: a döntést endpointonként kell meghozni, mert nem minden GET mellékhatásmentes, és nem minden ismétlés olcsó.

TLS 1.3 0-RTT kapcsolat: a kliens korai HTTP-kérése két edge szerver felé ismétlődhet

Mitől lesz gyorsabb?

Egy új TLS-kapcsolatnál a kliens először elküldi a ClientHello üzenetet, majd megvárja a szerver válaszát, és csak ezután küld alkalmazási adatot; a TLS 1.3 early data ezzel szemben egy korábbi kapcsolat során kapott session ticketre támaszkodik, így a kliens az első HTTP-kérést már a ClientHello mellé oda tudja tenni.

A 0-RTT ezért nem az első látogatást gyorsítja, hanem csak akkor használható, ha a kliens korábban már járt a szervernél, és még van érvényes ticketje; a névben szereplő nulla sem a kézfogás hiányát jelenti, csupán azt, hogy az első alkalmazási adat elküldéséhez nem kell megvárni egy teljes hálózati fordulót.

A szerver ettől még elutasíthatja az early datát, ilyenkor pedig a kliens a rendes kézfogás után újraküldi a kérést, vagyis már ez a teljesen szabályos működés is megmutatja, hogy a 0-RTT-hez csak olyan művelet illik, amely biztonságosan ismételhető.

A titkosítás megmarad, az egyszeriség nem

Az early data kulcsa egy korábbi kapcsolatból származó titokra épül, és mivel a kliens még nem kapott friss, kapcsolatspecifikus adatot a szervertől, a TLS 1.3 nem garantál védelmet a kapcsolatok közötti visszajátszás ellen: egy támadó rögzítheti a titkosított korai adatot, majd egy másik kapcsolaton újra elküldheti.

A kérés tartalma ettől nem válik olvashatóvá, és észrevétlenül sem írható át, a veszély ugyanis más természetű: a szerver ugyanazt a műveletet többször hajthatja végre.

Elosztott rendszerben nehéz teljesen kizárni ezt, hiszen két edge szervernek közösen, erősen konzisztens módon kellene nyilvántartania, melyik korai adatot látta már. Ez ugyan lehetséges, de állapotot, koordinációt és késleltetést visz abba az útvonalba, amelyet éppen gyorsítani próbálunk, ezért a TLS-specifikáció a kliens és az alkalmazási protokoll felelősségévé is teszi, hogy csak biztonságosan újrajátszható adat kerüljön 0-RTT-be.

Miért nem elég csak a GET-et engedni?

Az HTTP early data szabályai alapértelmezésként megengedik a safe, vagyis biztonságos metódusokat, az unsafe vagy ismeretlen biztonságú metódusokat viszont tiltják, ami jó kiindulópont ugyan, de nem helyettesíti az endpoint ismeretét.

  • Egy GET /unsubscribe?token=… rosszul tervezett, mégis létező útvonal, amely megismételve ismét állapotot módosít.
  • Egy GET /download/report nem feltétlenül ír adatot, de minden hívás elindíthat egy drága lekérdezést vagy exportot.
  • Az olvasottsági, audit- vagy rate-limit-számlálót növelő GET üzleti értelemben szintén mellékhatással jár.

A safe és az idempotens ráadásul nem ugyanazt jelenti: míg a safe metódus olvasási szándékot fejez ki, az idempotens művelet többszöri végrehajtása ugyanarra a kívánt állapotra vezet, egy drága, egyébként idempotens lekérdezés ismétlése pedig ettől még alkalmas lehet erőforrás-kimerítésre.

Az Early-Data fejléc és a 425 válasz

Az origin szerver gyakran nem látja a kliens TLS-kézfogását, mert előtte egy CDN vagy reverse proxy terminálja a kapcsolatot, ezért az Early-Data: 1 fejléc továbbadja neki, hogy a kérés a kapcsolat egy korábbi állomásán early dataként érkezett.

Ha az origin nem vállalja az ismétlés kockázatát, 425 Too Early választ küldhet, amely után a kliens a befejezett TLS-kapcsolaton próbálkozik újra; a megismételt kérés már nem mehet early dataként, a 425-ös válasz pedig alapértelmezés szerint nem cache-elhető.

NGINX-ben az ssl_early_data direktíva és a $ssl_early_data változó adnak ehhez építőelemeket, a proxy azonban csak azt tudja megmondani, hogy korán érkezett-e a kérés, azt már az alkalmazás ismeri, hogy egy adott üzleti művelet ismétlése elfogadható-e.

Hol engedném át?

Jó jelölt lehet egy publikus, cache-elhető HTML-oldal, egy statikus konfiguráció vagy egy valóban mellékhatásmentes keresés, feltéve, hogy a CDN és az origin következetesen kezeli az early data jelzését.

Írási API-k, hitelesített állapotváltások, fizetés, jelszó-visszaállítás és költséges lekérdezések előtt nem engedném át, éles rendszerben pedig alapértelmezésként kikapcsolva hagynám, és csak méréssel igazolt késleltetéscsökkenés, valamint konkrét endpointlista alapján nyitnám meg.

A 0-RTT akkor jó optimalizálás, ha a kiválasztott útvonalak már eleve elviselik az ismétlést, mert ez nem egyszerű teljesítménykapcsoló, hanem a HTTP-műveletekről szóló biztonsági döntés.

Források