Early Data TLS Memerlukan Semantik Aplikasi yang Aman terhadap Replay

TLS 1.3 dapat membawa application data pada flight pertama client ketika client dan server berbagi pre-shared key (PSK) yang sesuai, umumnya dari koneksi sebelumnya. Mode ini disebut early data atau 0-RTT. Mekanisme tersebut menghilangkan waktu tunggu handshake dari critical path untuk traffic aplikasi yang memenuhi syarat, tetapi pengurangan latensi itu datang dengan kontrak keamanan yang lebih sempit.

Batas utamanya adalah replay. TLS tidak memberikan jaminan non-replay untuk data 0-RTT di antara koneksi. Server dapat menerapkan mekanisme anti-replay, tetapi aplikasi tetap harus memperlakukan early data sebagai data yang berpotensi diputar ulang. Perbedaan ini penting ketika satu request yang diterima dapat mengubah state persisten, memakai capability sekali pakai, memicu tindakan eksternal, atau menghasilkan efek lain yang seharusnya hanya terjadi satu kali.

Ini bukan kelemahan enkripsi TLS. Early data tetap terenkripsi. Persoalannya berasal dari waktu tersedianya key dan byte request dibandingkan dengan handshake baru.

Early data mendahului ServerHello baru

Pada koneksi TLS 1.3 yang di-resume, client dapat menurunkan early traffic key dari PSK dan mengirim application data bersama ClientHello. Server belum memberikan nilai ServerHello baru yang ikut membentuk traffic key berikutnya.

Alur sederhananya:

Client                                      Server

ClientHello
+ pre_shared_key
+ early_data
Application Data (0-RTT)  ------------->

                                  ServerHello
                                  EncryptedExtensions
                                  Finished
                         <-------------

Finished
Application Data (1-RTT)  ------------->

Spesifikasi TLS 1.3 saat ini, RFC 9846, menyatakan dua batas penting untuk early data: protocol tidak memberikan jaminan forward secrecy yang sama seperti traffic berikutnya, dan tidak memberikan jaminan non-replay antar-koneksi. Properti kedua menjadi persoalan desain aplikasi yang dibahas di sini.

Penyerang tidak perlu mendekripsi flight early data yang tertangkap untuk mencoba replay. Mengirim ulang byte terenkripsi yang relevan dapat cukup jika aturan penerimaan di sisi server memungkinkan flight tersebut diproses lagi.

State anti-replay mengurangi risiko tanpa mengubah semantik request

TLS 1.3 menjelaskan mekanisme server yang dapat membatasi penerimaan replay. Deployment dapat mencatat nilai ClientHello yang baru diterima, membatasi ticket ke zone otoritatif, atau mengoordinasikan penerimaan dengan cara lain agar handshake early data yang sama tidak diterima berulang kali.

Kontrol tersebut memiliki batas operasional. Service terdistribusi dapat memiliki beberapa region, process, atau partisi storage. State anti-replay dapat tidak tersedia, terlambat direplikasi, terpartisi, atau sengaja dibatasi cakupannya karena pertimbangan biaya dan availability. Karena itu, RFC 9846 mewajibkan client membatasi early data pada message yang dianggap aman untuk diputar ulang dan aman untuk dicoba kembali melalui koneksi lain.

Semantik aplikasi tetap menjadi batas terakhir. Jika replay request sebanyak dua kali membuat dua pembelian, mengirim dua command yang tidak dapat dibatalkan, atau memakai dua unit resource langka, pengurangan replay pada layer transport bukan dasar yang cukup untuk menerima request tersebut melalui 0-RTT.

Artinya, “terenkripsi” dan “fresh” adalah dua properti berbeda. Autentikasi AEAD melindungi early data dari modifikasi yang tidak terdeteksi di bawah traffic key. Mekanisme itu tidak memberikan semantik eksekusi unik secara global kepada operasi aplikasi di dalam data tersebut.

HTTP membawa status early data ke layer aplikasi

RFC 8470 menetapkan penggunaan TLS early data pada HTTP. RFC tersebut menambahkan request header field Early-Data agar informasi penggunaan early data dapat melewati intermediary hop, serta mendefinisikan status code 425 Too Early untuk server yang tidak bersedia memproses request dengan risiko replay.

Batas pentingnya adalah bahwa penerimaan TLS dan penerimaan HTTP merupakan dua keputusan berbeda. Pada layer TLS, server menerima atau menolak early data sebagai satu unit; server tidak dapat memilih hanya satu request dari flight early data tersebut. Setelah HTTP menerima request, aplikasi atau intermediary masih dapat menolak menjalankan operasinya.

Jalur keputusan yang umum dapat berbentuk:

request tiba
     |
     v
diterima sebagai early data?
     |
  +--+--+
  |     |
tidak   ya
  |     |
jalur   apakah operasi aman terhadap replay?
normal  |
     +--+--+
     |     |
     ya   tidak
     |     |
 proses  425 Too Early
            |
            v
      retry setelah handshake

RFC 8470 menetapkan bahwa retry setelah 425 Too Early tidak boleh dikirim lagi sebagai early data. Dengan demikian, server dapat menunda operasi berisiko sampai koneksi memiliki properti keamanan traffic biasa setelah handshake.

