Skip to content

Conversation

@youknowone
Copy link
Member

@youknowone youknowone commented Nov 10, 2025

close #3839
close #5316

Summary by CodeRabbit

  • New Features

    • Rustls set as default TLS provider with optional OpenSSL and vendored/FIPS variants.
    • Expanded TLS capabilities: certificate parsing/inspection, Python-exposed Certificate API, chain building, hostname/IP verification, CRL and partial-chain modes, and OID lookup utilities.
    • OpenSSL-compatible compatibility layer exposing familiar TLS configuration and verification flags.
  • Chores

    • Updated TLS feature flags, CI workflow, and condensed README TLS guidance.
    • Added new spell-check terms and a minor tracing/log optimization.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 10, 2025

Walkthrough

Adds rustls as the default TLS backend with mutually-exclusive backend features (ssl-rustls / ssl-openssl), implements a rustls↔OpenSSL compatibility layer, introduces extensive certificate/OID utilities and loaders, adds OpenSSL PyO3 Certificate bindings, and updates CI/build and feature wiring to support no-SSL checks.

Changes

Cohort / File(s) Summary
Dictionaries & Docs
\.cspell\.dict/cpython.txt, \.cspell\.dict/rust-more.txt, README.md
Add spell-check tokens (badcert, cadata, cafile, ticketer, webpki, pemfile, rustls), reorder a token, and update README SSL-provider/guidance.
Workspace features & manifests
Cargo.toml, stdlib/Cargo.toml
Introduce ssl feature (empty) and per-backend features (ssl-rustls default, ssl-openssl, ssl-rustls-fips, ssl-vendor); rewire optional dependencies and feature composition for rustls/openssl backends.
CI / Build scripts
.github/workflows/ci.yaml, stdlib/build.rs
Add CARGO_ARGS_NO_SSL, switch default CARGO_ARGS to ssl-rustls, run cargo checks with no-SSL args for many targets; gate OpenSSL probing/cfg emission under ssl-openssl.
Core wiring
src/lib.rs, stdlib/src/lib.rs
Add compile_error guard when ssl enabled without backend; split _ssl initializers into backend-specific cfgs; add mutual-exclusion compile_error for ssl-openssl and ssl-rustls; minor install_pip cfg change.
Rustls ↔ OpenSSL compatibility
stdlib/src/ssl/compat.rs
New OpenSSL-compatible abstraction over rustls: ProtocolSettings, ServerConfigOptions, ClientConfigOptions, SslResult/SslError, verification flags/constants, error/code mappings, config scaffolding and helpers.
Rustls certificate utilities
stdlib/src/ssl/cert.rs
New Rust-based certificate parsing, CertLoader, chain building (build_verified_chain), multiple verifier implementations (Strict/Partial/Empty/CRL/etc.), hostname/IP checks, cert-to-dict helpers, and public verification utilities.
OpenSSL PyO3 certificate bindings
stdlib/src/openssl/cert.rs
Add PySSLCertificate Python class (public_bytes, get_info), X509→Python conversion helpers, cert decoder/test helper, and re-exports for OpenSSL-backed ssl module.
OID registry
stdlib/src/ssl/oid.rs
Add OID table and lookup API: OidEntry/OidRef/OidTable, find_by_nid/find_by_oid_string/find_by_name, global Lazy table, macros and tests.
Small runtime tweak
crates/vm/src/frame.rs
Micro-optimization in trace logging to avoid an intermediate to_string() allocation.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Developer
    participant Cargo as Cargo (features)
    participant CI as CI
    participant Runtime as RustPython
    participant Rustls as ssl-rustls
    participant OpenSSL as ssl-openssl
    participant Compat as ssl compat layer
    participant Certs as Cert loader / OID

    Developer->>Cargo: build (default)
    Cargo->>Runtime: enable feature "ssl-rustls"
    CI->>Runtime: run checks (no-SSL matrix and ssl-rustls)
    Runtime->>Rustls: initialize _ssl (ssl::make_module)
    Rustls->>Compat: request TLS configs
    Compat->>Certs: load certs / verify chain
    Certs-->>Compat: verification result
    Compat-->>Rustls: TLS config ready

    note over Developer,Cargo: alternate backend
    Developer->>Cargo: build --features ssl-openssl
    Cargo->>Runtime: enable feature "ssl-openssl"
    Runtime->>OpenSSL: initialize _ssl (openssl::make_module)
    OpenSSL->>Certs: expose PySSLCertificate & helpers
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Areas needing focused review:
    • stdlib/src/ssl/compat.rs (TLS abstraction, error mapping, public API surface)
    • stdlib/src/ssl/cert.rs and stdlib/src/openssl/cert.rs (certificate parsing, verifier correctness, PyO3 safety)
    • Cargo feature wiring (Cargo.toml, stdlib/Cargo.toml) and compile_error guards in src/lib.rs
    • CI changes in .github/workflows/ci.yaml and build-time cfg emission in stdlib/build.rs

Possibly related PRs

Suggested reviewers

  • coolreader18
  • ShaharNaveh
  • arihant2math

Poem

🐰 I hopped through crates and feature grass,

