Skip to content

Conversation

@youknowone
Copy link
Member

@youknowone youknowone commented Oct 23, 2025

Summary by CodeRabbit

  • New Features

    • Added TLS 1.1 and TLS 1.2 protocol support
    • Enhanced SSL certificate chain inspection and validation, including access to verified chains
    • Added cipher suite discovery and shared-cipher computation
    • Exposed ALPN negotiation info, channel binding, post-handshake verification, and improved shutdown behavior
    • Added ECDH curve configuration and verify-flag controls
  • Chores

    • Updated OpenSSL-related dependency version

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 24, 2025

Walkthrough

Bumps openssl-sys to 0.9.110 and expands the SSL/TLS implementation: adds TLS1.1/1.2, enables ECDH, introduces new OpenSSL symbol shims, and exposes multiple new PySslContext/PySslSocket APIs for ciphers, ECDH curve, verify flags, verified chains, ALPN, channel binding, post-handshake verification, and shutdown.

Changes

Cohort / File(s) Summary
Dependency Update
stdlib/Cargo.toml
Bumps openssl-sys from 0.9.80 to 0.9.110 under [target.'cfg(not(target_arch = "wasm32"))'.dependencies] (keeps optional = true).
SSL Module Expansion
stdlib/src/ssl.rs
Broadens OpenSSL feature gating (ossl111), adds TLS constants (TLSv1_1, TLSv1_2), enables HAS_ECDH, adds OpenSSL shims (SSL_get_ciphers, SSL_get_client_ciphers, X509_check_ca, SSL_verify_client_post_handshake), extends protocol/version enums and dispatch, adds PySslContext methods (get_ciphers, set_ecdh_curve, verify_flags getter/setter), adds PySslSocket methods/getters (get_verified_chain, shared_ciphers, selected_alpn_protocol, get_channel_binding, verify_client_post_handshake, shutdown), updates PySslSession id signature, and refines error handling, certificate/chain processing, and runtime caching for verify paths.

Sequence Diagram(s)

sequenceDiagram
    participant Py as Python code
    participant Ctx as PySslContext
    participant Sock as PySslSocket
    participant OpenSSL as OpenSSL FFI

    Note over Ctx,OpenSSL #D6EAF8: Context operations
    Py->>Ctx: get_ciphers()
    Ctx->>OpenSSL: SSL_get_ciphers / EVP calls
    OpenSSL-->>Ctx: cipher list
    Ctx-->>Py: list of cipher dicts

    Py->>Ctx: set_ecdh_curve(curve_name)
    Ctx->>OpenSSL: OBJ_txt2nid / EC_KEY_new_by_curve_name
    OpenSSL-->>Ctx: set temp ECDH key
    Ctx-->>Py: OK / error

    Note over Sock,OpenSSL #E8F8F5: Socket runtime ops
    Py->>Sock: get_verified_chain()
    Sock->>OpenSSL: Access verified chain (X509_verify_cert / chain retrieval)
    OpenSSL-->>Sock: DER certs
    Sock-->>Py: list[bytes] | None

    Py->>Sock: shared_ciphers()
    Sock->>OpenSSL: SSL_get_client_ciphers / SSL_get_ciphers
    OpenSSL-->>Sock: client & server lists
    Sock-->>Py: intersection list

    Py->>Sock: selected_alpn_protocol()
    Sock->>OpenSSL: SSL_get0_alpn_selected
    OpenSSL-->>Sock: protocol | null
    Sock-->>Py: protocol | None

    Py->>Sock: get_channel_binding("tls-unique")
    Sock->>OpenSSL: SSL_get_finished / TLS APIs
    OpenSSL-->>Sock: binding bytes
    Sock-->>Py: bytes | None

    Py->>Sock: verify_client_post_handshake()
    Sock->>OpenSSL: SSL_verify_client_post_handshake
    OpenSSL-->>Sock: result / error
    Sock-->>Py: OK / raises

    Py->>Sock: shutdown()
    Sock->>OpenSSL: SSL_shutdown sequence
    OpenSSL-->>Sock: closed / error
    Sock-->>Py: socket | raises
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Pay extra attention to:
    • Conditional compilation and OpenSSL version gating (ossl111).
    • Correct FFI bindings for newly added OpenSSL symbols and their availability checks.
    • Error construction and propagation to Python (exceptions with reason/library fields).
    • Certificate chain handling, channel binding bounds/format, and ALPN/cipher intersection logic.
    • New public APIs' signatures and ABI/FFI safety (PySslContext/PySslSocket/PySslSession).

Poem

