CO_ER_SSL_TOO_MANY, /* too many SSL connections */
CO_ER_SSL_NO_MEM, /* no more memory to allocate an SSL connection */
CO_ER_SSL_RENEG, /* forbidden client renegotiation */
+ CO_ER_SSL_KEYUPDATE, /* too many TLS1.3 KeyUpdate messages */
CO_ER_SSL_CA_FAIL, /* client cert verification failed in the CA chain */
CO_ER_SSL_CRT_FAIL, /* client cert verification failed on the certificate */
CO_ER_SSL_MISMATCH, /* Server presented an SSL certificate different from the configured one */
#define SSL_SOCK_F_HAS_ALPN (1 << 5) /* An ALPN has been negotiated */
#define SSL_SOCK_F_KTLS_ULP (1 << 6) /* TLS ULP is enabled on that socket */
#define SSL_SOCK_F_KTLS_RX_CTRL (1 << 7) /* a non-app-data record is blocking kTLS splicing, fall back to a buffered read to consume it */
+#define SSL_SOCK_F_KILL (1 << 9) /* connection must be aborted (e.g. KeyUpdate flood) */
struct ssl_sock_ctx {
struct connection *conn;
struct buffer early_buf; /* buffer to store the early data received */
int sent_early_data; /* Amount of early data we sent so far */
int flags; /* Various flags for the ssl_sock_ctx */
+ uint keyupdate_tokens; /* remaining TLS1.3 KeyUpdate token budget */
+ uint keyupdate_last_ms; /* last KeyUpdate token refill */
#ifdef HA_USE_KTLS
char record_type; /* Record type to use if not just sending application data */
#endif
int ctx_cache; /* max number of entries in the ssl_ctx cache. */
int capture_buffer_size; /* Size of the capture buffer. */
int keylog; /* activate keylog */
+ uint keyupdate_max; /* max received TLS1.3 KeyUpdates per second per connection (0 == unlimited) */
int extra_files; /* which files not defined in the configuration file are we looking for */
int extra_files_noext; /* whether we remove the extension when looking up a extra file */
int security_level; /* configure the openssl security level */
}
#endif
+/*
+ * parse "tune.ssl.keyupdate-rate-limit" : max received TLS1.3 KeyUpdate
+ * messages per second and per connection. 0 disables the limit.
+ */
+static int ssl_parse_global_keyupdate_ratelimit(char **args, int section_type, struct proxy *curpx,
+ const struct proxy *defpx, const char *file, int line,
+ char **err)
+{
+ int val;
+
+ if (too_many_args(1, args, err, NULL))
+ return -1;
+
+ if (!*args[1]) {
+ memprintf(err, "'%s' expects a number (KeyUpdates/s, 0 to disable).", args[0]);
+ return -1;
+ }
+ val = atoi(args[1]);
+ if (val < 0) {
+ memprintf(err, "'%s' expects a positive number.", args[0]);
+ return -1;
+ }
+ global_ssl.keyupdate_max = val;
+ return 0;
+}
+
/* Allow to explicitly disable certificate compression when set to "off" */
#ifdef SSL_OP_NO_RX_CERTIFICATE_COMPRESSION
static int ssl_parse_certificate_compression(char **args, int section_type, struct proxy *curpx,
{ CFG_GLOBAL, "tune.ssl.capture-cipherlist-size", ssl_parse_global_capture_buffer },
{ CFG_GLOBAL, "tune.ssl.capture-buffer-size", ssl_parse_global_capture_buffer },
{ CFG_GLOBAL, "tune.ssl.keylog", ssl_parse_global_keylog },
+ { CFG_GLOBAL, "tune.ssl.keyupdate-rate-limit", ssl_parse_global_keyupdate_ratelimit },
{ CFG_GLOBAL, "ssl-default-bind-ciphers", ssl_parse_global_ciphers },
{ CFG_GLOBAL, "ssl-default-server-ciphers", ssl_parse_global_ciphers },
{ CFG_GLOBAL, "ssl-default-bind-curves", ssl_parse_global_curves },
case CO_ER_SSL_TOO_MANY: return "SSL_TOO_MANY";
case CO_ER_SSL_NO_MEM: return "SSL_NO_MEM";
case CO_ER_SSL_RENEG: return "SSL_RENEG";
+ case CO_ER_SSL_KEYUPDATE: return "SSL_KEYUPDATE";
case CO_ER_SSL_CA_FAIL: return "SSL_CA_FAIL";
case CO_ER_SSL_CRT_FAIL: return "SSL_CRT_FAIL";
case CO_ER_SSL_MISMATCH: return "SSL_MISMATCH";
case CO_ER_SSL_TOO_MANY: return "Too many SSL connections";
case CO_ER_SSL_NO_MEM: return "Out of memory when initializing an SSL connection";
case CO_ER_SSL_RENEG: return "Rejected a client-initiated SSL renegotiation attempt";
+ case CO_ER_SSL_KEYUPDATE: return "Aborted connection after a TLS 1.3 KeyUpdate flood";
case CO_ER_SSL_CA_FAIL: return "SSL client CA chain cannot be verified";
case CO_ER_SSL_CRT_FAIL: return "SSL client certificate not trusted";
case CO_ER_SSL_MISMATCH: return "Server presented an SSL certificate different from the configured one";
.default_dh_param = SSL_DEFAULT_DH_PARAM,
.ctx_cache = DEFAULT_SSL_CTX_CACHE,
.capture_buffer_size = 0,
+ .keyupdate_max = 100, /* max received TLS1.3 KeyUpdates/s per conn; 0 to disable */
.extra_files = SSL_GF_ALL,
.extra_files_noext = 0,
#ifdef HAVE_SSL_KEYLOG
int flags;
ctx = BIO_get_data(h);
+
+ /* A KeyUpdate flood was detected, we should abort now, as we may
+ * have plenty more KeyUpdate to deal with, and that would end up
+ * triggering the watchdog.
+ */
+ if (ctx->flags & SSL_SOCK_F_KILL) {
+ ctx->conn->flags |= CO_FL_ERROR;
+ return -1;
+ }
#ifdef HA_USE_KTLS
#ifdef HAVE_VANILLA_OPENSSL
if (ctx->flags & SSL_SOCK_F_KTLS_RECV) {
SSL *ssl);
#endif
+static void ssl_sock_keyupdate_ratelimit(int write_p, int version,
+ int content_type, const void *buf,
+ size_t len, SSL *ssl);
+
/* List head of all registered SSL/TLS protocol message callbacks. */
struct list ssl_sock_msg_callbacks = LIST_HEAD_INIT(ssl_sock_msg_callbacks);
return ERR_ABORT;
}
#endif
+ if (global_ssl.keyupdate_max) {
+ if (!ssl_sock_register_msg_callback(ssl_sock_keyupdate_ratelimit))
+ return ERR_ABORT;
+ }
#ifdef USE_QUIC_OPENSSL_COMPAT
if (!ssl_sock_register_msg_callback(quic_tls_compat_msg_callback))
return ERR_ABORT;
}
#endif
+static void ssl_sock_keyupdate_ratelimit(int write_p, int version,
+ int content_type, const void *buf,
+ size_t len, SSL *ssl)
+{
+ struct ssl_sock_ctx *ctx = NULL;
+ struct connection *conn;
+ uint rate, elapsed;
+
+ /* only count KeyUpdate messages received from the peer */
+ if (write_p || content_type != SSL3_RT_HANDSHAKE)
+ return;
+ if (len < 1 || ((const unsigned char *)buf)[0] != SSL3_MT_KEY_UPDATE)
+ return;
+
+ conn = ssl_sock_get_conn(ssl, &ctx);
+ if (!conn || !ctx)
+ return;
+
+ rate = global_ssl.keyupdate_max;
+ elapsed = now_ms - ctx->keyupdate_last_ms; /* wrap-safe */
+ if (elapsed) {
+ uint refill = (uint)((ullong)elapsed * rate / 1000);
+
+ if (refill) {
+ ctx->keyupdate_tokens += refill;
+ if (ctx->keyupdate_tokens > rate)
+ ctx->keyupdate_tokens = rate;
+ ctx->keyupdate_last_ms = now_ms;
+ }
+ }
+
+ if (ctx->keyupdate_tokens)
+ ctx->keyupdate_tokens--;
+ else {
+ /* received way too many KeyUpdates, kill the connection */
+ if (conn->owner)
+ session_add_glitch_ctr(conn->owner, 1);
+ ctx->flags |= SSL_SOCK_F_KILL;
+ conn->flags |= CO_FL_ERROR;
+ conn->err_code = CO_ER_SSL_KEYUPDATE;
+ }
+}
+
/* Callback is called for ssl protocol analyse */
static __maybe_unused void ssl_sock_msgcbk(int write_p, int version, int content_type, const void *buf, size_t len, SSL *ssl, void *arg)
{
ctx->xprt_ctx = NULL;
ctx->error_code = 0;
ctx->flags = SSL_SOCK_F_EARLY_ENABLED;
+ ctx->keyupdate_tokens = global_ssl.keyupdate_max;
+ ctx->keyupdate_last_ms = now_ms;
#ifdef HA_USE_KTLS
ctx->record_type = 0;
#endif