SSL_CTX_SET_CLIENT_HELLO_CB(3SSL)

πŸ’– SSL_CTX_set_client_hello_cb β€” the OpenSSL 3SSL manual, but make it chat πŸ’–

okay bestie, sit down, hydrate, and let's talk about SSL_CTX_set_client_hello_cb, plus its whole little friend group: SSL_client_hello_cb_fn, SSL_client_hello_isv2, SSL_client_hello_get0_legacy_version, SSL_client_hello_get0_random, SSL_client_hello_get0_session_id, SSL_client_hello_get0_ciphers, SSL_client_hello_get0_compression_methods, SSL_client_hello_get1_extensions_present, SSL_client_hello_get_extension_order, and SSL_client_hello_get0_ext. They're all callback-ish, early-stage, server-side ClientHello processing girlies. 🌸


✨ NAME ✨

So basically these are callback functions for early server-side ClientHello processing. That's the whole vibe. πŸ‘‘


πŸŽ€ SYNOPSIS πŸŽ€

Here's the cast, exactly as they're declared, no edits, we don't do that here:

typedef int (*SSL_client_hello_cb_fn)(SSL *s, int *al, void *arg);
void SSL_CTX_set_client_hello_cb(SSL_CTX *c, SSL_client_hello_cb_fn *f,
                                 void *arg);
unsigned int SSL_client_hello_get0_legacy_version(SSL *s);
size_t SSL_client_hello_get0_random(SSL *s, const unsigned char **out);
size_t SSL_client_hello_get0_session_id(SSL *s, const unsigned char **out);
size_t SSL_client_hello_get0_ciphers(SSL *s, const unsigned char **out);
size_t SSL_client_hello_get0_compression_methods(SSL *s,
                                                 const unsigned char **out);
int SSL_client_hello_get1_extensions_present(SSL *s, int **out,
                                             size_t *outlen);
int SSL_client_hello_get_extension_order(SSL *s, uint16_t *exts,
                                         size_t *num_exts);
int SSL_client_hello_get0_ext(SSL *s, unsigned int type, const unsigned char **out,
                              size_t *outlen);
🚨 Deprecation alert, bestie 🚨 The following function has been deprecated since OpenSSL 4.0, and you can hide it entirely by defining OPENSSL_API_COMPAT with a suitable version value β€” peep openssl_user_macros(7) if you wanna know more:
int SSL_client_hello_isv2(SSL *s);

πŸ’… DESCRIPTION πŸ’…

the setter, the callback, the whole situation

SSL_CTX_set_client_hello_cb() sets the callback function, and that callback gets called automatically during the early stages of ClientHello processing on the server. The arg you hand over when you set the callback gets passed right back to the callback at runtime, like a little gift you leave for future you. 🎁

Now listen, the return value of your callback literally decides fates:

  • Return failure (0) β†’ the connection terminates πŸ’€ and you, yes you, should say what alert value goes out in the al parameter. Do not leave al hanging, that's rude.
  • Return a negative value β†’ you suspend the handshake, and the handshake function returns immediately. SSL_get_error(3) will hand you SSL_ERROR_WANT_CLIENT_HELLO_CB to confirm the handshake got suspended. πŸ›‘

And here's the important part, no cap: it's the ClientHello callback's job to store info about the state of the last call, if it needs that to continue. Next time you call into the handshake function, the ClientHello callback gets called again, and if it returns success, normal handshake processing picks up right from that point. Resume, slay, continue. ✨

the SSLv2 one (rip)

SSL_client_hello_isv2() tells you whether the ClientHello was carried in an SSLv2 record and is in the SSLv2 format. Support for the SSLv2 format was removed in 4.0, so this function will always return 0. She's giving nostalgia, not function. πŸͺ¦

the raw-field getters

SSL_client_hello_get0_random(), SSL_client_hello_get0_session_id(), SSL_client_hello_get0_ciphers(), and SSL_client_hello_get0_compression_methods() give you access to the corresponding ClientHello fields. They return the field length, and optionally set an out pointer to the octets of that field. Obsessed with a clean pointer handoff. 🫢

the single-extension one

Similarly, SSL_client_hello_get0_ext() gives you access to individual extensions from the ClientHello, one extension at a time. For the wire protocol extension type value you provide, the extension value and length come back in the output parameters (if present).

the "which extensions tho" one

SSL_client_hello_get1_extensions_present() is your lil pre-game: use it prior to SSL_client_hello_get0_ext() to figure out which extensions are actually present in the ClientHello before you go querying for them.

  • Both the out and outlen parameters are required.
  • On success, the caller must release the storage allocated for *out using OPENSSL_free(). Yes, you. Free it. πŸ”₯
  • The contents of *out is an array of integers holding the numerical value of the TLS extension types, in the order they appear in the ClientHello.
  • *outlen holds the number of elements in the array.
  • If the ClientHello has no extensions at all, the function still returns success, with *out set to NULL and *outlen set to 0. Iconic minimalism.
  • ⚠️ Note that SSL_client_hello_get1_extensions_present() returns only recognised extensions. So unrecognised extensions (including GREASE) will not appear in the output. Sorry GREASE girlies. πŸ™ˆ