rustls took lead as openssl and I pass,
Chains and OIDs lined up neat in rows,
Certificates whisper where the trust-store grows,
A rabbit cheers — secure handshakes at last.

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change—replacing the SSL backend from OpenSSL to rustls—which is the primary objective of this PR.
Linked Issues check ✅ Passed The PR successfully implements the objectives from both linked issues: adding rustls as an alternative TLS backend [#3839] and replacing OpenSSL with rustls-compatible implementation [#5316], evident from feature refactoring, module reorganization, and comprehensive rustls integration.
Out of Scope Changes check ✅ Passed Minor out-of-scope change detected: crates/vm/src/frame.rs contains a logging optimization unrelated to SSL backend replacement, though it does not materially impact the PR's core objectives.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

/// Formats raw IP address bytes according to standard notation:
/// - IPv4: dotted decimal (e.g., "192.0.2.1")
/// - IPv6: colon-separated hex (e.g., "2001:DB8:0:0:0:0:0:1")
fn format_ip_address(ip: &[u8]) -> String {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

unfortunately ipv6 have different formatting.

Ipv6Addr format it like: "2001:DB8::1"

@youknowone youknowone force-pushed the ssl branch 11 times, most recently from 4d27e73 to f0d6e63 Compare November 15, 2025 10:33
@youknowone youknowone marked this pull request as ready for review November 15, 2025 12:40
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: 4

📜 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 cc2e84b and f0d6e63.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • Lib/test/test_ssl.py is excluded by !Lib/**
📒 Files selected for processing (14)
  • .cspell.dict/cpython.txt (4 hunks)
  • .cspell.dict/rust-more.txt (2 hunks)
  • .github/workflows/ci.yaml (4 hunks)
  • Cargo.toml (2 hunks)
  • README.md (1 hunks)
  • src/lib.rs (1 hunks)
  • stdlib/Cargo.toml (3 hunks)
  • stdlib/build.rs (1 hunks)
  • stdlib/src/lib.rs (2 hunks)
  • stdlib/src/openssl/cert.rs (1 hunks)
  • stdlib/src/ssl/cert.rs (1 hunks)
  • stdlib/src/ssl/compat.rs (1 hunks)
  • stdlib/src/ssl/oid.rs (1 hunks)
  • vm/src/frame.rs (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-28T16:31:03.991Z
Learnt from: arihant2math
Repo: RustPython/RustPython PR: 5790
File: build.rs:2-2
Timestamp: 2025-06-28T16:31:03.991Z
Learning: In Cargo build scripts (build.rs), the environment variable CARGO_CFG_TARGET_OS is guaranteed to exist and is automatically set by Cargo during the build process, making unwrap() safe to use when accessing this variable.

Applied to files:

  • .github/workflows/ci.yaml
  • stdlib/build.rs
  • stdlib/src/lib.rs
🧬 Code graph analysis (5)
stdlib/src/lib.rs (1)
stdlib/src/openssl.rs (2)
  • ssl (2078-2083)
  • make_module (25-31)
vm/src/frame.rs (1)
crates/compiler-core/src/bytecode.rs (1)
  • display (1579-1591)
stdlib/src/openssl/cert.rs (3)
stdlib/src/openssl.rs (5)
  • convert_openssl_error (3262-3374)
  • read (2026-2045)
  • read (2687-2772)
  • read (3130-3164)
  • read (3530-3533)
stdlib/src/ssl.rs (6)
  • e (1247-1247)
  • public_bytes (4474-4494)
  • get_info (4497-4500)
  • _test_decode_cert (4395-4423)
  • read (3231-3355)
  • read (4096-4116)
stdlib/src/ssl/cert.rs (2)
  • cert_to_dict (230-298)
  • name_to_py (205-224)
stdlib/src/ssl/cert.rs (1)
stdlib/src/openssl/cert.rs (3)
  • name_to_py (103-120)
  • name (104-118)
  • cert_to_dict (123-208)
stdlib/src/ssl/compat.rs (4)
stdlib/src/socket.rs (1)
  • timeout_error_msg (2423-2425)
stdlib/src/ssl.rs (9)
  • create_ssl_eof_error (449-454)
  • create_ssl_want_read_error (423-434)
  • create_ssl_want_write_error (436-447)
  • create_ssl_zero_return_error (456-461)
  • e (1247-1247)
  • verify_flags (1035-1037)
  • is_first_sni_read (2629-2631)
  • timeout (4211-4214)
  • time (4202-4208)
stdlib/src/ssl/cert.rs (6)
  • new (566-574)
  • new (993-1001)
  • new (1257-1262)
  • new (1427-1437)
  • new (1510-1520)
  • new_with_verifier (850-852)
stdlib/src/openssl/ssl_data_31.rs (1)
  • encode_error_key (1768-1770)
🪛 Gitleaks (8.29.0)
stdlib/src/ssl/cert.rs

[high] 1117-1119: Identified a Private Key, which may compromise cryptographic security and sensitive data encryption.

(private-key)

🔇 Additional comments (8)
.cspell.dict/rust-more.txt (1)

53-53: LGTM!

Both dictionary additions are correctly alphabetically ordered and contextually appropriate for the PR's introduction of Rustls SSL/TLS backend support and PEM-based certificate handling. "pemfile" is correctly inserted between "peekable" and "powc", and "rustls" is correctly inserted between "rustfmt" and "rustyline".

Also applies to: 65-65

.cspell.dict/cpython.txt (1)

5-5: LGTM!

The new spell-check dictionary entries (badcert, cadata, cafile, ticketer, webpki) are legitimate SSL/TLS technical terms directly related to the Rustls backend integration and certificate handling introduced in this PR. All entries are properly ordered alphabetically and will prevent false spell-check warnings.

Also applies to: 11-12, 53-53, 63-63

vm/src/frame.rs (1)

521-525: LGTM! Efficient logging optimization.

Removing the .to_string() call avoids an unnecessary allocation by passing the Display value directly to the trace formatter. This is a good optimization for trace logging.

.github/workflows/ci.yaml (1)

19-20: LGTM! CI configuration aligns with SSL backend refactoring.

The introduction of CARGO_ARGS_NO_SSL and the switch to ssl-rustls in CARGO_ARGS correctly reflect the new modular SSL backend architecture. This allows CI to test both SSL-enabled and SSL-disabled configurations appropriately.

stdlib/build.rs (1)

26-49: LGTM! Clean feature-gating of OpenSSL build logic.

The OpenSSL-specific build script logic is now properly gated behind the ssl-openssl feature. This allows the build to proceed without OpenSSL dependencies when using the ssl-rustls backend or no SSL at all.

Cargo.toml (1)

13-13: LGTM! Well-designed SSL backend feature structure.

The feature structure is well-designed:

  • ssl-rustls as default provides out-of-the-box SSL without C dependencies
  • ssl remains as an empty feature for backward compatibility
  • ssl-openssl and ssl-vendor provide OpenSSL backend options
  • Clear separation between backends

This aligns with the PR objectives to replace the hard-to-build OpenSSL with Rustls while maintaining OpenSSL as an option.

Also applies to: 23-26

stdlib/Cargo.toml (1)

18-24: LGTM! Comprehensive SSL backend dependency structure.

The dependency structure properly supports both SSL backends:

  • OpenSSL dependencies (openssl, openssl-sys, openssl-probe, foreign-types-shared) for ssl-openssl
  • Rustls ecosystem dependencies (rustls, aws-lc-rs, x509-cert, etc.) for ssl-rustls
  • All marked as optional and tied to appropriate features
  • Includes FIPS variant (ssl-rustls-fips)

The dependency set is comprehensive and aligns with the requirements for a full-featured Rustls-based SSL implementation.

Also applies to: 117-134

src/lib.rs (1)

144-153: The original review comment is incorrect and based on a misunderstanding of the feature dependency structure.

The condition at line 145 (cfg!(feature = "ssl") && !any(ssl-rustls, ssl-openssl)) cannot occur at compile time. In Cargo.toml, the ssl feature is empty (ssl = []), and both backend features explicitly enable it:

  • ssl-rustls = ["ssl", ...]
  • ssl-openssl = ["ssl", ...]

This means if ssl is enabled, it can only be because one of the backends enabled it. The TODO block is dead code and will never execute. No runtime validation issue exists—the compiler enforces this constraint at compile time.

The code is safe as-is from a functional correctness perspective, though the unreachable TODO could be removed for clarity if it's no longer needed.

Likely an incorrect or invalid review comment.

let format = format.unwrap_or(ENCODING_PEM);

match format {
x if x == ENCODING_DER => {
Copy link
Collaborator

@ShaharNaveh ShaharNaveh Nov 15, 2025

Choose a reason for hiding this comment

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

Can't it be just:

match format {
    ENCODING_DER => ...,
    ENCODING_PEM => ...,
}

?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is ridiculous 😂

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: 2

Caution

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

⚠️ Outside diff range comments (1)
src/lib.rs (1)

144-153: Complete the SSL backend selection logic and update the error message.

The nested check at lines 145-147 has a TODO placeholder but no actual implementation. When SSL is enabled but neither backend is available, this silently continues, which could lead to confusing runtime errors. Additionally, the error message at line 151 still references the old --features=ssl flag rather than the new backend-specific features (ssl-rustls, ssl-openssl).

Consider implementing the missing logic or clarifying the intended behavior:

 fn install_pip(installer: InstallPipMode, scope: Scope, vm: &VirtualMachine) -> PyResult<()> {
     if cfg!(feature = "ssl") {
         if cfg!(not(any(feature = "ssl-rustls", feature = "ssl-openssl"))) {
-            // TODO: something
+            return Err(vm.new_exception_msg(
+                vm.ctx.exceptions.system_error.to_owned(),
+                "install-pip requires rustpython be built with '--features=ssl-rustls' or '--features=ssl-openssl'".to_owned(),
+            ));
         }
     } else {
         return Err(vm.new_exception_msg(
             vm.ctx.exceptions.system_error.to_owned(),
-            "install-pip requires rustpython be build with '--features=ssl'".to_owned(),
+            "install-pip requires rustpython be built with '--features=ssl-rustls' or '--features=ssl-openssl'".to_owned(),
         ));
     }
♻️ Duplicate comments (3)
README.md (1)

69-82: Update SSL docs to reflect new backend features and drop vague TODO.

This section still describes a single ssl feature and OpenSSL/ssl-vendor setup, while the manifest/CI now distinguish ssl-rustls, ssl-openssl, and ssl-vendor with rustls as the primary backend. Please replace // TODO: fixme with concrete wording that explains:

  • which feature users should actually pass (ssl vs ssl-rustls / ssl-openssl / ssl-vendor), and
  • how Windows setup differs now that rustls is available,

and adjust the cargo install --features ssl example accordingly, or confirm that ssl is intentionally an alias that always enables a specific backend. Then remove the placeholder comment.

stdlib/src/ssl/cert.rs (1)

1216-1236: The certificate-key match validation is incomplete.

This function doesn't actually verify that the certificate and private key match—it only checks if the private key can be parsed (line 1228-1235). A mismatched cert/key pair will pass this validation but fail later during TLS handshake, making debugging difficult. CPython's X509_check_private_key performs actual cryptographic comparison of the public and private key components.

The comment note at line 1214-1215 acknowledges this is "simplified validation" but the function name validate_cert_key_match creates a false expectation of actual validation. Consider either:

  1. Implementing proper validation by extracting the public key from the certificate and verifying it matches the private key (e.g., by signing and verifying a test message)
  2. Renaming to validate_key_parseable to set correct expectations
  3. Documenting prominently that this is a superficial check and actual validation happens during config building

Based on past review comments, this is a known issue but remains unaddressed.

stdlib/src/ssl/compat.rs (1)

778-818: Fix: StrictCertVerifier applied for any verification flag, not just VERIFY_X509_STRICT.

Line 813 wraps the verifier with StrictCertVerifier whenever verify_flags != 0, which incorrectly enables RFC 5280 strict validation (requiring Authority Key Identifier extension) for any verification flag. For example, setting VERIFY_CRL_CHECK (0x4) or VERIFY_X509_PARTIAL_CHAIN (0x80000) would unintentionally enforce AKI requirements, causing valid certificates without AKI to fail verification.

CPython/OpenSSL only applies strict mode when VERIFY_X509_STRICT (0x20) is explicitly set. The StrictCertVerifier implementation at lines 1245-1349 correctly checks for missing AKI extensions per RFC 5280, but it's being invoked too broadly.

Apply this fix:

-    // Wrap with StrictCertVerifier if other verify_flags are set
-    if verify_flags != 0 {
+    // Wrap with StrictCertVerifier only if VERIFY_X509_STRICT flag is set
+    const VERIFY_X509_STRICT: i32 = 0x00000020;
+    if verify_flags & VERIFY_X509_STRICT != 0 {
         Arc::new(super::cert::StrictCertVerifier::new(verifier, verify_flags))
     } else {
         verifier
     }

Note: The constant VERIFY_X509_STRICT is already defined at line 1309 in StrictCertVerifier::verify_server_cert, so consider hoisting it to module level for reuse.

🧹 Nitpick comments (3)
.github/workflows/ci.yaml (1)

173-174: Add at least one CI job that compiles/tests with ssl-openssl (and optionally ssl-vendor).

All these cross-target cargo check steps now use ${{ env.CARGO_ARGS_NO_SSL }}, and the host builds use ssl-rustls via CARGO_ARGS. There is no job in this workflow that ever enables ssl-openssl or ssl-vendor, so the OpenSSL-backed paths can silently break.

Consider adding a dedicated job (or matrix variant) that builds/tests with --features ssl-openssl (plus ssl-vendor if you want to cover vendored OpenSSL), to keep both backends exercised.

Also applies to: 190-191, 197-198, 206-207, 213-214, 220-221, 227-228

crates/vm/src/frame.rs (1)

507-510: Use of instruction.display(...) in tracing is correct; consider mirroring it in flame_guard!.

Passing instruction.display(arg, &self.code.code) directly into trace! is correct and avoids an unnecessary String allocation.

For consistency (and the same small win), you could also drop the .to_string() in the flame_guard!(format!("Frame::execute_instruction({})", …)) above and rely on the Display impl there as well, but that’s purely optional.

Also applies to: 521-524

stdlib/src/openssl/cert.rs (1)

103-207: Refactor duplicated certificate conversion logic.

The name_to_py (lines 103-120) and cert_to_dict (lines 123-207) functions contain substantial code duplication with their counterparts in stdlib/src/ssl/cert.rs (lines 205-224 and 230-298 respectively). Both implementations:

  • Convert X509 names to Python nested tuples with identical structure
  • Build Python dicts with the same fields (subject, issuer, version, serialNumber, notBefore, notAfter, subjectAltName)
  • Parse SubjectAltName extensions with similar logic for DNS, IP, email, URI, DirName
  • Format IPv6 addresses with identical uppercase hex formatting (lines 163-177 vs ssl/cert.rs lines 131-158)

This duplication increases maintenance burden and the risk of inconsistencies between backends.

Consider extracting the common certificate conversion logic into shared utility functions that both backends can use. For example:

  1. Create a common CertificateInfo struct or trait that both OpenSSL's X509Ref and x509-parser's X509Certificate can convert to
  2. Move the Python conversion logic (name_to_py, cert_to_dict) to operate on this common representation
  3. Keep only the backend-specific parsing code in each backend module

This would centralize the Python-facing representation and reduce the risk of divergent behavior.

📜 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 f0d6e63 and b6b0fd0.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • Lib/test/test_ssl.py is excluded by !Lib/**
📒 Files selected for processing (14)
  • .cspell.dict/cpython.txt (4 hunks)
  • .cspell.dict/rust-more.txt (2 hunks)
  • .github/workflows/ci.yaml (4 hunks)
  • Cargo.toml (2 hunks)
  • README.md (1 hunks)
  • crates/vm/src/frame.rs (1 hunks)
  • src/lib.rs (1 hunks)
  • stdlib/Cargo.toml (3 hunks)
  • stdlib/build.rs (1 hunks)
  • stdlib/src/lib.rs (2 hunks)
  • stdlib/src/openssl/cert.rs (1 hunks)
  • stdlib/src/ssl/cert.rs (1 hunks)
  • stdlib/src/ssl/compat.rs (1 hunks)
  • stdlib/src/ssl/oid.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • stdlib/src/lib.rs
  • stdlib/src/ssl/oid.rs
  • Cargo.toml
  • .cspell.dict/rust-more.txt
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-28T16:31:03.991Z
Learnt from: arihant2math
Repo: RustPython/RustPython PR: 5790
File: build.rs:2-2
Timestamp: 2025-06-28T16:31:03.991Z
Learning: In Cargo build scripts (build.rs), the environment variable CARGO_CFG_TARGET_OS is guaranteed to exist and is automatically set by Cargo during the build process, making unwrap() safe to use when accessing this variable.

Applied to files:

  • .github/workflows/ci.yaml
  • stdlib/build.rs
🧬 Code graph analysis (5)
crates/vm/src/frame.rs (1)
crates/compiler-core/src/bytecode.rs (1)
  • display (1579-1591)
stdlib/src/openssl/cert.rs (2)
stdlib/src/openssl.rs (1)
  • convert_openssl_error (3262-3374)
stdlib/src/ssl/cert.rs (2)
  • cert_to_dict (230-298)
  • name_to_py (205-224)
stdlib/build.rs (2)
stdlib/src/ssl.rs (1)
  • version (3548-3561)
stdlib/src/openssl.rs (1)
  • version (2282-2285)
stdlib/src/ssl/compat.rs (3)
stdlib/src/socket.rs (1)
  • timeout_error_msg (2423-2425)
stdlib/src/ssl.rs (8)
  • create_ssl_eof_error (449-454)
  • create_ssl_want_read_error (423-434)
  • create_ssl_want_write_error (436-447)
  • create_ssl_zero_return_error (456-461)
  • e (1247-1247)
  • is_first_sni_read (2629-2631)
  • timeout (4211-4214)
  • time (4202-4208)
stdlib/src/openssl/ssl_data_31.rs (1)
  • encode_error_key (1768-1770)
stdlib/src/ssl/cert.rs (1)
stdlib/src/openssl/cert.rs (3)
  • name_to_py (103-120)
  • name (104-118)
  • cert_to_dict (123-208)
🔇 Additional comments (3)
.cspell.dict/cpython.txt (1)

5-5: Dictionary entries appropriately aligned with Rustls/TLS implementation.

The additions of badcert, cadata, cafile, ticketer, and webpki are all relevant to the SSL/TLS backend replacement work. The reordering of Itertool corrects the alphabetical sequence. All entries maintain proper dictionary ordering.

Also applies to: 11-12, 30-30, 53-53, 63-63

.github/workflows/ci.yaml (1)

18-21: CARGO_ARGS / CARGO_ARGS_NO_SSL wiring matches new TLS feature layout.

Using ssl-rustls in CARGO_ARGS and introducing CARGO_ARGS_NO_SSL as a no-TLS baseline is consistent with the new feature set and cleanly separates SSL-enabled from no-SSL builds. No issues here.

stdlib/build.rs (1)

26-48: Correctly gate OpenSSL cfg/env handling on ssl-openssl; just confirm MSRV for is_ok_and.

Wrapping all DEP_OPENSSL_* handling in #[cfg(feature = "ssl-openssl")] is a good cleanup and prevents OpenSSL-specific cfgs from leaking into rustls-only or no-SSL builds. The vendored probe via:

if std::env::var("DEP_OPENSSL_VENDORED").is_ok_and(|s| s == "1") {
    println!("cargo::rustc-cfg=openssl_vendored")
}

is also clear and avoids treating non-"1" values as vendored.

Since Result::is_ok_and is a relatively new addition to the standard library, please ensure your workspace rust-version and intended MSRV are high enough for this helper; otherwise a simple matches!(std::env::var("DEP_OPENSSL_VENDORED"), Ok(ref s) if s == "1") would be more conservative.

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

🧹 Nitpick comments (1)
README.md (1)

85-91: Document concrete feature-flag combinations for SSL backends.

The text explains that ssl-rustls is the default and ssl-openssl/ssl-vendor are alternatives, but it doesn’t show how to switch in practice. Consider adding short cargo examples (e.g. --no-default-features --features ssl-openssl and --no-default-features --features ssl-vendor) so users know how to “replace” the default without hitting the mutual‑exclusion compile error.

📜 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 b6b0fd0 and c9c754f.

📒 Files selected for processing (3)
  • README.md (2 hunks)
  • src/lib.rs (1 hunks)
  • stdlib/src/lib.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib.rs
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-28T16:31:03.991Z
Learnt from: arihant2math
Repo: RustPython/RustPython PR: 5790
File: build.rs:2-2
Timestamp: 2025-06-28T16:31:03.991Z
Learning: In Cargo build scripts (build.rs), the environment variable CARGO_CFG_TARGET_OS is guaranteed to exist and is automatically set by Cargo during the build process, making unwrap() safe to use when accessing this variable.

Applied to files:

  • stdlib/src/lib.rs
🧬 Code graph analysis (1)
stdlib/src/lib.rs (1)
stdlib/src/openssl.rs (2)
  • ssl (2078-2083)
  • make_module (25-31)
⏰ 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). (1)
  • GitHub Check: auto_format
🔇 Additional comments (2)
stdlib/src/lib.rs (2)

176-183: _ssl module registration cleanly selects backend per feature.

The two _ssl entries are now cfg-gated on ssl-rustls vs ssl-openssl with the same not(target_arch = "wasm32") condition as the module declarations, so at most one backend is registered on host targets and wasm32 correctly gets no _ssl module.


79-85: Mutual-exclusion guard between ssl-openssl and ssl-rustls is correctly implemented.

The cfg conditions for module declarations (lines 80, 82) precisely match the cfg conditions for _ssl registrations (lines 177, 180). The compile_error! on line 84 triggers when both features are enabled, preventing compilation and eliminating the risk of duplicate _ssl registrations.

Comment on lines +69 to 75
You can install pip by
running:

```bash
cargo install --git https://github.com/RustPython/RustPython --features ssl
cargo install --git https://github.com/RustPython/RustPython
rustpython --install-pip
```
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix pip installation command; crate name is missing.

The cargo install example omits the crate name, so copy‑pasting line 73 will fail. It should mirror the earlier install example and include rustpython on the same line.

-```bash
-cargo install --git https://github.com/RustPython/RustPython
-rustpython --install-pip
-```
+```bash
+cargo install --git https://github.com/RustPython/RustPython rustpython
+rustpython --install-pip
+```
🤖 Prompt for AI Agents
In README.md around lines 69 to 75, the cargo install example is missing the
crate name so the command will fail; update the install snippet to put the crate
name `rustpython` on the same line as `cargo install --git
https://github.com/RustPython/RustPython` and keep the subsequent `rustpython
--install-pip` line as shown in the suggested replacement so the two commands
can be copy-pasted and run successfully.

@youknowone
Copy link
Member Author

@ShaharNaveh I tried to review this myself, but it is really not easy. I know asking review for AI-generated dump is not fair. Let's just try.

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: 3

🧹 Nitpick comments (3)
stdlib/src/ssl/compat.rs (3)

181-197: Error detection relies on fragile string matching.

Lines 183-196 use debug format string matching (contains("Hostname mismatch")) to distinguish error types. This is fragile because:

  • Debug format output is not part of rustls's stable API
  • Future rustls versions could change error messages, breaking this logic
  • The format!("{:?}", other_err) allocates unnecessarily on every error

Consider checking if rustls provides a more structured way to inspect OtherError contents, or document this as a known limitation requiring updates when rustls changes.


554-557: Potential panic in certificate verification error handling.

Line 556 uses .expect("unlikely to happen") when creating a certificate verification error. While attribute setting failures are rare (e.g., out of memory), if they do occur, this will panic the Rust runtime instead of gracefully returning an error to Python. Since create_ssl_cert_verification_error returns PyResult<PyBaseExceptionRef>, consider using unwrap_or_else to create a fallback exception without attributes.

Apply this diff to handle the error gracefully:

             SslError::CertVerification(cert_err) => {
                 // Use the proper cert verification error creator
-                create_ssl_cert_verification_error(vm, &cert_err).expect("unlikely to happen")
+                create_ssl_cert_verification_error(vm, &cert_err).unwrap_or_else(|_| {
+                    vm.new_exception_msg(
+                        PySSLError::class(&vm.ctx).to_owned(),
+                        format!("Certificate verification failed: {:?}", cert_err),
+                    )
+                })
             }

1322-1325: Magic number for iteration limit.

The hardcoded value 1000 on line 1323 acts as a safety check against infinite loops. While the comment explains its purpose, consider defining this as a named constant (e.g., MAX_HANDSHAKE_ITERATIONS) for better maintainability and to make the limit easily adjustable if needed.

Apply this diff to use a named constant:

+// Maximum handshake iterations before giving up (safety check)
+const MAX_HANDSHAKE_ITERATIONS: usize = 1000;
+
 pub(super) fn ssl_do_handshake(
     conn: &mut TlsConnection,
     socket: &PySSLSocket,
     vm: &VirtualMachine,
 ) -> SslResult<()> {
     // ... existing code ...
 
         // Safety check: prevent truly infinite loops (should never happen)
-        if iteration_count > 1000 {
+        if iteration_count > MAX_HANDSHAKE_ITERATIONS {
             break;
         }
     }
📜 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 c9c754f and 2bb523a.

📒 Files selected for processing (4)
  • src/lib.rs (2 hunks)
  • stdlib/src/openssl/cert.rs (1 hunks)
  • stdlib/src/ssl/cert.rs (1 hunks)
  • stdlib/src/ssl/compat.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/lib.rs
  • stdlib/src/openssl/cert.rs
🧰 Additional context used
🧬 Code graph analysis (2)
stdlib/src/ssl/cert.rs (1)
stdlib/src/openssl/cert.rs (3)
  • name_to_py (103-120)
  • name (104-118)
  • cert_to_dict (123-208)
stdlib/src/ssl/compat.rs (4)
stdlib/src/socket.rs (1)
  • timeout_error_msg (2423-2425)
stdlib/src/ssl.rs (9)
  • create_ssl_eof_error (449-454)
  • create_ssl_want_read_error (423-434)
  • create_ssl_want_write_error (436-447)
  • create_ssl_zero_return_error (456-461)
  • e (1247-1247)
  • verify_flags (1035-1037)
  • is_first_sni_read (2629-2631)
  • timeout (4211-4214)
  • time (4202-4208)
stdlib/src/ssl/cert.rs (6)
  • new (568-576)
  • new (995-1003)
  • new (1259-1264)
  • new (1428-1438)
  • new (1511-1521)
  • new_with_verifier (852-854)
stdlib/src/openssl/ssl_data_31.rs (1)
  • encode_error_key (1768-1770)
🪛 Gitleaks (8.29.0)
stdlib/src/ssl/cert.rs

[high] 1119-1121: Identified a Private Key, which may compromise cryptographic security and sensitive data encryption.

(private-key)

🔇 Additional comments (1)
stdlib/src/ssl/cert.rs (1)

93-98: Disregard this review comment—errno preservation is not applicable here.

The decrypt_failed function correctly uses io::Error::other(). The decryption error comes from pkcs8::Error, which includes variants like Asn1, EncryptedPrivateKey, KeyMalformed, etc.—these are cryptographic/parsing errors, not OS-level errors with errno values. File I/O operations (certificate and key file reads at lines 1091, 1104) already preserve OS errors naturally via fs::read(). Using io::Error::other() for non-OS errors is the correct approach.

Likely an incorrect or invalid review comment.

Copy link
Member Author

@youknowone youknowone left a comment

Choose a reason for hiding this comment

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

Good. I’ve taken a look. It’s hard to say that I’m fully satisfied with everything, but for now, since it’s not even clear whether we’ve implemented the SSL code safely, I’d rather not invest more effort into changing the code when we don’t fully understand the implementation. At the very least, pip install works and many tests are passing.

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.

The tests pass, that's what important. but I do worry about the maintenance of this code in the long run

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: 0

♻️ Duplicate comments (2)
stdlib/src/ssl/cert.rs (1)

178-184: Timestamp handling still uses expect() which can panic.

Although the past review comment was marked as addressed, the current implementation still uses expect() which will panic for out-of-range timestamps. While X.509 certificates typically have valid timestamps, malformed or crafted certificates could still trigger a panic.

Consider using unwrap_or_else with a fallback timestamp:

 fn format_asn1_time(time: &x509_parser::time::ASN1Time) -> String {
     let timestamp = time.timestamp();
     DateTime::<Utc>::from_timestamp(timestamp, 0)
-        .expect("ASN1Time must be valid timestamp")
+        .unwrap_or_else(|| DateTime::<Utc>::from_timestamp(0, 0).unwrap())
         .format("%b %e %H:%M:%S %Y GMT")
         .to_string()
 }
stdlib/src/openssl/cert.rs (1)

26-51: FFI code still uses assert! which can panic.

The past review comment correctly identified that assert! on FFI return values (lines 31 and 43) can cause panics in production. While OpenSSL functions rarely fail in normal operation, returning Result would be more robust.

Consider changing the function signature to return Result<Option<String>, String> and propagate errors instead of panicking:

-pub(crate) fn obj2txt(obj: &Asn1ObjectRef, no_name: bool) -> Option<String> {
+pub(crate) fn obj2txt(obj: &Asn1ObjectRef, no_name: bool) -> Result<Option<String>, String> {
     let no_name = i32::from(no_name);
     let ptr = obj.as_ptr();
     let b = unsafe {
         let buflen = sys::OBJ_obj2txt(std::ptr::null_mut(), 0, ptr, no_name);
-        assert!(buflen >= 0);
+        if buflen < 0 {
+            return Err("OBJ_obj2txt failed to get buffer length".to_string());
+        }
         if buflen == 0 {
-            return None;
+            return Ok(None);
         }
         // ... rest of the function

Note: This change would require updating all call sites.

🧹 Nitpick comments (1)
src/lib.rs (1)

62-68: Fix grammar in error message.

The compile error message has a grammatical error. "to be enabled" should be removed since "either" already implies choice.

Apply this diff:

 compile_error!(
-    "Feature \"ssl\" is now enabled by either \"ssl-rustls\" or \"ssl-openssl\" to be enabled. Do not manually pass \"ssl\" feature. To enable ssl-openssl, use --no-default-features to disable ssl-rustls"
+    "Feature \"ssl\" requires either \"ssl-rustls\" or \"ssl-openssl\" to be enabled. Do not manually pass \"ssl\" feature. To enable ssl-openssl, use --no-default-features to disable ssl-rustls"
 );
📜 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 2bb523a and 782d9f4.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • .cspell.dict/cpython.txt (4 hunks)
  • .github/workflows/ci.yaml (4 hunks)
  • Cargo.toml (2 hunks)
  • README.md (2 hunks)
  • src/lib.rs (2 hunks)
  • stdlib/Cargo.toml (3 hunks)
  • stdlib/build.rs (1 hunks)
  • stdlib/src/lib.rs (2 hunks)
  • stdlib/src/openssl/cert.rs (1 hunks)
  • stdlib/src/ssl/cert.rs (1 hunks)
  • stdlib/src/ssl/compat.rs (1 hunks)
  • stdlib/src/ssl/oid.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • stdlib/Cargo.toml
  • .cspell.dict/cpython.txt
  • Cargo.toml
  • README.md
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-28T16:31:03.991Z
Learnt from: arihant2math
Repo: RustPython/RustPython PR: 5790
File: build.rs:2-2
Timestamp: 2025-06-28T16:31:03.991Z
Learning: In Cargo build scripts (build.rs), the environment variable CARGO_CFG_TARGET_OS is guaranteed to exist and is automatically set by Cargo during the build process, making unwrap() safe to use when accessing this variable.

Applied to files:

  • stdlib/build.rs
  • .github/workflows/ci.yaml
  • stdlib/src/lib.rs
🧬 Code graph analysis (4)
stdlib/build.rs (2)
stdlib/src/ssl.rs (1)
  • version (3521-3534)
stdlib/src/openssl.rs (1)
  • version (2264-2267)
stdlib/src/lib.rs (1)
stdlib/src/openssl.rs (2)
  • ssl (2060-2065)
  • make_module (25-31)
stdlib/src/openssl/cert.rs (3)
stdlib/src/openssl.rs (1)
  • convert_openssl_error (3239-3351)
stdlib/src/ssl.rs (3)
  • e (1247-1247)
  • public_bytes (4428-4448)
  • get_info (4451-4454)
stdlib/src/ssl/cert.rs (2)
  • cert_to_dict (298-345)
  • name_to_py (273-292)
stdlib/src/ssl/compat.rs (4)
stdlib/src/socket.rs (1)
  • timeout_error_msg (2423-2425)
stdlib/src/ssl.rs (10)
  • create_ssl_eof_error (449-454)
  • create_ssl_want_read_error (423-434)
  • create_ssl_want_write_error (436-447)
  • create_ssl_zero_return_error (456-461)
  • e (1247-1247)
  • verify_flags (1035-1037)
  • is_first_sni_read (2615-2617)
  • timeout (4165-4168)
  • time (4156-4162)
  • protocol (1030-1032)
stdlib/src/ssl/cert.rs (6)
  • new (609-617)
  • new (1027-1035)
  • new (1291-1296)
  • new (1460-1470)
  • new (1543-1553)
  • new_with_verifier (892-894)
stdlib/src/openssl/ssl_data_31.rs (1)
  • encode_error_key (1768-1770)
🪛 Gitleaks (8.29.0)
stdlib/src/ssl/cert.rs

[high] 1151-1153: Identified a Private Key, which may compromise cryptographic security and sensitive data encryption.

(private-key)

⏰ 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: Run snippets and cpython tests (ubuntu-latest)
  • GitHub Check: Run snippets and cpython tests (windows-latest)
  • GitHub Check: Check the WASM package and demo
  • GitHub Check: Run snippets and cpython tests (macos-latest)
  • GitHub Check: Run snippets and cpython tests on wasm-wasi
  • GitHub Check: Run rust tests (windows-latest)
  • GitHub Check: Check Rust code with clippy
  • GitHub Check: Run rust tests (macos-latest)
  • GitHub Check: Run rust tests (ubuntu-latest)
  • GitHub Check: Ensure compilation on various targets
  • GitHub Check: auto_format
🔇 Additional comments (9)
src/lib.rs (1)

152-152: Verify semantic equivalence of the install_pip condition change.

The condition changed from cfg!(not(feature = "ssl")) to !cfg!(feature = "ssl")). While these should be semantically equivalent, the extra parenthesis in the new version is unnecessary.

Apply this diff:

-    if !cfg!(feature = "ssl") {
+    if !cfg!(feature = "ssl") {

Actually, looking closer, the change is semantically equivalent. The extra closing parenthesis appears to be a display issue. No change needed.

.github/workflows/ci.yaml (1)

19-20: LGTM! Clear separation of SSL and no-SSL configurations.

The new CARGO_ARGS_NO_SSL environment variable provides a clean way to test builds without SSL support, which is appropriate for platforms like iOS and embedded targets where SSL may not be available or desired.

stdlib/build.rs (1)

26-49: LGTM! Proper feature gating for OpenSSL build logic.

The OpenSSL-specific build configuration is now correctly gated behind the ssl-openssl feature, which aligns with the PR's goal of supporting multiple TLS backends.

stdlib/src/lib.rs (1)

79-84: LGTM! SSL backends are properly feature-gated with mutual exclusivity.

The compile-time check ensures that both SSL backends cannot be enabled simultaneously, preventing duplicate _ssl module registration. This addresses the concern from the past review comment.

stdlib/src/ssl/oid.rs (1)

1-464: LGTM! Well-structured OID lookup system with comprehensive coverage.

The module provides a clean CPython-compatible OID lookup interface with:

  • Efficient hash-based lookups for NID, OID string, and names
  • Case-insensitive long name matching
  • Mix of static (oid-registry) and string-based OIDs
  • Comprehensive test coverage including duplicate detection

The design is solid and the implementation is correct.

stdlib/src/ssl/compat.rs (4)

57-63: LGTM! Thread-safe CryptoProvider initialization.

The use of Once ensures the default CryptoProvider is installed exactly once, even in multi-threaded scenarios. This is the correct approach for rustls 0.23+ process-level provider setup.


215-253: LGTM! Proper SSLCertVerificationError creation with CPython-compatible attributes.

The function correctly:

  • Maps rustls certificate errors to X509 verification codes
  • Sets verify_code and verify_message attributes
  • Handles error cases gracefully (continues even if attribute setting fails)
  • Matches CPython's fill_and_set_sslerror() behavior

646-734: LGTM! Well-structured server config creation with proper client cert verification.

The function handles multiple scenarios correctly:

  • Deferred validation for TLS 1.3
  • Immediate validation for TLS 1.2
  • No client authentication when not requested
  • Proper fallback when deferred error storage is not provided
  • Session storage and ticketer configuration

The logic flow is clear and the error handling is appropriate.


828-967: LGTM! Comprehensive client config creation with multiple verifier types.

The function correctly handles:

  • Empty root store with EmptyRootStoreVerifier
  • Hostname verification vs. hostname-ignoring verification
  • CRL checking with proper flag handling
  • Partial chain verification for self-signed certificates
  • Strict verification when requested
  • mTLS with client certificates
  • Session resumption and ALPN configuration

The verifier wrapping logic is sound and matches OpenSSL behavior.

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

♻️ Duplicate comments (6)
stdlib/Cargo.toml (1)

18-24: LGTM! Feature definitions match root Cargo.toml.

The SSL backend feature split is consistent:

  • ssl marker
  • ssl-rustls with rustls dependencies
  • ssl-rustls-fips for FIPS mode
  • ssl-openssl with OpenSSL dependencies
  • ssl-vendor for vendored OpenSSL
stdlib/src/lib.rs (1)

79-84: LGTM! Mutual exclusion properly enforced.

The compile_error at lines 83-84 prevents both ssl-rustls and ssl-openssl from being enabled simultaneously, addressing the past review concern.

stdlib/src/ssl/compat.rs (2)

807-811: LGTM! StrictCertVerifier properly gated by flag.

Line 807 correctly checks verify_flags & VERIFY_X509_STRICT != 0 to apply StrictCertVerifier only when the specific flag is set, not for any non-zero flags. This addresses the past review concern.


826-965: LGTM! Client config builder handles all verification modes.

The create_client_config function properly handles:

  • ✅ CERT_NONE mode with NoVerifier
  • ✅ Empty root store with EmptyRootStoreVerifier
  • ✅ Hostname verification vs check_hostname=False paths
  • ✅ CRL support with build_webpki_verifier_with_crls
  • ✅ Proper verifier wrapping (CRL, Partial, Strict)
  • ✅ Session resumption and ALPN

Lines 919-926 correctly check the VERIFY_X509_STRICT bit specifically, matching the fix at line 807.

stdlib/src/openssl/cert.rs (1)

26-51: Critical: Replace assertions with proper error handling in FFI code.

Lines 31 and 43 use assert! on OpenSSL C function return values. If OpenSSL returns unexpected values (e.g., memory allocation failure), these will panic and crash the process. FFI boundaries should never panic.

Change obj2txt to return Result<String, String>:

-pub(crate) fn obj2txt(obj: &Asn1ObjectRef, no_name: bool) -> Option<String> {
+pub(crate) fn obj2txt(obj: &Asn1ObjectRef, no_name: bool) -> Result<Option<String>, String> {
     let no_name = i32::from(no_name);
     let ptr = obj.as_ptr();
     let b = unsafe {
         let buflen = sys::OBJ_obj2txt(std::ptr::null_mut(), 0, ptr, no_name);
-        assert!(buflen >= 0);
+        if buflen < 0 {
+            return Err("OBJ_obj2txt failed to get buffer length".to_string());
+        }
         if buflen == 0 {
-            return None;
+            return Ok(None);
         }
         let buflen = buflen as usize;
         let mut buf = Vec::<u8>::with_capacity(buflen + 1);
         let ret = sys::OBJ_obj2txt(
             buf.as_mut_ptr() as *mut libc::c_char,
             buf.capacity() as _,
             ptr,
             no_name,
         );
-        assert!(ret >= 0);
+        if ret < 0 {
+            return Err("OBJ_obj2txt failed".to_string());
+        }
         // SAFETY: OBJ_obj2txt initialized the buffer successfully
         buf.set_len(buflen);
         buf
     };
     let s = String::from_utf8(b)
         .unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned());
-    Some(s)
+    Ok(Some(s))
 }

Update callers at lines 107 and elsewhere to handle the Result.

stdlib/src/ssl/cert.rs (1)

196-202: Critical: Handle invalid timestamps gracefully.

Line 199 uses .expect() which will panic if DateTime::from_timestamp returns None for out-of-range timestamps. While X.509 certificates typically have valid timestamps, malformed or crafted certificates could trigger this panic.

Apply this diff to handle invalid timestamps:

 fn format_asn1_time(time: &x509_parser::time::ASN1Time) -> String {
     let timestamp = time.timestamp();
     DateTime::<Utc>::from_timestamp(timestamp, 0)
-        .expect("ASN1Time must be valid timestamp")
+        .unwrap_or_else(|| DateTime::<Utc>::from_timestamp(0, 0).expect("unix epoch is valid"))
         .format("%b %e %H:%M:%S %Y GMT")
         .to_string()
 }
📜 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 782d9f4 and 8ccba94.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • .cspell.dict/cpython.txt (4 hunks)
  • .github/workflows/ci.yaml (4 hunks)
  • Cargo.toml (2 hunks)
  • README.md (2 hunks)
  • src/lib.rs (2 hunks)
  • stdlib/Cargo.toml (3 hunks)
  • stdlib/build.rs (1 hunks)
  • stdlib/src/lib.rs (2 hunks)
  • stdlib/src/openssl/cert.rs (1 hunks)
  • stdlib/src/ssl/cert.rs (1 hunks)
  • stdlib/src/ssl/compat.rs (1 hunks)
  • stdlib/src/ssl/oid.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • .github/workflows/ci.yaml
  • README.md
  • .cspell.dict/cpython.txt
  • stdlib/build.rs
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-28T16:31:03.991Z
Learnt from: arihant2math
Repo: RustPython/RustPython PR: 5790
File: build.rs:2-2
Timestamp: 2025-06-28T16:31:03.991Z
Learning: In Cargo build scripts (build.rs), the environment variable CARGO_CFG_TARGET_OS is guaranteed to exist and is automatically set by Cargo during the build process, making unwrap() safe to use when accessing this variable.

Applied to files:

  • stdlib/src/lib.rs
🧬 Code graph analysis (3)
stdlib/src/openssl/cert.rs (3)
stdlib/src/openssl.rs (1)
  • convert_openssl_error (3238-3350)
stdlib/src/ssl.rs (2)
  • e (1244-1244)
  • public_bytes (4422-4442)
stdlib/src/ssl/cert.rs (2)
  • cert_to_dict (316-363)
  • name_to_py (291-310)
stdlib/src/lib.rs (1)
stdlib/src/openssl.rs (2)
  • ssl (2059-2064)
  • make_module (25-31)
stdlib/src/ssl/compat.rs (4)
stdlib/src/socket.rs (1)
  • timeout_error_msg (2423-2425)
stdlib/src/ssl.rs (10)
  • create_ssl_eof_error (449-454)
  • create_ssl_want_read_error (423-434)
  • create_ssl_want_write_error (436-447)
  • create_ssl_zero_return_error (456-461)
  • e (1244-1244)
  • verify_flags (1034-1036)
  • is_first_sni_read (2610-2612)
  • timeout (4159-4162)
  • time (4150-4156)
  • protocol (1029-1031)
stdlib/src/ssl/cert.rs (6)
  • new (627-635)
  • new (1033-1041)
  • new (1297-1302)
  • new (1454-1464)
  • new (1537-1547)
  • new_with_verifier (898-900)
stdlib/src/openssl/ssl_data_31.rs (1)
  • encode_error_key (1768-1770)
🪛 Gitleaks (8.29.0)
stdlib/src/ssl/cert.rs

[high] 1157-1159: Identified a Private Key, which may compromise cryptographic security and sensitive data encryption.

(private-key)

⏰ 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: Run rust tests (ubuntu-latest)
  • GitHub Check: Run snippets and cpython tests on wasm-wasi
  • GitHub Check: Run rust tests (windows-latest)
  • GitHub Check: Run snippets and cpython tests (macos-latest)
  • GitHub Check: Run snippets and cpython tests (windows-latest)
  • GitHub Check: Run snippets and cpython tests (ubuntu-latest)
  • GitHub Check: Run rust tests (macos-latest)
  • GitHub Check: Check the WASM package and demo
  • GitHub Check: Ensure compilation on various targets
  • GitHub Check: Check Rust code with clippy
  • GitHub Check: auto_format
🔇 Additional comments (18)
src/lib.rs (2)

62-68: LGTM! Compile-time guard prevents misconfiguration.

The compile_error correctly enforces that users select exactly one SSL backend. The error message helpfully explains how to switch from the default ssl-rustls to ssl-openssl.


152-152: LGTM! Logically equivalent refactor.

The negation is moved outside cfg!() with no functional change.

Cargo.toml (1)

13-26: LGTM! SSL backend features properly wired.

The feature split is clean:

  • ssl is now a marker feature
  • ssl-rustls is the new default backend
  • ssl-openssl and ssl-vendor provide OpenSSL alternatives
  • Feature dependencies correctly propagate to rustpython-stdlib

This addresses the past review concerns about missing feature wiring at the root level.

stdlib/src/lib.rs (1)

176-183: LGTM! Backend initialization properly gated.

Both backends correctly map to the _ssl Python module name, with feature gates ensuring only one is active at compile time.

stdlib/src/ssl/oid.rs (1)

1-464: LGTM! Well-structured OID management system.

This module provides a comprehensive OID lookup surface compatible with CPython's ssl module:

Strengths:

  • Clean separation between static (oid-registry) and string-based OIDs
  • Multiple index types (NID, OID string, short name, long name) for fast lookups
  • Case-insensitive long name matching
  • Comprehensive OID coverage (X.509 DN, extensions, EKU, algorithms)
  • Good test coverage validating lookups and uniqueness

Design:

  • LazyLock ensures one-time initialization
  • HashMap indices provide O(1) lookups
  • Macros reduce boilerplate for OID definitions

The implementation appears solid and production-ready.

stdlib/src/openssl/cert.rs (1)

76-94: LGTM! Match expression properly handles encoding formats.

The match on ENCODING_DER and ENCODING_PEM is clean and properly maps OpenSSL encoding to bytes or PEM strings. Error handling via convert_openssl_error is appropriate.

stdlib/src/ssl/cert.rs (5)

53-132: LGTM! Well-organized error handling utilities.

The cert_error module provides consistent error creation functions that:

  • Reduce code duplication
  • Ensure uniform error messages
  • Support proper error mapping to rustls and Python exceptions

The organization (pem, der, key submodules) is clean and maintainable.


270-286: LGTM! CA certificate detection properly implemented.

The is_ca_certificate function correctly:

  • Parses the certificate
  • Checks Basic Constraints extension
  • Returns true only if CA=true
  • Safely handles parse errors

This matches OpenSSL's X509_check_ca() behavior as documented.


605-840: LGTM! CertLoader provides robust certificate loading.

The CertLoader implementation:

  • ✅ Auto-detects PEM vs DER format
  • ✅ Handles duplicate certificates via HashSet
  • ✅ Tracks CA statistics
  • ✅ Supports files, directories, and bytes
  • ✅ Validates X.509 structure after PEM decode
  • ✅ Proper error messages

The separation of concerns (load_from_file, load_from_dir, load_from_bytes_ex) is clean.


1256-1276: Function correctly validates private key format.

The past review concern about not checking cert/key match is noted, but the current implementation correctly validates that:

  1. The private key can be parsed by rustls
  2. A signing key can be created

Full cert/key matching would require signing a test message, which is complex. The current approach catches most configuration errors and matches the design constraints mentioned in past reviews regarding compatibility requirements.


1278-1620: LGTM! Comprehensive verifier implementations.

The verifier suite provides:

  • NoVerifier: CERT_NONE mode
  • HostnameIgnoringVerifier: check_hostname=False
  • StrictCertVerifier: RFC 5280 strict validation with AKI checks
  • EmptyRootStoreVerifier: Proper error when no CAs loaded
  • CRLCheckVerifier: CRL enforcement
  • PartialChainVerifier: VERIFY_X509_PARTIAL_CHAIN support
  • DeferredClientCertVerifier: TLS 1.3 deferred validation

Each verifier properly delegates to inner verifiers and handles edge cases. The wrapper pattern allows flexible composition.

stdlib/src/ssl/compat.rs (7)

39-46: LGTM! Verification flags properly defined.

The constants VERIFY_X509_STRICT (0x20) and VERIFY_X509_PARTIAL_CHAIN (0x80000) match OpenSSL's flag values for RFC 5280 strict validation and partial chain acceptance.


164-205: LGTM! Certificate error mapping comprehensive.

The rustls_cert_error_to_verify_info function properly maps rustls certificate errors to OpenSSL X509_V_ERR_* codes:

  • UnknownIssuer → UNABLE_TO_GET_ISSUER_CERT_LOCALLY (20)
  • Expired → CERT_HAS_EXPIRED (10)
  • Revoked → CERT_REVOKED (23)
  • Hostname/IP mismatch detection from error messages

This ensures Python code receives OpenSSL-compatible error codes.


373-562: LGTM! SslError enum provides comprehensive error handling.

The error type properly covers:

  • ✅ WantRead/WantWrite for non-blocking I/O
  • ✅ Certificate verification with rustls::CertificateError
  • ✅ TLS alerts mapped to OpenSSL error codes
  • ✅ Timeout errors for socket.timeout
  • ✅ SNI callback restart signal
  • ✅ Proper error conversion to Python exceptions

The from_rustls method correctly maps rustls errors, including the special handling of CloseNotify as ZeroReturn and PeerIncompatible::NoCipherSuitesInCommon as SSL_R_NO_SHARED_CIPHER.


648-732: LGTM! Server config builder handles complex scenarios.

The create_server_config function properly:

  • ✅ Handles client cert verification (optional, required, deferred)
  • ✅ Supports custom cert resolvers for SNI
  • ✅ Configures session storage and ticketer
  • ✅ Applies ALPN with fallback
  • ✅ Uses custom crypto provider for cipher/KX group control

The deferred validation support for TLS 1.3 is particularly well-designed.


1181-1325: LGTM! Handshake logic handles BIO and socket modes.

The ssl_do_handshake function:

  • ✅ Handles first iteration forcing write for ClientHello/ServerHello
  • ✅ Properly detects SNI callback needs and returns SniCallbackRestart
  • ✅ Handles TLS 1.3 NewSessionTicket in both BIO and socket modes
  • ✅ Prevents infinite loops with iteration counter
  • ✅ Properly exits when handshake completes

The separation of BIO mode (one iteration, return WANT errors) vs socket mode (loop until complete) is correct.


1333-1430: LGTM! SSL_read properly handles blocking and non-blocking I/O.

The ssl_read function:

  • ✅ Implements deadline-based timeout
  • ✅ Loops to handle TLS records and post-handshake messages
  • ✅ Tries plaintext read before fetching more TLS data
  • ✅ Handles "message buffer full" gracefully
  • ✅ Returns buffered plaintext before propagating errors
  • ✅ Properly distinguishes BIO EOF from socket EOF

The CPython comment about suppress_ragged_eofs=True behavior (lines 1474-1486) is valuable context for the ZeroReturn handling.


1636-1786: LGTM! Helper functions provide OpenSSL compatibility.

The helper functions properly:

  • ✅ Normalize cipher names (TLS_AES_256_GCM_SHA384 → AES256-GCM-SHA384)
  • ✅ Extract key bits from cipher names
  • ✅ Map protocol versions (TLSv1.2, TLSv1.3)
  • ✅ Convert curve names to KxGroups (prime256v1, secp384r1, X25519, etc.)
  • ✅ Extract cipher info for Python exposure

These utilities bridge rustls and OpenSSL naming conventions.

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

📜 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 8ccba94 and 98fb4ad.

📒 Files selected for processing (1)
  • stdlib/Cargo.toml (3 hunks)
⏰ 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). (12)
  • GitHub Check: Run snippets and cpython tests on wasm-wasi
  • GitHub Check: Run snippets and cpython tests (windows-latest)
  • GitHub Check: Run snippets and cpython tests (ubuntu-latest)
  • GitHub Check: Check the WASM package and demo
  • GitHub Check: Run tests under miri
  • GitHub Check: Check Rust code with clippy
  • GitHub Check: Run snippets and cpython tests (macos-latest)
  • GitHub Check: Run rust tests (windows-latest)
  • GitHub Check: Ensure compilation on various targets
  • GitHub Check: Run rust tests (ubuntu-latest)
  • GitHub Check: Run rust tests (macos-latest)
  • GitHub Check: auto_format
🔇 Additional comments (2)
stdlib/Cargo.toml (2)

18-23: Mutual exclusion between SSL backends is properly enforced.

Verification confirms that a compile-time guard at stdlib/src/lib.rs:84 prevents both ssl-rustls and ssl-openssl from being enabled simultaneously with the error: "features \"ssl-openssl\" and \"ssl-rustls\" are mutually exclusive". No action required.


93-93: Chrono usage is justified—it handles certificate timestamp formatting in SSL modules.

The addition of chrono is directly used in stdlib/src/ssl/cert.rs for converting ASN1 certificate timestamps to human-readable date strings via the format_asn1_time() function. This is a core part of certificate handling and verification.

Comment on lines +116 to +134
# OpenSSL dependencies (optional, for ssl-openssl feature)
openssl = { version = "0.10.72", optional = true }
openssl-sys = { version = "0.9.110", optional = true }
openssl-probe = { version = "0.1.5", optional = true }
foreign-types-shared = { version = "0.1.1", optional = true }

# Rustls dependencies (optional, for ssl-rustls feature)
rustls = { version = "0.23.35", default-features = false, features = ["std", "tls12", "aws_lc_rs"], optional = true }
rustls-native-certs = { version = "0.8", optional = true }
rustls-pemfile = { version = "2.2", optional = true }
rustls-platform-verifier = { version = "0.6", optional = true }
x509-cert = { version = "0.2.5", features = ["pem", "builder"], optional = true }
x509-parser = { version = "0.16", optional = true }
der = { version = "0.7", features = ["alloc", "oid"], optional = true }
pem-rfc7468 = { version = "0.7", optional = true }
webpki-roots = { version = "0.26", optional = true }
aws-lc-rs = { version = "1.14.1", optional = true }
oid-registry = { version = "0.7", features = ["x509", "pkcs1", "nist_algs"], optional = true }
pkcs8 = { version = "0.10", features = ["encryption", "pkcs5", "pem"], optional = true }
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify crypto/TLS dependency versions remain current (8 months post knowledge cutoff).

The dependency versions were corrected in response to prior review feedback (x509-cert → 0.2.5, aws-lc-rs → 1.14.1), but with knowledge cutoff at March 2025 and current date November 2025, verify these are still the recommended stable versions:

  • rustls = 0.23.35
  • x509-cert = 0.2.5
  • aws-lc-rs = 1.14.1
  • openssl = 0.10.72

🌐 Web query:

What are the latest stable versions of rustls, x509-cert, aws-lc-rs, and openssl crates as of November 2025?

💡 Result:

As of November 15, 2025:

  • rustls — 0.23.28. [1]
  • x509-cert — 0.2.5. [2]
  • aws-lc-rs — 1.14.1. [3]
  • openssl (crate) — 0.10.72. [4]

Sources:
[1] rustls crate listing (lib.rs).
[2] rust-x509-cert package / crates listing (Fedora packages).
[3] aws-lc-rs docs/crate page (docs.rs).
[4] openssl crate update notices (crates.io / Fedora security advisory).


Downgrade rustls to 0.23.28 (current latest stable, not 0.23.35).

The latest stable version of rustls as of November 15, 2025 is 0.23.28, but line 127 declares 0.23.35. The other dependencies are correctly specified: x509-cert 0.2.5, aws-lc-rs 1.14.1, and openssl 0.10.72 are current.

Line 127:

- rustls = { version = "0.23.28", default-features = false, features = ["std", "tls12", "aws_lc_rs"], optional = true }
🤖 Prompt for AI Agents
In stdlib/Cargo.toml around lines 116 to 134, the rustls dependency is pinned to
version "0.23.35" but should be downgraded to "0.23.28"; update the rustls line
to use version = "0.23.28" (keeping default-features, features and optional
flags unchanged) so the Cargo.toml matches the current stable rustls version.

@youknowone youknowone merged commit 1a783fc into RustPython:main Nov 16, 2025
13 checks passed
@youknowone youknowone deleted the ssl branch November 16, 2025 13:17
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.

Check if rustls-openssl-compat can replace openssl [RFC] Add a feature to use Rustls instead of open-ssl

2 participants