Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ef042f0
Add a test for ssl with ssl.MemoryBIO
im-0 May 24, 2026
aab1759
Add Serde serializer that converts structs into basic Python objects
im-0 May 26, 2026
ea88e05
Support module monkey-patching on VM level
im-0 May 30, 2026
4b663e0
Add OpenSSL data files used to build OID and NID tables
im-0 May 27, 2026
d2b1496
Enable/disable ssl-related tests from cpython test suite
im-0 May 30, 2026
6c311f8
Revert "Skip flaky tests (#7961)"
im-0 May 31, 2026
3f5cb40
Revert "Skip flaky test (#7967)"
im-0 May 31, 2026
b5a1ea3
Almost a complete rewrite of rustls integration
im-0 May 24, 2026
d0b2767
Skip test.test_httplib.HTTPSTest.test_networked_good_cert if rustls i…
im-0 Jun 1, 2026
814b5cd
On client, both CERT_OPTIONAL and CERT_REQUIRED enable cert verification
im-0 Jun 2, 2026
150128f
Rename stdlib_ssl_bio_unencrypted_trailer.py -> stdlib_ssl.py
im-0 Jun 3, 2026
196140b
Move crates/stdlib/src/rustls-data -> crates/stdlib/rustls-data
im-0 Jun 3, 2026
0332046
Use expectedFailureIf() instead of skipIf() for rustls-specific skips
im-0 Jun 3, 2026
fdd514b
Fix `cargo shear` warn (graviola dep for examples/custom_tls_provider…
im-0 Jun 3, 2026
6b954ef
Call SNI callback earlier and re-read the context after it
im-0 Jun 3, 2026
99d20f8
Remove allow_threads() from rustls integration
im-0 Jun 3, 2026
adce18b
Use certs from both SSL_CERT_FILE and SSL_CERT_DIR env vars if present
im-0 Jun 3, 2026
6036eaa
Keep rustls error in SslError until converted into Python error
im-0 Jun 3, 2026
4e79b93
Ensure that TLS alerts are sent back on TLS errors
im-0 Jun 3, 2026
7117204
Document TLS connection state machine
im-0 Jun 4, 2026
5e02b75
Test Rust -> Python serializer with py_serde::PyObjectSerializer
im-0 Jun 4, 2026
9feac87
Add documentation for Rust -> Python serializer
im-0 Jun 4, 2026
c56a8d4
Serialize unit struct into Python string with just a struct name
im-0 Jun 4, 2026
21a68f2
Enable ThreadedTests.test_ssl_in_multiple_threads
im-0 Jun 4, 2026
8a43b8e
Workaround for test.test_ssl.ThreadedTests.test_wrong_cert_tls*
im-0 Jun 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
284 changes: 46 additions & 238 deletions Cargo.lock

Large diffs are not rendered by default.

16 changes: 6 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ threading = ["rustpython-vm/threading", "rustpython-stdlib/threading"]
sqlite = ["rustpython-stdlib/sqlite"]
ssl = ["host_env"]
ssl-rustls = ["ssl", "rustpython-stdlib/ssl-rustls"]
ssl-rustls-aws-lc = ["ssl-rustls", "dep:rustls", "rustls/aws_lc_rs"]
ssl-rustls-aws-lc = ["ssl-rustls", "dep:rustls", "rustls/aws_lc_rs", "rustpython-stdlib/ssl-rustls-aws-lc"]
ssl-rustls-aws-lc-fips = ["ssl-rustls-aws-lc", "rustls/fips"]
ssl-openssl = ["ssl", "rustpython-stdlib/ssl-openssl"]
ssl-openssl-vendor = ["ssl-openssl", "rustpython-stdlib/ssl-openssl-vendor"]
Expand All @@ -49,7 +49,6 @@ env_logger = "0.11"
flamescope = { version = "0.1.2", optional = true }

rustls = { workspace = true, optional = true }
rustls-graviola = { workspace = true, optional = true }

[target.'cfg(windows)'.dependencies]
libc = { workspace = true }
Expand All @@ -62,6 +61,7 @@ criterion = { workspace = true }
pyo3 = { workspace = true, features = ["auto-initialize"] }
rustpython-stdlib = { workspace = true }
ruff_python_parser = { workspace = true }
rustls-graviola = { workspace = true }

[[bench]]
name = "execution"
Expand All @@ -79,7 +79,6 @@ path = "src/main.rs"
name = "custom_tls_providers"
path = "examples/custom_tls_providers.rs"
required-features = [
"rustls-graviola",
"rustls/ring",
"rustpython-pylib/freeze-stdlib",
"rustpython-stdlib/ssl-rustls",
Expand Down Expand Up @@ -197,7 +196,6 @@ ruff_source_file = { package = "rustpython-ruff_source_file", version = "0.15.8"
# ruff_text_size = { git = "https://github.com/astral-sh/ruff.git", rev = "c2a8815842f9dc5d24ec19385eae0f1a7188b0d9" }
# ruff_source_file = { git = "https://github.com/astral-sh/ruff.git", rev = "c2a8815842f9dc5d24ec19385eae0f1a7188b0d9" }

der = { version = "0.8", features = ["alloc", "oid", "pem", "zeroize"] }
phf = { version = "0.13.1", default-features = false, features = ["macros"]}
adler32 = "1.2.0"
approx = "0.5.1"
Expand Down Expand Up @@ -273,7 +271,6 @@ optional = "0.5"
parking_lot = "0.12.3"
paste = "1.0.15"
pbkdf2 = "0.13"
pem-rfc7468 = "1.0"
pkcs8 = "0.11"
proc-macro2 = "1.0.105"
psm = "0.1"
Expand All @@ -287,11 +284,12 @@ result-like = "0.5.0"
rustix = { version = "1.1", features = ["event", "param", "system"] }
rustls = { version = "0.23.39", default-features = false }
rustls-graviola = "0.3"
rustls-native-certs = "0.8"
rustls-pemfile = "2.2"
rustls-pki-types = { version = "1.14.1", default-features = false }
rustls-platform-verifier = "0.7"
webpki = { package = "rustls-webpki", version = "0.103.13", default-features = false }
rustyline = "18"
serde = { package = "serde_core", version = "1.0.225", default-features = false, features = ["alloc"] }
serde = { version = "1.0.225", default-features = false, features = ["alloc", "derive"] }
serde_bytes = { version = "0.11.19", default-features = false, features = ["std"] }
schannel = "0.1.29"
scopeguard = "1"
serde-wasm-bindgen = "0.6.5"
Expand Down Expand Up @@ -326,9 +324,7 @@ windows-sys = "0.61.2"
wasm-bindgen = "0.2.106"
wasm-bindgen-futures = "0.4"
web-sys = "0.3"
webpki-roots = "1.0"
which = "8"
x509-cert = "0.2.5"
x509-parser = "0.18"
xml = "1.3"
writeable = "0.6"
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_asyncio/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -1288,7 +1288,6 @@ def test_create_unix_server_ssl_verified(self):
server.close()
self.loop.run_until_complete(proto.done)

@unittest.expectedFailure # TODO: RUSTPYTHON; - SSL peer certificate format differs
@unittest.skipIf(ssl is None, 'No ssl module')
def test_create_server_ssl_verified(self):
proto = MyProto(loop=self.loop)
Expand Down
12 changes: 0 additions & 12 deletions Lib/test/test_asyncio/test_sendfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -566,32 +566,20 @@ class EPollEventLoopTests(SendfileTestsBase,
def create_event_loop(self):
return asyncio.SelectorEventLoop(selectors.EpollSelector())

@unittest.skipIf(sys.platform != "win32", "TODO: RUSTPYTHON; Flaky on CI")
def test_sendfile_ssl_pre_and_post_data(self):
return super().test_sendfile_ssl_pre_and_post_data()

if hasattr(selectors, 'PollSelector'):
class PollEventLoopTests(SendfileTestsBase,
test_utils.TestCase):

def create_event_loop(self):
return asyncio.SelectorEventLoop(selectors.PollSelector())

@unittest.skipIf(sys.platform != "win32", "TODO: RUSTPYTHON; Flaky on CI")
def test_sendfile_ssl_pre_and_post_data(self):
return super().test_sendfile_ssl_pre_and_post_data()

# Should always exist.
class SelectEventLoopTests(SendfileTestsBase,
test_utils.TestCase):

def create_event_loop(self):
return asyncio.SelectorEventLoop(selectors.SelectSelector())

@unittest.skipIf(sys.platform != "win32", "TODO: RUSTPYTHON; Flaky on CI")
def test_sendfile_ssl_pre_and_post_data(self):
return super().test_sendfile_ssl_pre_and_post_data()


if __name__ == '__main__':
unittest.main()
2 changes: 0 additions & 2 deletions Lib/test/test_asyncio/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -738,7 +738,6 @@ async def client(addr):
asyncio.wait_for(client(srv.addr),
timeout=support.SHORT_TIMEOUT))

@unittest.expectedFailure # TODO: RUSTPYTHON; - gc.collect() doesn't release SSLContext properly
def test_create_connection_memory_leak(self):
HELLO_MSG = b'1' * self.PAYLOAD_SIZE

Expand Down Expand Up @@ -1617,7 +1616,6 @@ async def test():
else:
self.fail('Unexpected ResourceWarning: {}'.format(cm.warning))

@unittest.expectedFailure # TODO: RUSTPYTHON; - gc.collect() doesn't release SSLContext properly
def test_handshake_timeout_handler_leak(self):
s = socket.socket(socket.AF_INET)
s.bind(('127.0.0.1', 0))
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_httplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -2098,6 +2098,7 @@ def test_networked_trusted_by_default_cert(self):
h.close()
self.assertIn('text/html', content_type)

@unittest.skipIf("rustls" in __import__('ssl').OPENSSL_VERSION, "TODO: RUSTPYTHON; rustls does not support server host name verification by CN")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does this pass before?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this doesn't crash/hangs Id prefer to have it as

Suggested change
@unittest.skipIf("rustls" in __import__('ssl').OPENSSL_VERSION, "TODO: RUSTPYTHON; rustls does not support server host name verification by CN")
@unittest.expectedFailureIf("rustls" in __import__('ssl').OPENSSL_VERSION, "TODO: RUSTPYTHON; rustls does not support server host name verification by CN")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@youknowone There is a hack for a case when server certificate is loaded into a certificate store:

verify_hostname(end_entity, server_name)?;

It is possible to closer replicate OpenSSL behavior but for this it will better to just fork https://github.com/rustls/webpki instead of adding more hacks around it (I added some too and I do not like it). And it will not work with rustls_platform_verifier anyway as its behavior is not configurable (whether we need to use rustls_platform_verifier at all is a different question).

def test_networked_good_cert(self):
# We feed the server's cert as a validating cert
import ssl
Expand Down
16 changes: 8 additions & 8 deletions Lib/test/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1346,6 +1346,7 @@ def test_load_verify_cadata(self):
with self.assertRaises(ssl.SSLError):
ctx.load_verify_locations(cadata=cacert_der + b"A")

@unittest.expectedFailureIf("rustls" in ssl.OPENSSL_VERSION, "TODO: RUSTPYTHON; rustls does not support custom DH parameters")
def test_load_dh_params(self):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
try:
Expand Down Expand Up @@ -1481,6 +1482,7 @@ def test_load_default_certs(self):
self.assertRaises(TypeError, ctx.load_default_certs, 'SERVER_AUTH')

@unittest.skipIf(sys.platform == "win32", "not-Windows specific")
@unittest.expectedFailureIf("rustls" in ssl.OPENSSL_VERSION, "TODO: RUSTPYTHON; rustls does not support certificate lazy loading")
def test_load_default_certs_env(self):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
with os_helper.EnvironmentVarGuard() as env:
Expand All @@ -1492,6 +1494,7 @@ def test_load_default_certs_env(self):
@unittest.skipUnless(sys.platform == "win32", "Windows specific")
@unittest.skipIf(support.Py_DEBUG,
"Debug build does not share environment between CRTs")
@unittest.expectedFailureIf("rustls" in ssl.OPENSSL_VERSION, "TODO: RUSTPYTHON; rustls does not support certificate lazy loading")
def test_load_default_certs_env_windows(self):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.load_default_certs()
Expand Down Expand Up @@ -2092,6 +2095,7 @@ def test_ciphers(self):
cert_reqs=ssl.CERT_NONE, ciphers="^$:,;?*'dorothyx")
s.connect(self.server_addr)

@unittest.expectedFailureIf("rustls" in ssl.OPENSSL_VERSION, "TODO: RUSTPYTHON; capath certificates are loaded eagerly instead of on request")
def test_get_ca_certs_capath(self):
# capath certs are loaded on request
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
Expand Down Expand Up @@ -2863,7 +2867,6 @@ def test_echo(self):
'Cannot create a client socket with a PROTOCOL_TLS_SERVER context',
str(e.exception))

@unittest.skip("TODO: RUSTPYTHON; flaky")
@unittest.skipUnless(support.Py_GIL_DISABLED, "test is only useful if the GIL is disabled")
def test_ssl_in_multiple_threads(self):
# See GH-124984: OpenSSL is not thread safe.
Expand Down Expand Up @@ -3071,6 +3074,7 @@ def test_ecc_cert(self):

@unittest.skipUnless(IS_OPENSSL_3_0_0,
"test requires RFC 5280 check added in OpenSSL 3.0+")
@unittest.expectedFailureIf("rustls" in ssl.OPENSSL_VERSION, "TODO: RUSTPYTHON; rustls cert verification does not match OpenSSL's VERIFY_X509_STRICT")
def test_verify_strict(self):
# verification fails by default, since the server cert is non-conforming
client_context = ssl.create_default_context()
Expand Down Expand Up @@ -3996,7 +4000,6 @@ def test_default_ecdh_curve(self):
s.connect((HOST, server.port))
self.assertIn("ECDH", s.cipher()[0])

@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.skipUnless("tls-unique" in ssl.CHANNEL_BINDING_TYPES,
"'tls-unique' channel binding not available")
def test_tls_unique_channel_binding(self):
Expand Down Expand Up @@ -4259,7 +4262,6 @@ def servername_cb(ssl_sock, server_name, initial_context):
self.check_common_name(stats, SIGNED_CERTFILE_HOSTNAME)
self.assertEqual(calls, [])

@unittest.expectedFailure # TODO: RUSTPYTHON; + TLSV1_ALERT_ACCESS_DENIED
def test_sni_callback_alert(self):
# Returning a TLS alert is reflected to the connecting client
server_context, other_context, client_context = self.sni_contexts()
Expand All @@ -4273,7 +4275,6 @@ def cb_returning_alert(ssl_sock, server_name, initial_context):
sni_name='supermessage')
self.assertEqual(cm.exception.reason, 'TLSV1_ALERT_ACCESS_DENIED')

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_sni_callback_raising(self):
# Raising fails the connection with a TLS handshake failure alert.
server_context, other_context, client_context = self.sni_contexts()
Expand All @@ -4293,7 +4294,6 @@ def cb_raising(ssl_sock, server_name, initial_context):
self.assertRegex(cm.exception.reason, regex)
self.assertEqual(catch.unraisable.exc_type, ZeroDivisionError)