Nama HTTP method bukan policy lengkap

Memetakan kelayakan 0-RTT langsung ke HTTP method terlihat praktis: izinkan GET dan HEAD, tolak POST, lalu selesai. Aturan itu dapat menjadi titik awal yang konservatif, tetapi nama method tidak sepenuhnya menggambarkan efek aplikasi.

Request yang secara nominal aman masih dapat memicu perilaku aplikasi seperti memakai link sekali pakai, mencatat transisi state, memulai komputasi mahal, atau berinteraksi dengan backend yang tidak aman terhadap replay. Sebaliknya, API dapat merancang operasi yang mengubah state menggunakan application-level idempotency key, tetapi mekanisme tersebut memerlukan scope, persistence, collision behavior, dan kontrak retry yang jelas.

Karena itu, policy sebaiknya berada dekat dengan semantik operasi. Metadata routing dapat menyediakan default, sedangkan handler atau definisi service mencatat apakah replay dapat diterima untuk operasi tertentu.

Idempotency membatasi efek duplikat hanya jika scope-nya persisten

Idempotency key dapat membuat sebagian operasi retry lebih aman, tetapi sekadar menerima field bergaya Idempotency-Key tidak otomatis menghasilkan replay safety. Server perlu mengikat key ke principal dan operasi yang dimaksud, menyimpan hasil atau execution state selama interval yang memadai, serta memastikan duplicate request yang berjalan bersamaan bertemu pada satu outcome.

Contohnya:

(principal, operation, idempotency_key)
                  |
                  v
          atomic lookup/create
             /          \
        first call     duplicate
            |              |
         execute       return stored
         operation        outcome
            |
        store outcome

Jika dua region memiliki idempotency store independen, key yang sama masih dapat dieksekusi sekali di setiap region. Jika record kedaluwarsa sebelum replay datang, perlindungannya tidak lagi mencakup replay tersebut. Jika key tidak diikat ke parameter request, penggunaan ulang secara tidak sengaja dapat mengembalikan outcome untuk operasi yang berbeda.

Semua itu adalah invariant aplikasi, bukan properti TLS. 0-RTT dapat memanfaatkannya ketika invariant tersebut memang sudah memberikan kontrol duplicate execution yang dibutuhkan, tetapi 0-RTT tidak memperkuatnya.

Autentikasi di dalam early data juga terkena replay

Request aplikasi dapat membawa cookie, bearer token, signed request, atau credential lain di dalam early data. Enkripsi mencegah pengamat pasif membaca field tersebut, tetapi replay dapat mereproduksi authenticated request secara utuh.

Request signature juga tidak otomatis tahan replay. Replay resistance bergantung pada material yang ditandatangani dan policy verifier: nonce, timestamp, unique request identifier, server challenge, atau replay cache yang persisten dapat diperlukan sesuai protocol. Signature yang valid membuktikan integrity dan kepemilikan signing authority sesuai skemanya; signature itu sendiri tidak menyatakan bahwa message bertanda tangan yang sama belum pernah diterima.

Pemisahan yang sama berlaku pada authorization. Sebuah request dapat sepenuhnya diizinkan untuk suatu principal tetapi tetap tidak aman jika dieksekusi dua kali.

Proxy perlu mempertahankan sinyal early data

TLS terminator dapat menerima 0-RTT sebelum meneruskan HTTP request ke origin melalui koneksi berbeda. Dalam arsitektur tersebut, origin tidak lagi dapat menyimpulkan status early data dari sesi TLS miliknya sendiri.

RFC 8470 menggunakan header field Early-Data: 1 untuk membawa sinyal tersebut. Trust terhadap header ini penting. Infrastruktur yang melakukan terminasi TLS perlu menetapkan atau meneruskannya sesuai protocol, sementara deployment perlu mencegah client yang tidak dipercaya menghapus atau memalsukan forwarding metadata tepercaya ketika request melintasi proxy boundary.

Keputusan origin kemudian tetap eksplisit: operasi yang diklasifikasikan sensitif terhadap replay dapat menolak request dengan 425 Too Early, sedangkan operasi yang memenuhi syarat dapat berjalan berdasarkan replay policy service.

0-RTT sebaiknya menjadi capability per operasi

Mengaktifkan early data secara global hanya karena TLS library mendukungnya menempatkan keputusan pada abstraction layer yang keliru. TLS stack mengetahui apakah byte tiba sebelum handshake selesai; stack tersebut tidak mengetahui apakah byte itu berarti “baca dokumen dari cache” atau “transfer nilai.”

Desain yang lebih aman menjadikan kelayakan 0-RTT sebagai properti eksplisit dari operasi aplikasi. Default dapat tetap nonaktif. Suatu operasi baru dinyatakan memenuhi syarat ketika perilaku replay-nya dapat diterima, termasuk efek pada downstream system dan perilaku retry melalui intermediary.

Pendekatan ini menjaga optimasi performa tetap sempit. Early data TLS 1.3 dapat menghilangkan latensi handshake untuk traffic tertentu tanpa menganggap enkripsi juga menyediakan exactly-once execution. Batas keamanan yang berguna bersifat sederhana: setiap operasi yang diterima melalui 0-RTT harus tetap benar ketika request diputar ulang.

Referensi