🐰 I hopped through headers, ciphers in tow,
ECDH seeds and TLS1.2 in the snow,
Chains verified, ALPN picked true,
Channel-bound whispers — secure and new,
OpenSSL updated, the warren hums anew.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Implement more SSL methods" directly and accurately summarizes the primary change in the pull request. The changeset's main focus is adding new SSL/TLS functionality, including multiple new methods on PySslContext and PySslSocket classes (such as get_ciphers, set_ecdh_curve, get_verified_chain, shared_ciphers, and others), along with updated protocol support and a dependency bump. The title is concise, clear, and avoids noise or vague terminology, making it easy for a teammate reviewing the git history to understand that this PR introduces new SSL methods.
Docstring Coverage ✅ Passed Docstring coverage is 88.89% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c3dad1d and 91df67c.

⛔ Files ignored due to path filters (1)
  • Lib/test/test_httplib.py is excluded by !Lib/**
📒 Files selected for processing (1)
  • stdlib/src/ssl.rs (26 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.rs: Format Rust code with the default rustfmt style (run cargo fmt)
Run clippy and fix any warnings or lints introduced by your changes
Follow Rust best practices for error handling and memory management

Files:

  • stdlib/src/ssl.rs
{vm,stdlib}/**/*.rs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use RustPython macros (pyclass, pymodule, pyfunction, etc.) when implementing Python functionality in Rust

Files:

  • stdlib/src/ssl.rs
🧬 Code graph analysis (1)
stdlib/src/ssl.rs (5)
derive/src/lib.rs (1)
  • pymodule (211-215)
derive-impl/src/lib.rs (1)
  • pymodule (58-60)
derive-impl/src/util.rs (1)
  • with (305-317)
Lib/ssl.py (1)
  • get_default_verify_paths (432-443)
vm/src/exceptions.rs (1)
  • cstring_error (1143-1145)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Check the WASM package and demo
  • GitHub Check: Run snippets and cpython tests (macos-latest)
  • GitHub Check: Run tests under miri
  • GitHub Check: Check Rust code with rustfmt and clippy
  • GitHub Check: Run snippets and cpython tests on wasm-wasi
  • GitHub Check: Run rust tests (ubuntu-latest)
  • GitHub Check: Run snippets and cpython tests (windows-latest)
  • GitHub Check: Run snippets and cpython tests (ubuntu-latest)
  • GitHub Check: Ensure compilation on various targets
  • GitHub Check: Run rust tests (macos-latest)
  • GitHub Check: Run rust tests (windows-latest)
🔇 Additional comments (17)
stdlib/src/ssl.rs (17)

29-29: LGTM - OpenSSL 1.1.1 feature enabled.

The pymodule attribute now includes ossl111, enabling OpenSSL 1.1.1+ features like TLS 1.3 support and post-handshake authentication.


125-128: LGTM - TLS 1.1/1.2 and ECDH support enabled.

The new protocol constants and ECDH flag properly expose additional TLS versions and ECDH support to Python code.

Also applies to: 159-159


393-440: LGTM - Efficient lazy caching of certificate paths.

The lazy static initialization properly caches OpenSSL certificate paths and environment variable names. The unsafe CStr conversions are safe because they operate on static strings from OpenSSL.


624-653: LGTM - Proper cipher enumeration.

The method correctly iterates over OpenSSL's cipher stack and converts each cipher to a Python dictionary with name, protocol, and secret_bits fields.


655-680: LGTM - Correct ECDH curve configuration.

The method properly validates the curve name, looks up the NID, creates the EC key, and sets it on the context using a write lock.


888-901: LGTM - Correct CA certificate filtering.

The method now properly filters certificates to include only those with CA=TRUE in Basic Constraints using X509_check_ca().


934-947: LGTM - Important protocol/socket type validation.

These checks prevent creating a client socket with a server-only context (and vice versa), which would lead to confusing handshake failures.


1208-1237: LGTM - Proper verified chain extraction.

The method correctly retrieves the verified certificate chain using SSL_get0_verified_chain() and converts each certificate to DER format. The borrowed pointer handling is safe.


1254-1315: LGTM - Correct shared cipher computation.

The pointer comparison approach is correct for OpenSSL cipher suites, as cipher objects are cached globally. This matches CPython's implementation.


1317-1340: LGTM - Proper ALPN protocol retrieval.

The method correctly retrieves the negotiated ALPN protocol using SSL_get0_alpn_selected() and handles the case where no protocol was negotiated.


1342-1383: LGTM - Correct TLS channel binding implementation.

The XOR logic for determining which finished message to use matches CPython's implementation and correctly handles both session resumption and client/server roles.