@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'SSLEOFError' object has no attribute 'reason'
def test_sni_callback_wrong_return_type(self):
# Returning the wrong return type terminates the TLS connection
# with an internal error alert.
Expand Down Expand Up @@ -4359,7 +4359,7 @@ def test_sendfile(self):
s.sendfile(file)
self.assertEqual(s.recv(1024), TEST_DATA)

@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailureIf("rustls" in ssl.OPENSSL_VERSION, "TODO: RUSTPYTHON; AttributeError: 'NoneType' object has no attribute 'id'")
def test_session(self):
client_context, server_context, hostname = testing_context()
# TODO: sessions aren't compatible with TLSv1.3 yet
Expand Down Expand Up @@ -4417,7 +4417,7 @@ def test_session(self):
self.assertEqual(sess_stat['accept'], 4)
self.assertEqual(sess_stat['hits'], 2)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: False != True
@unittest.expectedFailureIf("rustls" in ssl.OPENSSL_VERSION, "TODO: RUSTPYTHON; AssertionError: None is not true")
def test_session_handling(self):
client_context, server_context, hostname = testing_context()
client_context2, _, _ = testing_context()
Expand Down Expand Up @@ -5036,7 +5036,7 @@ def msg_cb(conn, direction, version, content_type, msg_type, data):
with self.assertRaises(TypeError):
client_context._msg_callback = object()

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: ('read', <TLSVersion.TLSv1_2: 771>, <_TLSContentType.HANDSHAKE: 22>, <_TLSMessageType.SERVER_KEY_EXCHANGE: 12>) not found in []
@unittest.expectedFailureIf("rustls" in ssl.OPENSSL_VERSION, "TODO: RUSTPYTHON; AssertionError: ('read', <TLSVersion.TLSv1_2: 771>, <_TLSContentType.HANDSHAKE: 22>, <_TLSMessageType.SERVER_KEY_EXCHANGE: 12>) not found in []")
def test_msg_callback_tls12(self):
client_context, server_context, hostname = testing_context()
client_context.maximum_version = ssl.TLSVersion.TLSv1_2
Expand Down
18 changes: 9 additions & 9 deletions crates/stdlib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
# SSL backends
ssl = ["host_env"]
ssl-rustls = ["__ssl-rustls", "rustls/custom-provider"]
ssl-rustls-aws-lc = ["ssl-rustls", "rustls/aws_lc_rs"]
ssl-rustls-fips = ["ssl-rustls-aws-lc", "rustls/fips"]
ssl-openssl = ["ssl", "openssl", "openssl-sys", "foreign-types-shared", "openssl-probe"]
ssl-openssl-vendor = ["ssl-openssl", "openssl/vendored"]
tkinter = ["dep:tk-sys", "dep:tcl-sys", "dep:widestring"]
flame-it = ["flame"]

