@@ -820,6 +820,14 @@ def explode(*args, **kwargs): # type: ignore[no-untyped-def]
820820 "Getting group name is not supported by the linked OpenSSL version" ,
821821)
822822
823+ _requires_client_hello_cb = _make_requires (
824+ getattr (_lib , "Cryptography_HAS_CLIENT_HELLO_CB" , 0 ),
825+ (
826+ "SSL client hello callback is not supported by the "
827+ "linked cryptographic library"
828+ ),
829+ )
830+
823831
824832class Session :
825833 """
@@ -905,6 +913,7 @@ def __init__(self, method: int) -> None:
905913 self ._info_callback = None
906914 self ._keylog_callback = None
907915 self ._tlsext_servername_callback = None
916+ self ._client_hello_callback = None
908917 self ._app_data = None
909918 self ._alpn_select_helper : _ALPNSelectHelper | None = None
910919 self ._alpn_select_callback : _ALPNSelectCallback | None = None
@@ -1762,6 +1771,31 @@ def wrapper(ssl, alert, arg): # type: ignore[no-untyped-def]
17621771 self ._context , self ._tlsext_servername_callback
17631772 )
17641773
1774+ @_requires_client_hello_cb
1775+ @_require_not_used
1776+ def set_ssl_ctx_client_hello_callback (self , callback ) -> None :
1777+ """
1778+ Specify a callback function to be called when the ClientHello
1779+ is received.
1780+
1781+ :param callback: The callback function. It will be invoked with one
1782+ argument, the Connection instance.
1783+
1784+ .. versionadded:: 0.13
1785+ """
1786+
1787+ @wraps (callback )
1788+ def wrapper (ssl , alert , arg ): # type: ignore[no-untyped-def]
1789+ callback (Connection ._reverse_mapping [ssl ])
1790+ return 1
1791+
1792+ self ._client_hello_callback = _ffi .callback (
1793+ "int (*)(SSL *, int *, void *)" , wrapper
1794+ )
1795+ _lib .SSL_CTX_set_client_hello_cb (
1796+ self ._context , self ._client_hello_callback , _ffi .NULL
1797+ )
1798+
17651799 @_require_not_used
17661800 def set_tlsext_use_srtp (self , profiles : bytes ) -> None :
17671801 """
@@ -3262,3 +3296,50 @@ def wrapper(ssl, where, return_code): # type: ignore[no-untyped-def]
32623296 "void (*)(const SSL *, int, int)" , wrapper
32633297 )
32643298 _lib .SSL_set_info_callback (self ._ssl , self ._info_callback )
3299+
3300+ @_requires_client_hello_cb
3301+ def get_client_hello_extension (self , type : int ) -> bytes :
3302+ """
3303+ Returns the client extension with the specified type. If the extensions
3304+ cannot be found an empty byte string is returned.
3305+
3306+ :param type: The type of extension to retrieve as integer.
3307+ :return: A byte array containing the extension or an empty byte array
3308+ if the extension is absent.
3309+ """
3310+ out = _ffi .new ("const unsigned char **" )
3311+ outlen = _ffi .new ("size_t *" )
3312+ _lib .SSL_client_hello_get0_ext (self ._ssl , type , out , outlen )
3313+
3314+ if not outlen :
3315+ return b""
3316+
3317+ return _ffi .buffer (out [0 ], outlen [0 ])[:]
3318+
3319+ @_requires_client_hello_cb
3320+ def get_client_hello_extensions_present (self ) -> list [bytes ]:
3321+ """
3322+ Returns a list of the types of the client hello extensions
3323+ that are present in the ClientHello message.
3324+ """
3325+ # SSL_client_hello_get1_extensions_present returns a new array
3326+ # allocated by OpenSSL_malloc
3327+ data = _ffi .new ("int **" )
3328+ data_len = _ffi .new ("size_t *" )
3329+ rc = _lib .SSL_client_hello_get1_extensions_present (
3330+ self ._ssl , data , data_len
3331+ )
3332+
3333+ _openssl_assert (rc == 1 )
3334+
3335+ if not data_len :
3336+ return []
3337+
3338+ # OpenSSL returns the number of items and FFI wants the numbers of
3339+ # types, so multiply it by the size of each item (int)
3340+ data_gc = _ffi .gc (data [0 ], _lib .OPENSSL_free )
3341+
3342+ buf = _ffi .buffer (data_gc , data_len [0 ] * _ffi .sizeof ("int" ))
3343+ retarray = _ffi .from_buffer ("int[]" , buf )
3344+
3345+ return list (retarray )
0 commit comments