1385-1406: LGTM - Proper post-handshake authentication.

The method correctly implements post-handshake client verification for TLS 1.3, with appropriate version checks and error handling.


1408-1435: LGTM - Proper SSL shutdown handling.

The method correctly performs SSL shutdown and gracefully handles non-blocking operations by treating WANT_READ/WANT_WRITE as acceptable states.


1744-1763: LGTM - Correct OpenSSL function shims.

These extern C declarations properly expose OpenSSL functions that aren't yet in openssl-sys, with appropriate version guards.


1970-2002: LGTM - Enhanced SSL error information.

The error conversion now properly exposes library and reason attributes on SSL exceptions, matching CPython's behavior. The downcast().unwrap() on line 2002 is safe because we just created the exception from ssl_error(vm).


2099-2100: LGTM - Correct X.509 version normalization.

The fix properly converts OpenSSL's 0-based version numbering (0=v1, 1=v2, 2=v3) to Python's 1-based numbering (1=v1, 2=v2, 3=v3).


2313-2313: LGTM - Improved error propagation.

The changes properly propagate errors from valid_uses() and collection operations instead of potentially panicking, improving robustness.

Also applies to: 2318-2324


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Collaborator

@ShaharNaveh ShaharNaveh left a comment

Choose a reason for hiding this comment

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

@youknowone youknowone force-pushed the ssl branch 5 times, most recently from 8a06226 to 2f9375a Compare October 24, 2025 12:53
@youknowone youknowone force-pushed the ssl branch 4 times, most recently from 9c870a9 to a2c04ed Compare October 25, 2025 13:49
@youknowone youknowone marked this pull request as ready for review October 25, 2025 15:26
@youknowone
Copy link
Member Author

@ShaharNaveh I cut the commits to find what's breaking CI. test_ssl and the test files will be included in next patch

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
stdlib/src/ssl.rs (3)

492-500: Protocol constants added but not enforced in context creation.

PROTOCOL_TLSv1 / TLSv1_1 / TLSv1_2 map to SslMethod::tls(), but min/max versions aren’t restricted. As-is, TLS 1.3 (or higher) may still negotiate, breaking CPython parity. Set explicit min/max (or disable via options) per selected protocol.

Apply one of the following (preferred: min/max API):