__ssl-rustls = ["ssl", "rustls", "rustls-native-certs", "rustls-pemfile", "rustls-platform-verifier", "x509-cert", "x509-parser", "der", "pem-rfc7468", "webpki-roots", "oid-registry", "pkcs8"]
__ssl-rustls = ["ssl", "rustls", "rustls-pki-types", "rustls-platform-verifier", "webpki", "x509-parser", "oid-registry", "pkcs8", "serde", "rustpython-vm/serde"]

[dependencies]
# rustpython crates
Expand Down Expand Up @@ -93,7 +95,7 @@

# tkinter
tk-sys = { workspace = true, optional = true }
tcl-sys = { workspace = true, optional = true }

Check warning on line 98 in crates/stdlib/Cargo.toml

View workflow job for this annotation

GitHub Actions / cargo shear

shear/unused_optional_dependency

unused optional dependency `tcl-sys`
widestring = { workspace = true, optional = true }
chrono.workspace = true

Expand All @@ -116,16 +118,13 @@

# Rustls dependencies (optional, for ssl-rustls feature)
rustls = { workspace = true, default-features = false, features = ["std", "tls12"], optional = true }
rustls-native-certs = { workspace = true, optional = true }
rustls-pemfile = { workspace = true, optional = true }
rustls-pki-types = { workspace = true, optional = true }
rustls-platform-verifier = { workspace = true, optional = true }
x509-cert = { workspace = true, features = ["pem", "builder"], optional = true }
webpki = { workspace = true, optional = true }

