Skip to content

Commit e679e76

Browse files
committed
Don't set attributes when ECH is not available.
1 parent 5649b6e commit e679e76

5 files changed

Lines changed: 111 additions & 81 deletions

File tree

‎Doc/library/ssl.rst‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,6 +1078,8 @@ Constants
10781078
:class:`enum.IntEnum` collection of Encrypted Client Hello (ECH) statuses
10791079
returned by :meth:`SSLSocket.get_ech_status`.
10801080

1081+
Only available when :data:`HAS_ECH` is ``True``.
1082+
10811083
.. versionadded:: next
10821084

10831085
.. attribute:: ECHStatus.ECH_STATUS_GREASE_ECH
@@ -1430,7 +1432,7 @@ SSL sockets also have the following additional methods and attributes:
14301432
:attr:`ECHStatus.ECH_STATUS_GREASE_ECH`, the configuration can be used
14311433
for a new connection using ECH.
14321434

1433-
Raise :exc:`NotImplementedError` if :data:`HAS_ECH` is ``False``.
1435+
Only available when :data:`HAS_ECH` is ``True``.
14341436

14351437
.. versionadded:: next
14361438

@@ -1439,7 +1441,7 @@ SSL sockets also have the following additional methods and attributes:
14391441
Gets the status of Encrypted Client Hello (ECH) processing. Returns an
14401442
:class:`ECHStatus` instance.
14411443

1442-
Raise :exc:`NotImplementedError` if :data:`HAS_ECH` is ``False``.
1444+
Only available when :data:`HAS_ECH` is ``True``.
14431445

14441446
.. versionadded:: next
14451447

@@ -1521,8 +1523,7 @@ SSL sockets also have the following additional methods and attributes:
15211523
Hello (ECH) is used: :class:`str` type, or ``None`` for server-side socket or
15221524
if no outer server name is available.
15231525

1524-
Raise :exc:`NotImplementedError` if :data:`HAS_ECH` is ``False`` and the
1525-
socket has an SSL wrapper.
1526+
Only available when :data:`HAS_ECH` is ``True``.
15261527

15271528
.. versionadded:: next
15281529

@@ -1915,7 +1916,7 @@ to speed up repeated connections from the same clients.
19151916
The *ech_config* parameter should be a :term:`bytes-like object` containing
19161917
a binary-encoded ECHConfigList.
19171918

1918-
This method will raise :exc:`NotImplementedError` if :data:`HAS_ECH` is ``False``.
1919+
Only available when :data:`HAS_ECH` is ``True``.
19191920

19201921
.. versionadded:: next
19211922

@@ -1945,8 +1946,7 @@ to speed up repeated connections from the same clients.
19451946
argument accepts the same values as for
19461947
:meth:`~SSLContext.set_alpn_protocols`.
19471948

1948-
This method will raise :exc:`NotImplementedError` if :data:`HAS_ECH` is
1949-
``False``.
1949+
Only available when :data:`HAS_ECH` is ``True``.
19501950

19511951
.. versionadded:: next
19521952

‎Lib/ssl.py‎

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,11 @@
151151
lambda name: name.startswith('CERT_'),
152152
source=_ssl)
153153

154-
_IntEnum._convert_(
155-
'ECHStatus', __name__,
156-
lambda name: name.startswith('ECH_STATUS_'),
157-
source=_ssl)
154+
if HAS_ECH:
155+
_IntEnum._convert_(
156+
'ECHStatus', __name__,
157+
lambda name: name.startswith('ECH_STATUS_'),
158+
source=_ssl)
158159