the no-malloc sibling

SSL_client_hello_get_extension_order() is basically SSL_client_hello_get1_extensions_present() but without internal memory allocation. Very "I brought my own storage" energy. πŸ’…

  • Called with exts set to NULL β†’ it returns the number of extensions (perfect for allocating storage for a subsequent call, iykyk).
  • Otherwise, *exts gets populated with the ExtensionType values, in the order the corresponding extensions appeared in the ClientHello.
  • *num_exts is an input/output parameter: input = the size of the storage you allocated; output = how many ExtensionType values were written.
  • If the input *num_exts is smaller than the number of extensions in question β†’ that is treated as an error. 🚨
  • A subsequent call with exts set to NULL can retrieve the size of storage needed.
  • A ClientHello that contained no extensions is treated as success, with *num_exts set to 0.

🌈 NOTES 🌈

The ClientHello callback hands application code a vast window of possibilities to affect the TLS handshake. Main character energy, honestly. πŸ‘‘

A primary use is letting the server examine the server name indication extension from the client, so it can pick an appropriate certificate to present and make other configuration adjustments relevant to that server name and its config. Those config changes can include:

  • swapping out the associated SSL_CTX pointer,
  • modifying the server's list of permitted TLS versions,
  • changing the server's cipher list in response to the client's cipher list,
  • etc etc, you get it. πŸ¦‹

Also, recommended: applications should utilize a ClientHello callback and NOT use a servername callback, to avoid unexpected behavior that occurs due to the relative order of processing between things like session resumption and the historical servername callback. Learn from the elders' mistakes, bestie. 😭

πŸ“Œ The SSL_client_hello_* family of functions may only be called from code executing within a ClientHello callback. That's the VIP section only.

πŸ“Œ The SSL_client_hello_get0_*() functions return raw ClientHello data, whereas SSL_client_hello_get1_extensions_present() returns only recognized extensions β€” so unknown/GREASE extensions are not included.

πŸ“Œ When Encrypted Client Hello (ECH) is used and ECH decryption has succeeded, the inner ClientHello message is what's visible to the ClientHello callback functions. If ECH decryption failed, the outer ClientHello message content is provided instead. πŸ•΅οΈβ€β™€οΈ


⭐ RETURN VALUES ⭐

Your supplied ClientHello callback returns:

  • SSL_CLIENT_HELLO_SUCCESS on success βœ…
  • SSL_CLIENT_HELLO_ERROR on failure ❌
  • SSL_CLIENT_HELLO_RETRY to suspend processing πŸ”„

And the crew:

  • SSL_client_hello_isv2() returns 0.
  • SSL_client_hello_get0_random(), SSL_client_hello_get0_session_id(), SSL_client_hello_get0_ciphers(), and SSL_client_hello_get0_compression_methods() return the length of the corresponding ClientHello fields. If zero is returned, do not assume the output pointer is valid. Just don't. πŸ™
  • SSL_client_hello_get0_ext() returns 1 if the extension of type type is present, and 0 otherwise.
  • SSL_client_hello_get1_extensions_present() returns 1 on success and 0 on failure.
  • SSL_client_hello_get_extension_order() returns 1 on success and 0 on failure.

πŸ¦‹ SEE ALSO πŸ¦‹


πŸ’œ HISTORY πŸ’œ

The SSL ClientHello callback, SSL_client_hello_isv2(), SSL_client_hello_get0_random(), SSL_client_hello_get0_session_id(), SSL_client_hello_get0_ciphers(), SSL_client_hello_get0_compression_methods(), SSL_client_hello_get0_ext(), and SSL_client_hello_get1_extensions_present() were added in OpenSSL 1.1.1. Then SSL_client_hello_get_extension_order() joined the group chat in OpenSSL 3.2.0. ✨


Copyright 2017-2026 The OpenSSL Project Authors. All Rights Reserved.

Licensed under the Apache License 2.0 (the "License"). You may not use this file except in compliance with the License. You can obtain a copy in the file LICENSE in the source distribution or at <https://www.openssl.org/source/license.html>.


and that's the whole page bestie, mwah πŸ’‹ this was 4.0.3, dated 2026-09-29, and it's the SSL_CTX_SET_CLIENT_HELLO_CB(3SSL) manual β€” go forth and callback responsibly πŸ§šπŸ’–

πŸ“¦ where this page comes from