Check warning on line 123 in crates/stdlib/Cargo.toml

View workflow job for this annotation

GitHub Actions / cargo shear

shear/unused_optional_dependency

unused optional dependency `webpki`
x509-parser = { workspace = true, optional = true }
der = { workspace = true, optional = true }
pem-rfc7468 = { workspace = true, features = ["alloc"], optional = true }
webpki-roots = { workspace = true, optional = true }
oid-registry = { workspace = true, features = ["x509", "pkcs1", "nist_algs"], optional = true }
pkcs8 = { workspace = true, features = ["encryption", "pkcs5", "pem"], optional = true }
pkcs8 = { workspace = true, features = ["encryption", "pkcs5"], optional = true }
oid-registry = { workspace = true, optional = true }

Check warning on line 126 in crates/stdlib/Cargo.toml

View workflow job for this annotation

GitHub Actions / cargo shear

shear/unused_optional_dependency

unused optional dependency `oid-registry`
serde = { workspace = true, optional = true }

[target.'cfg(not(any(target_os = "android", target_arch = "wasm32")))'.dependencies]
libsqlite3-sys = { workspace = true, features = ["bundled"], optional = true }
Expand All @@ -141,6 +140,7 @@

[dev-dependencies]
insta = { workspace = true }
rustls = { workspace = true, default-features = false, features = ["aws_lc_rs", "std", "tls12"] }
rustpython-pylib = { workspace = true, features = [ "freeze-stdlib" ] }


Expand Down
Loading
Loading