159160
PROTOCOL_SSLv23 = _SSLMethod.PROTOCOL_SSLv23 = _SSLMethod.PROTOCOL_TLS
160161
_PROTOCOL_NAMES = frozendict({
@@ -526,9 +527,10 @@ def set_alpn_protocols(self, alpn_protocols):
526527
protos = _encode_alpn_protocol_list(alpn_protocols)
527528
self._set_alpn_protocols(protos)
528529

529-
def set_outer_alpn_protocols(self, alpn_protocols):
530-
protos = _encode_alpn_protocol_list(alpn_protocols)
531-
self._set_outer_alpn_protocols(protos)
530+
if HAS_ECH:
531+
def set_outer_alpn_protocols(self, alpn_protocols):
532+
protos = _encode_alpn_protocol_list(alpn_protocols)
533+
self._set_outer_alpn_protocols(protos)
532534

533535
def _load_windows_store_certs(self, storename, purpose):
534536
try:
@@ -849,10 +851,11 @@ def context(self):
849851
def context(self, ctx):
850852
self._sslobj.context = ctx
851853

852-
@property
853-
def outer_server_hostname(self):
854-
"""The server name used in the outer ClientHello."""
855-
return self._sslobj.get_ech_status()[2]
854+
if HAS_ECH:
855+
@property
856+
def outer_server_hostname(self):
857+
"""The server name used in the outer ClientHello."""
858+
return self._sslobj.get_ech_status()[2]
856859

857860
@property
858861
def session(self):
@@ -1003,13 +1006,14 @@ def version(self):
10031006
def verify_client_post_handshake(self):
10041007
return self._sslobj.verify_client_post_handshake()
10051008

1006-
def get_ech_status(self):
1007-
"""Return the status of Encrypted Client Hello processing."""
1008-
return ECHStatus(self._sslobj.get_ech_status()[0])
1009+
if HAS_ECH:
1010+
def get_ech_status(self):
1011+
"""Return the status of Encrypted Client Hello processing."""
1012+
return ECHStatus(self._sslobj.get_ech_status()[0])
10091013

1010-
def get_ech_retry_config(self):
1011-
"""Return the server's ECH retry configuration as bytes."""
1012-
return self._sslobj.get_ech_retry_config()
1014+
def get_ech_retry_config(self):
1015+
"""Return the server's ECH retry configuration as bytes."""
1016+
return self._sslobj.get_ech_retry_config()
10131017

10141018

10151019
def _sslcopydoc(func):
@@ -1137,11 +1141,12 @@ def context(self, ctx):
11371141
self._context = ctx
11381142
self._sslobj.context = ctx
11391143

1140-
@property
1141-
def outer_server_hostname(self):
1142-
"""The server name used in the outer ClientHello."""
1143-
if self._sslobj is not None:
1144-
return self._sslobj.get_ech_status()[2]
1144+
if HAS_ECH:
1145+
@property
1146+
def outer_server_hostname(self):
1147+
"""The server name used in the outer ClientHello."""
1148+
if self._sslobj is not None:
1149+
return self._sslobj.get_ech_status()[2]
11451150

11461151
@property
11471152
@_sslcopydoc
@@ -1444,15 +1449,16 @@ def verify_client_post_handshake(self):
14441449
else:
14451450
raise ValueError("No SSL wrapper around " + str(self))
14461451

1447-
@_sslcopydoc
1448-
def get_ech_status(self):
1449-
if self._sslobj is not None:
1450-
return ECHStatus(self._sslobj.get_ech_status()[0])
1452+
if HAS_ECH:
1453+
@_sslcopydoc
1454+
def get_ech_status(self):
1455+
if self._sslobj is not None:
1456+
return ECHStatus(self._sslobj.get_ech_status()[0])
14511457

1452-
@_sslcopydoc
1453-
def get_ech_retry_config(self):
1454-
if self._sslobj is not None:
1455-
return self._sslobj.get_ech_retry_config()
1458+
@_sslcopydoc
1459+
def get_ech_retry_config(self):
1460+
if self._sslobj is not None:
1461+
return self._sslobj.get_ech_retry_config()
14561462

14571463
def _real_close(self):
14581464
self._sslobj = None

‎Lib/test/test_ssl.py‎

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2063,25 +2063,28 @@ def make_ssl_objects(self, context):
20632063
self.addCleanup(ssock.close)
20642064
return obj, ssock
20652065

2066-
@unittest.skipIf(ssl.HAS_ECH, 'requires OpenSSL without ECH')
2067-
def test_not_implemented(self):
2068-
ctx = make_test_context()
2069-
self.assertFalse(ssl.ECHStatus.__members__)
2070-
with self.assertRaises(NotImplementedError):
2071-
ctx.set_ech_config(self.ECH_CONFIG)
2072-
with self.assertRaises(NotImplementedError):
2073-
ctx.set_outer_alpn_protocols(['h2'])
2074-
for obj in self.make_ssl_objects(ctx):
2075-
with self.subTest(type=type(obj)):
2076-
with self.assertRaises(NotImplementedError):
2077-
obj.get_ech_status()
2078-
with self.assertRaises(NotImplementedError):
2079-
obj.get_ech_retry_config()
2080-
with self.assertRaises(NotImplementedError):
2081-
obj.outer_server_hostname
2082-
with self.assertRaises(NotImplementedError):
2083-
obj._sslobj.set_outer_server_name('localhost')
2066+
def test_availability(self):
2067+
self.assertEqual(hasattr(ssl, 'ECHStatus'), ssl.HAS_ECH)
2068+
for module in (ssl, _ssl):
2069+
names = [name for name in dir(module)
2070+
if name.startswith(('ECH_STATUS_', 'OP_ECH_'))]
2071+
with self.subTest(module=module):
2072+
self.assertEqual(bool(names), ssl.HAS_ECH)
2073+
2074+
context_methods = ('set_ech_config', '_set_outer_alpn_protocols')
2075+
socket_methods = ('get_ech_status', 'get_ech_retry_config')
2076+
for owner, names in (
2077+
(_ssl._SSLContext, context_methods),
2078+
(ssl.SSLContext, (*context_methods, 'set_outer_alpn_protocols')),
2079+
(_ssl._SSLSocket, (*socket_methods, 'set_outer_server_name')),
2080+
(ssl.SSLObject, (*socket_methods, 'outer_server_hostname')),
2081+
(ssl.SSLSocket, (*socket_methods, 'outer_server_hostname')),
2082+
):
2083+
for name in names:
2084+
with self.subTest(owner=owner, name=name):
2085+
self.assertEqual(hasattr(owner, name), ssl.HAS_ECH)
20842086

2087+
@unittest.skipUnless(ssl.HAS_ECH, 'requires ECH')
20852088
def test_socket_without_ssl_object(self):
20862089
ctx = make_test_context()
20872090
with ctx.wrap_socket(socket.socket()) as sock:

‎Modules/_ssl.c‎

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2122,6 +2122,7 @@ cipher_to_dict(const SSL_CIPHER *cipher)
21222122
);
21232123
}
21242124

2125+
#ifdef HAVE_OPENSSL_ECH
21252126
/*[clinic input]
21262127
@critical_section
21272128
_ssl._SSLSocket.get_ech_status
@@ -2131,7 +2132,6 @@ static PyObject *
21312132
_ssl__SSLSocket_get_ech_status_impl(PySSLSocket *self)
21322133
/*[clinic end generated code: output=263bf7fc7888e2d6 input=71320052d6f2a10f]*/
21332134
{
2134-
#ifdef HAVE_OPENSSL_ECH
21352135
char *inner_sni = NULL, *outer_sni = NULL;
21362136
if (self->ssl == NULL) {
21372137
Py_RETURN_NONE;
@@ -2141,10 +2141,6 @@ _ssl__SSLSocket_get_ech_status_impl(PySSLSocket *self)
21412141
OPENSSL_free(inner_sni);
21422142
OPENSSL_free(outer_sni);
21432143
return result;
2144-
#else
2145-
PyErr_SetString(PyExc_NotImplementedError, "OpenSSL does not have ECH support");
2146-
return NULL;
2147-
#endif
21482144
}
21492145

21502146
/*[clinic input]
@@ -2156,7 +2152,6 @@ static PyObject *
21562152
_ssl__SSLSocket_get_ech_retry_config_impl(PySSLSocket *self)
21572153
/*[clinic end generated code: output=967da2032df9a37a input=0e51b545e93f43ba]*/
21582154
{
2159-
#ifdef HAVE_OPENSSL_ECH
21602155
if (self->ssl == NULL) {
21612156
Py_RETURN_NONE;
21622157
}
@@ -2169,10 +2164,6 @@ _ssl__SSLSocket_get_ech_retry_config_impl(PySSLSocket *self)
21692164
PyObject *result = PyBytes_FromStringAndSize((char *)config, config_len);
21702165
OPENSSL_free(config);
21712166
return result;
2172-
#else
2173-
PyErr_SetString(PyExc_NotImplementedError, "OpenSSL does not have ECH support");
2174-
return NULL;
2175-
#endif
21762167
}
21772168

21782169
/*[clinic input]
@@ -2188,7 +2179,6 @@ _ssl__SSLSocket_set_outer_server_name_impl(PySSLSocket *self,
21882179
const char *outer_server_name)
21892180
/*[clinic end generated code: output=24e7c6b7b3c2ce84 input=8d1d96628e11b2fd]*/
21902181
{
2191-
#ifdef HAVE_OPENSSL_ECH
21922182
if (self->ssl == NULL) {
21932183
Py_RETURN_NONE;
21942184
}
@@ -2205,11 +2195,8 @@ _ssl__SSLSocket_set_outer_server_name_impl(PySSLSocket *self,
22052195
return NULL;
22062196
}
22072197
Py_RETURN_NONE;
2208-
#else
2209-
PyErr_SetString(PyExc_NotImplementedError, "OpenSSL does not have ECH support");
2210-
return NULL;
2211-
#endif
22122198
}
2199+
#endif /* HAVE_OPENSSL_ECH */
22132200

22142201
/*[clinic input]
22152202
@critical_section
@@ -4046,6 +4033,7 @@ _ssl__SSLContext__set_alpn_protocols_impl(PySSLContext *self,
40464033
Py_RETURN_NONE;
40474034
}
40484035

4036+
#ifdef HAVE_OPENSSL_ECH
40494037
/*[clinic input]
40504038
@critical_section
40514039
_ssl._SSLContext._set_outer_alpn_protocols
@@ -4058,7 +4046,6 @@ _ssl__SSLContext__set_outer_alpn_protocols_impl(PySSLContext *self,
40584046
Py_buffer *protos)
40594047
/*[clinic end generated code: output=823aea95e8dea835 input=b6ddf7d0791afd0e]*/
40604048
{
4061-
#ifdef HAVE_OPENSSL_ECH
40624049
if ((size_t)protos->len > UINT_MAX) {
40634050
PyErr_Format(PyExc_OverflowError,
40644051
"protocols longer than %u bytes", UINT_MAX);
@@ -4071,11 +4058,8 @@ _ssl__SSLContext__set_outer_alpn_protocols_impl(PySSLContext *self,
40714058
return NULL;
40724059
}
40734060
Py_RETURN_NONE;
4074-
#else
4075-
PyErr_SetString(PyExc_NotImplementedError, "OpenSSL does not have ECH support");
4076-
return NULL;
4077-
#endif
40784061
}
4062+
#endif /* HAVE_OPENSSL_ECH */
40794063

40804064
/*[clinic input]
40814065
@critical_section
@@ -5211,6 +5195,7 @@ _ssl__SSLContext_set_default_verify_paths_impl(PySSLContext *self)
52115195
Py_RETURN_NONE;
52125196
}
52135197

5198+
#ifdef HAVE_OPENSSL_ECH
52145199
/*[clinic input]
52155200
@critical_section
52165201
_ssl._SSLContext.set_ech_config
@@ -5229,7 +5214,6 @@ _ssl__SSLContext_set_ech_config_impl(PySSLContext *self,
52295214
Py_buffer *ech_config)
52305215
/*[clinic end generated code: output=cea53188b338cf80 input=3943e5aff04a583e]*/
52315216
{
5232-
#ifdef HAVE_OPENSSL_ECH
52335217
if (ech_config->len > INT_MAX) {
52345218
PyErr_Format(PyExc_OverflowError,
52355219
"ECH configuration longer than %d bytes", INT_MAX);
@@ -5259,11 +5243,8 @@ _ssl__SSLContext_set_ech_config_impl(PySSLContext *self,
52595243
OSSL_ECHSTORE_free(store);
52605244
BIO_free(config_bio);
52615245
return NULL;
5262-
#else
5263-
PyErr_SetString(PyExc_NotImplementedError, "OpenSSL does not have ECH support");
5264-
return NULL;
5265-
#endif
52665246
}
5247+
#endif /* HAVE_OPENSSL_ECH */
52675248

52685249
/*[clinic input]
52695250
@critical_section

0 commit comments

Comments
 (0)