Skip to content

Commit dc389ff

Browse files
committed
Some fixes.
1 parent 5097bb0 commit dc389ff

2 files changed

Lines changed: 55 additions & 19 deletions

File tree

‎Lib/test/test_ssl.py‎

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2046,6 +2046,12 @@ def psk_server_callback(*args, _=ctx, **kwargs): ...
20462046

20472047

20482048
class ECHTests(unittest.TestCase):
2049+
requires_ech_x25519 = unittest.skipUnless(
2050+
ssl.HAS_ECH
2051+
and 'x25519' in ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT).get_groups(),
2052+
'requires ECH with X25519',
2053+
)
2054+
20492055
# An ECHConfigList for localhost using X25519, HKDF-SHA256 and AES-128-GCM.
20502056
ECH_CONFIG = bytes.fromhex(
20512057
'003c fe0d 0038 db 0020 0020'
@@ -2097,7 +2103,7 @@ def test_socket_without_ssl_object(self):
20972103
self.assertIsNone(sock.get_ech_status())
20982104
self.assertIsNone(sock.get_ech_retry_config())
20992105

2100-
@unittest.skipUnless(ssl.HAS_ECH, 'requires ECH')
2106+
@requires_ech_x25519
21012107
def test_set_ech_config(self):
21022108
ctx = make_test_context()
21032109
for config in (self.ECH_CONFIG, bytearray(self.ECH_CONFIG),
@@ -2116,7 +2122,7 @@ def test_set_ech_config(self):
21162122
self.assertRaises(ssl.SSLError, ctx.set_ech_config, config)
21172123
self.assertIsNone(ctx.set_ech_config(self.ECH_CONFIG))
21182124

2119-
@unittest.skipUnless(ssl.HAS_ECH, 'requires ECH')
2125+
@requires_ech_x25519
21202126
def test_mixed_ech_config_versions(self):
21212127
ctx = make_test_context()
21222128
supported = self.ECH_CONFIG[2:]
@@ -2133,7 +2139,7 @@ def read_ech_key_and_config(self):
21332139
config = base64.b64decode(config.split(b'-----END ECHCONFIG-----')[0])
21342140
return key, config
21352141

2136-
@unittest.skipUnless(ssl.HAS_ECH, 'requires ECH')
2142+
@requires_ech_x25519
21372143
def test_load_ech_keys(self):
21382144
client_ctx, ctx, hostname = testing_context()
21392145
filename = data_file('ech.pem')
@@ -2171,7 +2177,7 @@ def test_load_ech_keys(self):
21712177
self.assertEqual(client.get_ech_status(),
21722178
ssl.ECHStatus.ECH_STATUS_SUCCESS)
21732179

2174-
@unittest.skipUnless(ssl.HAS_ECH, 'requires ECH')
2180+
@requires_ech_x25519
21752181
def test_ech_handshake(self):
21762182
key, config = self.read_ech_key_and_config()
21772183
other_config = bytearray(config)
@@ -2214,7 +2220,7 @@ def sni_callback(obj, name, ctx):
22142220
client, server, *_ = connected_bio_pair(client_ctx, server_ctx, hostname)
22152221
self.assertEqual(client.get_ech_retry_config(), bytes(other_config))
22162222

2217-
@unittest.skipUnless(ssl.HAS_ECH, 'requires ECH')
2223+
@requires_ech_x25519
22182224
def test_status_before_handshake(self):
22192225
ctx = make_test_context()
22202226
for configured in (False, True):
@@ -2233,7 +2239,7 @@ def test_status_before_handshake(self):
22332239
self.assertIsNone(obj.outer_server_hostname)
22342240
self.assertEqual(obj.get_ech_retry_config(), b'')
22352241

2236-
@unittest.skipUnless(ssl.HAS_ECH, 'requires ECH')
2242+
@requires_ech_x25519
22372243
def test_set_outer_server_name(self):
22382244
ctx = make_test_context()
22392245
ctx.set_ech_config(self.ECH_CONFIG)
@@ -2310,7 +2316,7 @@ def make_rejected_client(self):
23102316
c_in.write(s_out.read())
23112317
return client
23122318

2313-
@unittest.skipUnless(ssl.HAS_ECH, 'requires ECH')
2319+
@requires_ech_x25519
23142320
def test_rejected_ech(self):
23152321
client = self.make_rejected_client()
23162322
self.assertEqual(client.get_ech_status(), ssl.ECHStatus.ECH_STATUS_FAILED)
@@ -2320,7 +2326,7 @@ def test_rejected_ech(self):
23202326
self.assertEqual(client.outer_server_hostname, 'localhost')
23212327
self.assertEqual(client.get_ech_retry_config(), b'')
23222328

2323-
@unittest.skipUnless(ssl.HAS_ECH, 'requires ECH')
2329+
@requires_ech_x25519
23242330
@support.nomemtest
23252331
def test_status_allocation_failure(self):
23262332
import _testcapi

‎Modules/_ssl.c‎

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5241,36 +5241,66 @@ _ssl__SSLContext_set_ech_config_impl(PySSLContext *self,
52415241
if (config_bio == NULL) {
52425242
goto fail;
52435243
}
5244+
store = OSSL_ECHSTORE_new(NULL, NULL);
5245+
if (store == NULL) {
5246+
goto fail;
5247+
}
52445248
const unsigned char *config = ech_config->buf;
52455249
int config_len = (int)ech_config->len;
52465250
if (config_len >= 2 && ((config[0] << 8) | config[1]) == config_len - 2) {
52475251
config += 2;
52485252
config_len -= 2;
5249-
/* OpenSSL only recognizes binary input with a supported first entry. */
5250-
while (config_len >= 4 &&
5251-
((config[0] << 8) | config[1]) != OSSL_ECH_RFC9849_VERSION) {
5253+
/* Read entries separately so unsupported ones do not reject the list. */
5254+
while (config_len >= 4) {
52525255
int entry_len = 4 + ((config[2] << 8) | config[3]);
52535256
if (entry_len > config_len) {
52545257
break;
52555258
}
5259+
if (((config[0] << 8) | config[1]) == OSSL_ECH_RFC9849_VERSION) {
5260+
unsigned char length[2] = {
5261+
(unsigned char)(entry_len >> 8), (unsigned char)entry_len
5262+
};
5263+
if (BIO_reset(config_bio) != 1 ||
5264+
BIO_write(config_bio, length, sizeof(length)) != sizeof(length) ||
5265+
BIO_write(config_bio, config, entry_len) != entry_len) {
5266+
goto fail;
5267+
}
5268+
ERR_clear_error();
5269+
if (OSSL_ECHSTORE_read_echconfiglist(store, config_bio) != 1) {
5270+
unsigned long err = ERR_peek_error();
5271+
/* Unsupported HPKE suites may first raise provider errors. */
5272+
while (ERR_GET_LIB(err) == ERR_LIB_PROV &&
5273+
!ERR_FATAL_ERROR(err)) {
5274+
ERR_get_error();
5275+
err = ERR_peek_error();
5276+
}
5277+
/* Ignore unsupported suites and mandatory extensions,
5278+
* preserving malformed input and fatal errors. */
5279+
if (ERR_GET_LIB(err) != ERR_LIB_SSL ||
5280+
ERR_GET_REASON(err) != ERR_R_PASSED_INVALID_ARGUMENT) {
5281+
goto fail;
5282+
}
5283+
}
5284+
ERR_clear_error();
5285+
}
52565286
config += entry_len;
52575287
config_len -= entry_len;
52585288
}
5259-
unsigned char length[2] = {
5260-
(unsigned char)(config_len >> 8), (unsigned char)config_len
5261-
};
5262-
if (BIO_write(config_bio, length, sizeof(length)) != sizeof(length)) {
5289+
if (config_len != 0) {
5290+
ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR);
52635291
goto fail;
52645292
}
52655293
}
5266-
if (BIO_write(config_bio, config, config_len) != config_len) {
5294+
else if (BIO_write(config_bio, config, config_len) != config_len ||
5295+
OSSL_ECHSTORE_read_echconfiglist(store, config_bio) != 1) {
52675296
goto fail;
52685297
}
5269-
store = OSSL_ECHSTORE_new(NULL, NULL);
5270-
if (store == NULL) {
5298+
int num_entries;
5299+
if (OSSL_ECHSTORE_num_entries(store, &num_entries) != 1) {
52715300
goto fail;
52725301
}
5273-
if (OSSL_ECHSTORE_read_echconfiglist(store, config_bio) != 1) {
5302+
if (num_entries == 0) {
5303+
ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT);
52745304
goto fail;
52755305
}
52765306
PyMutex_Lock(&self->tstate_mutex);

0 commit comments

Comments
 (0)