@@
-            let method = match proto {
+            let method = match proto {
                 // SslVersion::Ssl3 => unsafe { ssl::SslMethod::from_ptr(sys::SSLv3_method()) },
                 SslVersion::Tls => ssl::SslMethod::tls(),
-                SslVersion::Tls1 => ssl::SslMethod::tls(),
-                SslVersion::Tls1_1 => ssl::SslMethod::tls(),
-                SslVersion::Tls1_2 => ssl::SslMethod::tls(),
+                SslVersion::Tls1 | SslVersion::Tls1_1 | SslVersion::Tls1_2 => ssl::SslMethod::tls(),
                 SslVersion::TlsClient => ssl::SslMethod::tls_client(),
                 SslVersion::TlsServer => ssl::SslMethod::tls_server(),
                 _ => return Err(vm.new_value_error("invalid protocol version")),
             };
@@
-            let mut options = SslOptions::ALL & !SslOptions::DONT_INSERT_EMPTY_FRAGMENTS;
+            let mut options = SslOptions::ALL & !SslOptions::DONT_INSERT_EMPTY_FRAGMENTS;
@@
             options |= SslOptions::NO_COMPRESSION;
             options |= SslOptions::CIPHER_SERVER_PREFERENCE;
             options |= SslOptions::SINGLE_DH_USE;
             options |= SslOptions::SINGLE_ECDH_USE;
             builder.set_options(options);
+
+            // Enforce exact protocol selection for legacy constants.
+            match proto {
+                SslVersion::Tls1 => {
+                    builder.set_min_proto_version(Some(ssl::SslVersion::TLS1)).unwrap();
+                    builder.set_max_proto_version(Some(ssl::SslVersion::TLS1)).unwrap();
+                }
+                SslVersion::Tls1_1 => {
+                    builder.set_min_proto_version(Some(ssl::SslVersion::TLS1_1)).unwrap();
+                    builder.set_max_proto_version(Some(ssl::SslVersion::TLS1_1)).unwrap();
+                }
+                SslVersion::Tls1_2 => {
+                    builder.set_min_proto_version(Some(ssl::SslVersion::TLS1_2)).unwrap();
+                    builder.set_max_proto_version(Some(ssl::SslVersion::TLS1_2)).unwrap();
+                }
+                _ => {}
+            }

Also applies to: 514-526, 125-129


767-773: ALPN callback slice bug (start..end range is wrong).

&client[pos..proto.len()] will panic when pos > 0 and returns the wrong slice. Use pos..pos + proto.len().

-                    let pos = memchr::memmem::find(client, proto)
+                    let pos = memchr::memmem::find(client, proto)
                         .expect("selected alpn proto should be present in client protos");
-                    Ok(&client[pos..proto.len()])
+                    Ok(&client[pos..pos + proto.len()])

165-175: HAS_SSLv2/SSLv3 constants are misleading on modern OpenSSL.

On OpenSSL ≥1.1.0 these protocols are removed/disabled; hardcoding true can mislead feature detection in Python code. Consider compile-time gating to reflect real support.

-    const HAS_SSLv2: bool = true;
-    const HAS_SSLv3: bool = true;
+    // Reflect actual availability (CPython-style semantics)
+    const HAS_SSLv2: bool = false;
+    const HAS_SSLv3: bool = cfg!(ossl101);
♻️ Duplicate comments (1)
stdlib/src/ssl.rs (1)

1256-1279: selected_alpn_protocol: LGTM after fixing the slice bug above.

API usage is correct; returns None when not negotiated.

🧹 Nitpick comments (5)
stdlib/src/ssl.rs (5)

585-614: get_ciphers: OK overall; minor safety/robustness nits.

Looks good. Optionally cast num_ciphers to i32 and guard against negative returns from OPENSSL_sk_num (defensive).


616-641: set_ecdh_curve behavior on OpenSSL ≥1.1.0.

set_tmp_ecdh is effectively a no-op from 1.1.0 onward (auto ECDH). Consider documenting this or gating to older OpenSSL to avoid surprising users.


1193-1254: shared_ciphers O(N×M) but acceptable; consider set-based lookup if needed.

Works under ossl110+. If performance becomes an issue, build a HashSet of client cipher pointers for O(N).


1281-1323: Channel binding (tls-unique): LGTM.

XOR of session_reused and is_client matches CPython. Consider returning NotImplementedError for other cb types to align messages.


1347-1374: shutdown semantics vs CPython unwrap; potential reuse after unwrap.

This returns the underlying socket without detaching/invalidating the SSLSocket. Consider aligning with CPython’s unwrap() (or clearly documenting semantics), and ensure subsequent operations on the SSLSocket error cleanly. Also consider looping SSL_shutdown twice for bidirectional shutdown per OpenSSL guidance.

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d23f6e and c3dad1d.

📒 Files selected for processing (2)
  • stdlib/Cargo.toml (1 hunks)
  • stdlib/src/ssl.rs (13 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.rs: Format Rust code with the default rustfmt style (run cargo fmt)
Run clippy and fix any warnings or lints introduced by your changes
Follow Rust best practices for error handling and memory management

Files:

  • stdlib/src/ssl.rs
{vm,stdlib}/**/*.rs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use RustPython macros (pyclass, pymodule, pyfunction, etc.) when implementing Python functionality in Rust

Files:

  • stdlib/src/ssl.rs
🧬 Code graph analysis (1)
stdlib/src/ssl.rs (2)
derive-impl/src/util.rs (1)
  • with (305-317)
vm/src/exceptions.rs (1)
  • cstring_error (1143-1145)
🔇 Additional comments (5)
stdlib/Cargo.toml (1)

110-110: openssl-sys bump looks fine; please verify link across all CI targets.

0.10.x openssl crate works with 0.9.110 openssl-sys. Still, ensure CI covers Linux, macOS, Windows, and vendored builds to catch symbol availability regressions.

stdlib/src/ssl.rs (4)

1147-1176: get_verified_chain: LGTM.

Uses SSL_get0_verified_chain and DER-encodes. Null/empty handling is correct.


1324-1345: verify_client_post_handshake: LGTM.

Correctly gated on ossl111; returns NotImplemented otherwise.


1587-1587: Minor: Good fix truncating read buffer before returning.

Prevents trailing zeros; matches Python semantics.


1683-1697: Extern FFI symbols: gate and test across providers.

Declaring missing symbols locally is fine; ensure:

  • SSL_get_ciphers is present on all supported OpenSSL/LibreSSL builds.
  • SSL_get_client_ciphers and SSL_verify_client_post_handshake remain under proper cfgs.
    Please verify CI matrix includes LibreSSL (if supported) or guard with additional cfgs to avoid link errors.

@youknowone youknowone merged commit 2e7a8b4 into RustPython:main Oct 26, 2025
12 checks passed
This was referenced Oct 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants