Using OpenSSL TLS 1.3 0‑RTT Early Data Safely
Learn how to enable and safely use OpenSSL’s TLS 1.3 0‑RTT early data, including API usage, replay‑safety checks, and performance trade‑offs.
11 Jun 2026, 19:35 UTC

Problem: you want to shave latency but fear replay attacks
When a client resumes a TLS 1.3 session, it can send application data in the very first flight (called early data or 0‑RTT). This cuts a round‑trip, but the data is not protected against replay unless the application adds its own defenses. OpenSSL provides the mechanism, but it leaves replay safety to you.
Thesis: enable early data only after you understand the API contract and add replay‑safe checks
By correctly configuring OpenSSL, checking return values, and limiting early data to idempotent or single‑use operations, you gain the latency benefit without opening a replay window.
How OpenSSL implements 0‑RTT
OpenSSL 1.1.1+ exposes two key functions:
SSL_CTX_set_early_data_enabled(ctx, 1)– tells the context to advertise and accept early data.SSL_write_early_data(ssl, buf, len)– attempts to write early data; returns the number of bytes written or a negative error code.
The server processes early data after validating the session ticket but before sending its own ServerHello. If the ticket key has rotated, the session is not resumable, or the application protocol forbids early data, OpenSSL rejects the write and returns SSL_ERROR_WANT_READ or SSL_ERROR_SSL.
Worked example: a minimal server and client
Server side (compile with gcc server.c -lcrypto -lssl -o server)
#include
#include
int main() {
SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
if (!ctx) { /* handle error */ }
/* load cert/key omitted for brevity */
SSL_CTX_set_early_data_enabled(ctx, 1); /* enable 0‑RTT */
SSL *ssl = SSL_new(ctx);
/* set up fd, accept() etc. */
SSL_set_fd(ssl, client_fd);
if (SSL_accept(ssl) <= 0) { /* handle error */ }
/* After handshake, try to read early data */
char buf[1024];
int r = SSL_read_early_data(ssl, buf, sizeof(buf));
if (r > 0) {
/* Process buf – only if operation is replay‑safe */
/* Example: increment a counter stored in a single‑use token */
} else if (SSL_get_error(ssl, r) == SSL_ERROR_WANT_READ) {
/* No early data presented */
} else {
/* Early data rejected – handle accordingly */
}
/* … */
SSL_free(ssl);
SSL_CTX_free(ctx);
return 0;
}
Client side (compile similarly)
#include
#include
int main() {
SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
SSL_CTX_set_early_data_enabled(ctx, 1);
SSL *ssl = SSL_new(ctx);
SSL_set_fd(ssl, sock);
/* Perform a resumed handshake – assume ticket already stored */
if (SSL_connect(ssl) <= 0) { /* handle error */ }
const char *msg = "ping";
int w = SSL_write_early_data(ssl, msg, strlen(msg));
if (w > 0) {
/* Early data accepted – safe to send if idempotent */
} else {
int err = SSL_get_error(ssl, w);
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_SSL) {
/* Early data not accepted – fall back to normal write after handshake */
SSL_write(ssl, msg, strlen(msg));
} else {
/* Fatal error */
}
}
/* … */
SSL_free(ssl);
SSL_CTX_free(ctx);
return 0;
}
Key points to notice:
- Check the return value of
SSL_write_early_dataandSSL_read_early_data. - Use
SSL_get_early_data_status(returnsSSL_EARLY_DATA_ACCEPTED,SSL_EARLY_DATA_REJECTED, orSSL_EARLY_DATA_NOT_ATTEMPTED) to distinguish outcomes. - Only perform replay‑safe operations (e.g., idempotent GET, single‑use nonce) on data marked as accepted.
Trade‑offs and limitations
Enabling early data increases memory pressure: OpenSSL must keep the ticket‑encryption keys and session state alive until the early‑data window closes. On a busy server handling thousands of concurrent connections, this can add noticeable RAM usage. Additionally, because OpenSSL does not automatically defend against replay, you must implement application‑level mitigations; forgetting to do so opens a classic replay vulnerability.
Another limitation is protocol‑level: some application protocols (e.g., certain custom binary protocols) may not be safe to send early data even if they are idempotent, because the server might have state that changes between the early‑data flight and the full handshake.
Actionable closing
- Enable early data only after auditing your application for replay safety.
- Call
SSL_CTX_set_early_data_enabled(or the per‑SSL equivalent) and verify the function returns 1. - After a handshake, check
SSL_get_early_data_status; proceed with early‑data handling only when it returnsSSL_EARLY_DATA_ACCEPTED. - Monitor memory usage (e.g., via
pmaporvalgrind) when enabling the feature on production servers. - Rotate ticket keys frequently if you are concerned about the window during which a stolen ticket could be replayed.
When these steps are followed, OpenSSL’s 0‑RTT early data can reduce latency without compromising security.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.