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
0cf37b3
Add a test for ssl with ssl.MemoryBIO
im-0 May 24, 2026
d625289
Add Serde serializer that converts structs into basic Python objects
im-0 May 26, 2026
ce22219
Support module monkey-patching on VM level
im-0 May 30, 2026
cac2ee7
Add OpenSSL data files used to build OID and NID tables
im-0 May 27, 2026
a54bbf0
Enable/disable ssl-related tests from cpython test suite
im-0 May 30, 2026
1e25e1a
Revert "Skip flaky tests (#7961)"
im-0 May 31, 2026
2cda4d8
Revert "Skip flaky test (#7967)"
im-0 May 31, 2026
0cb9242
Almost a complete rewrite of rustls integration
im-0 May 24, 2026
60cf7e1
Skip test.test_httplib.HTTPSTest.test_networked_good_cert if rustls i…
im-0 Jun 1, 2026
d06b7f5
On client, both CERT_OPTIONAL and CERT_REQUIRED enable cert verification
im-0 Jun 2, 2026
3acc916
Rename stdlib_ssl_bio_unencrypted_trailer.py -> stdlib_ssl.py
im-0 Jun 3, 2026
c5be311
Move crates/stdlib/src/rustls-data -> crates/stdlib/rustls-data
im-0 Jun 3, 2026
489cd9c
Use expectedFailureIf() instead of skipIf() for rustls-specific skips
im-0 Jun 3, 2026
f2f3f2a
Fix `cargo shear` warn (graviola dep for examples/custom_tls_provider…
im-0 Jun 3, 2026
cdd4433
Call SNI callback earlier and re-read the context after it
im-0 Jun 3, 2026
9bb9e45
Remove allow_threads() from rustls integration
im-0 Jun 3, 2026
bc9d497
Use certs from both SSL_CERT_FILE and SSL_CERT_DIR env vars if present
im-0 Jun 3, 2026
76c92ae
Keep rustls error in SslError until converted into Python error
im-0 Jun 3, 2026
e3c9596
Ensure that TLS alerts are sent back on TLS errors
im-0 Jun 3, 2026
bb0d7ef
Document TLS connection state machine
im-0 Jun 4, 2026
02eb49f
Test Rust -> Python serializer with py_serde::PyObjectSerializer
im-0 Jun 4, 2026
a25d257
Add documentation for Rust -> Python serializer
im-0 Jun 4, 2026
29013e3
Serialize unit struct into Python string with just a struct name
im-0 Jun 4, 2026
596b0da
Enable ThreadedTests.test_ssl_in_multiple_threads
im-0 Jun 4, 2026
f7117cf
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")
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 @@ sqlite = ["dep:libsqlite3-sys"]
# 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 @@ -116,16 +118,13 @@ foreign-types-shared = { workspace = true, optional = true }

# 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 }
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 }
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 @@ system-configuration = { workspace = true }

[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