mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Support custom rustls providers (#7657)
* Modularize rustls as work towards providers `rustls`'s architecture is very clean and trait-driven. There are many providers for `rustls` including the built-in `aws-lc-rs` and `ring` as well as backends for `boringssl`, `graviola`, `openssl`, `mbedtls`, etc. This commit removes the hard dependency on `aws-lc-rs` and adds support for `ring`. It works towards #7059 as well. * Clean up rustls features * Remove ring as an explicit feature * ssl-rustls is the default and implies aws-lc * Support custom rustls crypto providers The new feature, `ssl-rustls-no-provider`, enables custom rustls providers. By default, `aws-lc-rs` is enabled which matches the old behavior and keeps backward compatibility. I wrote a new type that abstracts what we need from crypto providers. CryptoExt encapsulates the ticketer as well as cipher suites and KX groups. I wrote fallbacks to help select a reasonable default if a provider is missing features (they all seem to support the same things though). I also wrote an example to show how to actually use custom providers. * Fix duplicate VERIFY_X509 constants and unused imports in ssl module Remove duplicate VERIFY_X509_STRICT/VERIFY_X509_PARTIAL_CHAIN definitions from compat.rs (already defined in _ssl module with #[pyattr]). Remove unused imports: ClientConnection, ServerConnection. * no-provider as default * Fix CI failures: openssl build, wasm target, cargo-shear - Update openssl.rs to use renamed sock_wait/SockWaitKind and add vm parameter - Add skip_ssl for wasm32-wasip2 target (aws-lc-sys cannot build for wasm) - Remove unused workspace dependency aws-lc-rs - Fix foreign-types-shared version to match openssl's dependency (0.1) - Restore Cargo.lock from upstream/main --------- Co-authored-by: Jeong, YunWon <jeong@youknowone.org>
This commit is contained in:
1 parent
1a959cf7f3
commit
30ae48b24b
17 files changed
+488
-121
No files matched your search
@@ -19,7 +19,7 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_ARGS: --no-default-features --features stdlib,importlib,stdio,encodings,sqlite,ssl-rustls,host_env
|
||||
CARGO_ARGS: --no-default-features --features stdlib,importlib,stdio,encodings,sqlite,ssl-rustls-aws-lc,host_env
|
||||
CARGO_ARGS_NO_SSL: --no-default-features --features stdlib,importlib,stdio,encodings,sqlite,host_env
|
||||
# Crates excluded from workspace builds:
|
||||
# - rustpython_wasm: requires wasm target
|
||||
@@ -186,6 +186,7 @@ jobs:
|
||||
skip_ssl: true
|
||||
- os: ubuntu-latest
|
||||
target: wasm32-wasip2
|
||||
skip_ssl: true
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-freebsd
|
||||
skip_ssl: true
|
||||
@@ -785,7 +786,7 @@ jobs:
|
||||
clang: true
|
||||
|
||||
- name: build rustpython
|
||||
run: cargo build --release --target wasm32-wasip1 --features freeze-stdlib,stdlib --verbose
|
||||
run: cargo build --release --target wasm32-wasip1 --no-default-features --features freeze-stdlib,stdlib,stdio,importlib,host_env --verbose
|
||||
- name: run snippets
|
||||
run: wasmer run --dir "$(pwd)" target/wasm32-wasip1/release/rustpython.wasm -- "$(pwd)/extra_tests/snippets/stdlib_random.py"
|
||||
- name: run cpython unittest
|
||||
|
||||
@@ -14,7 +14,7 @@ on:
|
||||
- .github/workflows/cron-ci.yaml
|
||||
|
||||
env:
|
||||
CARGO_ARGS: --no-default-features --features stdlib,importlib,stdio,encodings,ssl-rustls,jit,host_env
|
||||
CARGO_ARGS: --no-default-features --features stdlib,importlib,stdio,encodings,ssl-rustls-aws-lc,jit,host_env
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' # TODO: Remove on 2026/06/02
|
||||
|
||||
jobs:
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
- run: sudo apt-get update && sudo apt-get -y install lcov
|
||||
|
||||
- name: Run cargo-llvm-cov with Rust tests.
|
||||
run: cargo llvm-cov --no-report --workspace --exclude rustpython_wasm --exclude rustpython-compiler-source --exclude rustpython-venvlauncher --verbose --no-default-features --features stdlib,importlib,stdio,encodings,ssl-rustls,jit,host_env
|
||||
run: cargo llvm-cov --no-report --workspace --exclude rustpython_wasm --exclude rustpython-compiler-source --exclude rustpython-venvlauncher --verbose --no-default-features --features stdlib,importlib,stdio,encodings,ssl-rustls-aws-lc,jit,host_env
|
||||
|
||||
- name: Run cargo-llvm-cov with Python snippets.
|
||||
run: python scripts/cargo-llvm-cov.py
|
||||
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
libtool: true
|
||||
|
||||
- name: Build RustPython
|
||||
run: cargo build --release --target=${{ matrix.target }} --verbose --no-default-features --features stdlib,stdio,importlib,encodings,sqlite,host_env,ssl-rustls,threading,jit
|
||||
run: cargo build --release --target=${{ matrix.target }} --verbose --no-default-features --features stdlib,stdio,importlib,encodings,sqlite,host_env,ssl-rustls-aws-lc,threading,jit
|
||||
|
||||
- name: Rename Binary
|
||||
run: cp target/${{ matrix.target }}/release/rustpython target/rustpython-release-${{ runner.os }}-${{ matrix.target }}
|
||||
|
||||
@@ -19,7 +19,7 @@ env:
|
||||
CARGO_PROFILE_TEST_DEBUG: 0
|
||||
CARGO_PROFILE_DEV_DEBUG: 0
|
||||
CARGO_PROFILE_RELEASE_DEBUG: 0
|
||||
CARGO_ARGS: --workspace --no-default-features --features stdlib,importlib,stdio,encodings,sqlite,ssl-rustls,host_env,threading,jit --exclude rustpython_wasm --exclude rustpython-compiler-source --exclude rustpython-venvlauncher
|
||||
CARGO_ARGS: --workspace --no-default-features --features stdlib,importlib,stdio,encodings,sqlite,ssl-rustls-aws-lc,host_env,threading,jit --exclude rustpython_wasm --exclude rustpython-compiler-source --exclude rustpython-venvlauncher
|
||||
|
||||
jobs:
|
||||
build-caches:
|
||||
|
||||
Generated
+168
-18
@@ -256,7 +256,6 @@ checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f"
|
||||
dependencies = [
|
||||
"aws-lc-fips-sys",
|
||||
"aws-lc-sys",
|
||||
"untrusted 0.7.1",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -694,6 +693,17 @@ version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "core-models"
|
||||
version = "0.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "657f625ff361906f779745d08375ae3cc9fef87a35fba5f22874cf773010daf4"
|
||||
dependencies = [
|
||||
"hax-lib",
|
||||
"pastey",
|
||||
"rand 0.9.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpubits"
|
||||
version = "0.1.1"
|
||||
@@ -1318,7 +1328,7 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared 0.1.1",
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1327,12 +1337,6 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.3.0"
|
||||
@@ -1472,6 +1476,16 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "graviola"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4387e0458389da24c6fe732531e65595c7c4a32b027f98f4789e512e28224465"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom 0.3.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.7.1"
|
||||
@@ -1507,6 +1521,43 @@ version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
||||
|
||||
[[package]]
|
||||
name = "hax-lib"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "543f93241d32b3f00569201bfce9d7a93c92c6421b23c77864ac929dc947b9fc"
|
||||
dependencies = [
|
||||
"hax-lib-macros",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hax-lib-macros"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8755751e760b11021765bb04cb4a6c4e24742688d9f3aa14c2079638f537b0f"
|
||||
dependencies = [
|
||||
"hax-lib-macros-types",
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hax-lib-macros-types"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f177c9ae8ea456e2f71ff3c1ea47bf4464f772a05133fcbba56cd5ba169035a2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
@@ -1986,6 +2037,70 @@ version = "0.2.186"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-intrinsics"
|
||||
version = "0.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1b5db005ff8001e026b73a6842ee81bbef8ec5ff0e1915a67ae65fd2a9fafa5"
|
||||
dependencies = [
|
||||
"core-models",
|
||||
"hax-lib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-ml-kem"
|
||||
version = "0.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a14ab3e477de9df6ee1273a114018ff62c4996ca9220070c4e5cb1743f94a67d"
|
||||
dependencies = [
|
||||
"hax-lib",
|
||||
"libcrux-intrinsics",
|
||||
"libcrux-platform",
|
||||
"libcrux-secrets",
|
||||
"libcrux-sha3",
|
||||
"libcrux-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-platform"
|
||||
version = "0.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d9e21d7ed31a92ac539bd69a8c970b183ee883872d2d19ce27036e24cb8ecc4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-secrets"
|
||||
version = "0.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ce650f3041b44ba40d4263852347d007cd2cd9d1cc856a6f6c8b2e10c3fd40b"
|
||||
dependencies = [
|
||||
"hax-lib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-sha3"
|
||||
version = "0.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1ae0b7d0e1cc4793a609fd0ff2ca3b3a3fabae523770c619a3d4bc86417b0d7"
|
||||
dependencies = [
|
||||
"hax-lib",
|
||||
"libcrux-intrinsics",
|
||||
"libcrux-platform",
|
||||
"libcrux-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcrux-traits"
|
||||
version = "0.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812e4fa89f3f5e34b47f928b22b1b78395a0d4ec23b1f583db635f128159d65f"
|
||||
dependencies = [
|
||||
"libcrux-secrets",
|
||||
"rand 0.9.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libffi"
|
||||
version = "5.1.0"
|
||||
@@ -2532,6 +2647,12 @@ version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pastey"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4"
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.12.2"
|
||||
@@ -2781,6 +2902,28 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr2"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-utils"
|
||||
version = "0.10.0"
|
||||
@@ -3171,7 +3314,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom 0.2.17",
|
||||
"libc",
|
||||
"untrusted 0.9.0",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
@@ -3226,6 +3369,17 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-graviola"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "323c712e50c59ceb2ba9ad4d79dcfd3e0046a082d61efa87fcdf8f59af04473c"
|
||||
dependencies = [
|
||||
"graviola",
|
||||
"libcrux-ml-kem",
|
||||
"rustls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.8.3"
|
||||
@@ -3292,7 +3446,7 @@ dependencies = [
|
||||
"aws-lc-rs",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted 0.9.0",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3308,6 +3462,8 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"pyo3",
|
||||
"rustls",
|
||||
"rustls-graviola",
|
||||
"rustpython-capi",
|
||||
"rustpython-compiler",
|
||||
"rustpython-pylib",
|
||||
@@ -3591,7 +3747,6 @@ version = "0.5.0"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
"ascii",
|
||||
"aws-lc-rs",
|
||||
"base64",
|
||||
"blake2",
|
||||
"bzip2",
|
||||
@@ -3606,7 +3761,7 @@ dependencies = [
|
||||
"dyn-clone",
|
||||
"flame",
|
||||
"flate2",
|
||||
"foreign-types-shared 0.3.1",
|
||||
"foreign-types-shared",
|
||||
"gethostname",
|
||||
"hex",
|
||||
"hmac 0.12.1",
|
||||
@@ -4538,12 +4693,6 @@ dependencies = [
|
||||
"rand 0.8.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
@@ -4575,6 +4724,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
|
||||
dependencies = [
|
||||
"atomic",
|
||||
"getrandom 0.4.2",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
+20
-4
@@ -11,7 +11,7 @@ license.workspace = true
|
||||
|
||||
[features]
|
||||
capi = ["dep:rustpython-capi", "threading"]
|
||||
default = ["threading", "stdlib", "stdio", "importlib", "ssl-rustls", "host_env"]
|
||||
default = ["threading", "stdlib", "stdio", "importlib", "ssl-rustls-aws-lc", "host_env"]
|
||||
host_env = ["rustpython-vm/host_env", "rustpython-stdlib?/host_env"]
|
||||
importlib = ["rustpython-vm/importlib"]
|
||||
encodings = ["rustpython-vm/encodings"]
|
||||
@@ -22,8 +22,10 @@ freeze-stdlib = ["stdlib", "rustpython-vm/freeze-stdlib", "rustpython-pylib?/fre
|
||||
jit = ["rustpython-vm/jit"]
|
||||
threading = ["rustpython-vm/threading", "rustpython-stdlib/threading"]
|
||||
sqlite = ["rustpython-stdlib/sqlite"]
|
||||
ssl = []
|
||||
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-fips = ["ssl-rustls-aws-lc", "rustls/fips"]
|
||||
ssl-openssl = ["ssl", "rustpython-stdlib/ssl-openssl"]
|
||||
ssl-vendor = ["ssl-openssl", "rustpython-stdlib/ssl-vendor"]
|
||||
tkinter = ["rustpython-stdlib/tkinter"]
|
||||
@@ -46,6 +48,9 @@ dirs = "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 }
|
||||
|
||||
@@ -70,6 +75,17 @@ harness = false
|
||||
name = "rustpython"
|
||||
path = "src/main.rs"
|
||||
|
||||
[[example]]
|
||||
name = "custom_tls_providers"
|
||||
path = "examples/custom_tls_providers.rs"
|
||||
required-features = [
|
||||
"rustls-graviola",
|
||||
"rustls/ring",
|
||||
"rustpython-pylib/freeze-stdlib",
|
||||
"rustpython-stdlib/ssl-rustls",
|
||||
"rustpython-vm/freeze-stdlib",
|
||||
]
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
@@ -178,7 +194,6 @@ phf = { version = "0.13.1", default-features = false, features = ["macros"]}
|
||||
adler32 = "1.2.0"
|
||||
approx = "0.5.1"
|
||||
ascii = "1.1"
|
||||
aws-lc-rs = "1.16.3"
|
||||
base64 = "0.22"
|
||||
blake2 = "0.10.4"
|
||||
bitflags = "2.11.0"
|
||||
@@ -202,7 +217,7 @@ exitcode = "1.1.2"
|
||||
flame = "0.2.2"
|
||||
flamer = "0.5"
|
||||
flate2 = { version = "1.1.9", default-features = false }
|
||||
foreign-types-shared = "0.3.1"
|
||||
foreign-types-shared = "0.1"
|
||||
gethostname = "1.0.2"
|
||||
getrandom = { version = "0.3", features = ["std"] }
|
||||
glob = "0.3"
|
||||
@@ -263,6 +278,7 @@ rapidhash = "4.4.1"
|
||||
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-platform-verifier = "0.7"
|
||||
|
||||
@@ -102,7 +102,7 @@ rustpython
|
||||
|
||||
### SSL provider
|
||||
|
||||
For HTTPS requests, `ssl-rustls` feature is enabled by default. You can replace it with `ssl-openssl` feature if your environment requires OpenSSL.
|
||||
For HTTPS requests, `ssl-rustls-aws-lc` is enabled by default for the RustPython binary. Embedders can use `rustpython-stdlib`'s provider-agnostic `ssl-rustls` feature and install their own rustls crypto provider, or replace rustls with `ssl-openssl` if their environment requires OpenSSL.
|
||||
Note that to use OpenSSL on Windows, you may need to install OpenSSL, or you can enable the `ssl-vendor` feature instead,
|
||||
which compiles OpenSSL for you but requires a C compiler, perl, and `make`.
|
||||
OpenSSL version 3 is expected and tested in CI. Older versions may not work.
|
||||
|
||||
@@ -16,15 +16,16 @@ host_env = ["rustpython-vm/host_env"]
|
||||
compiler = ["rustpython-vm/compiler"]
|
||||
threading = ["rustpython-common/threading", "rustpython-vm/threading"]
|
||||
sqlite = ["dep:libsqlite3-sys"]
|
||||
# SSL backends - default to rustls
|
||||
ssl = []
|
||||
ssl-rustls = ["ssl", "rustls", "rustls-native-certs", "rustls-pemfile", "rustls-platform-verifier", "x509-cert", "x509-parser", "der", "pem-rfc7468", "webpki-roots", "aws-lc-rs", "oid-registry", "pkcs8"]
|
||||
ssl-rustls-fips = ["ssl-rustls", "aws-lc-rs/fips"]
|
||||
# SSL backends
|
||||
ssl = ["host_env"]
|
||||
ssl-rustls = ["__ssl-rustls", "rustls/custom-provider"]
|
||||
ssl-openssl = ["ssl", "openssl", "openssl-sys", "foreign-types-shared", "openssl-probe"]
|
||||
ssl-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"]
|
||||
|
||||
[dependencies]
|
||||
# rustpython crates
|
||||
rustpython-derive = { workspace = true }
|
||||
@@ -113,7 +114,7 @@ openssl-probe = { workspace = true, optional = true }
|
||||
foreign-types-shared = { workspace = true, optional = true }
|
||||
|
||||
# Rustls dependencies (optional, for ssl-rustls feature)
|
||||
rustls = { workspace = true, default-features = false, features = ["std", "tls12", "aws_lc_rs"], optional = true }
|
||||
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-platform-verifier = { workspace = true, optional = true }
|
||||
@@ -122,7 +123,6 @@ 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 }
|
||||
aws-lc-rs = { workspace = true, optional = true }
|
||||
oid-registry = { workspace = true, features = ["x509", "pkcs1", "nist_algs"], optional = true }
|
||||
pkcs8 = { workspace = true, features = ["encryption", "pkcs5", "pem"], optional = true }
|
||||
|
||||
|
||||
@@ -128,11 +128,11 @@ mod openssl;
|
||||
#[cfg(all(
|
||||
feature = "host_env",
|
||||
not(target_arch = "wasm32"),
|
||||
feature = "ssl-rustls"
|
||||
feature = "__ssl-rustls"
|
||||
))]
|
||||
mod ssl;
|
||||
pub mod ssl;
|
||||
|
||||
#[cfg(all(feature = "ssl-openssl", feature = "ssl-rustls", not(clippy)))]
|
||||
#[cfg(all(feature = "ssl-openssl", feature = "__ssl-rustls", not(clippy)))]
|
||||
compile_error!(r#"features "ssl-openssl" and "ssl-rustls" are mutually exclusive"#);
|
||||
|
||||
#[cfg(all(
|
||||
@@ -246,7 +246,7 @@ pub fn stdlib_module_defs(ctx: &Context) -> Vec<&'static builtins::PyModuleDef>
|
||||
#[cfg(all(
|
||||
feature = "host_env",
|
||||
not(target_arch = "wasm32"),
|
||||
feature = "ssl-rustls"
|
||||
feature = "__ssl-rustls"
|
||||
))]
|
||||
ssl::module_def(ctx),
|
||||
statistics::module_def(ctx),
|
||||
|
||||
@@ -2292,12 +2292,17 @@ mod _ssl {
|
||||
self.0.get_timeout().map(|d| Instant::now() + d)
|
||||
}
|
||||
|
||||
fn select(&self, needs: SslNeeds, deadline: &SocketDeadline) -> SelectRet {
|
||||
fn select(
|
||||
&self,
|
||||
needs: SslNeeds,
|
||||
deadline: &SocketDeadline,
|
||||
vm: &VirtualMachine,
|
||||
) -> SelectRet {
|
||||
let sock = match self.0.sock_opt() {
|
||||
Some(s) => s,
|
||||
None => return SelectRet::Closed,
|
||||
};
|
||||
// For blocking sockets without timeout, call sock_select with None timeout
|
||||
// For blocking sockets without timeout, call sock_wait with None timeout
|
||||
// to actually block waiting for data instead of busy-looping
|
||||
let timeout = match &deadline {
|
||||
Ok(deadline) => match deadline.checked_duration_since(Instant::now()) {
|
||||
@@ -2307,13 +2312,14 @@ mod _ssl {
|
||||
Err(true) => None, // Blocking: no timeout, wait indefinitely
|
||||
Err(false) => return SelectRet::Nonblocking,
|
||||
};
|
||||
let res = socket::sock_select(
|
||||
let res = socket::sock_wait(
|
||||
&sock,
|
||||
match needs {
|
||||
SslNeeds::Read => socket::SelectKind::Read,
|
||||
SslNeeds::Write => socket::SelectKind::Write,
|
||||
SslNeeds::Read => socket::SockWaitKind::Read,
|
||||
SslNeeds::Write => socket::SockWaitKind::Write,
|
||||
},
|
||||
timeout,
|
||||
vm,
|
||||
);
|
||||
match res {
|
||||
Ok(true) => SelectRet::TimedOut,
|
||||
@@ -2325,13 +2331,14 @@ mod _ssl {
|
||||
&self,
|
||||
err: &ssl::Error,
|
||||
deadline: &SocketDeadline,
|
||||
vm: &VirtualMachine,
|
||||
) -> (Option<SslNeeds>, SelectRet) {
|
||||
let needs = match err.code() {
|
||||
ssl::ErrorCode::WANT_READ => Some(SslNeeds::Read),
|
||||
ssl::ErrorCode::WANT_WRITE => Some(SslNeeds::Write),
|
||||
_ => None,
|
||||
};
|
||||
let state = needs.map_or(SelectRet::Ok, |needs| self.select(needs, deadline));
|
||||
let state = needs.map_or(SelectRet::Ok, |needs| self.select(needs, deadline, vm));
|
||||
(needs, state)
|
||||
}
|
||||
}
|
||||
@@ -2850,7 +2857,7 @@ mod _ssl {
|
||||
break;
|
||||
}
|
||||
// Wait briefly for peer's close_notify before retrying
|
||||
match socket_stream.select(SslNeeds::Read, &deadline) {
|
||||
match socket_stream.select(SslNeeds::Read, &deadline, vm) {
|
||||
SelectRet::TimedOut => {
|
||||
return Err(socket::timeout_error_msg(
|
||||
vm,
|
||||
@@ -2888,7 +2895,7 @@ mod _ssl {
|
||||
};
|
||||
|
||||
// Wait on the socket
|
||||
match socket_stream.select(needs, &deadline) {
|
||||
match socket_stream.select(needs, &deadline, vm) {
|
||||
SelectRet::TimedOut => {
|
||||
let msg = if err == sys::SSL_ERROR_WANT_READ {
|
||||
"The read operation timed out"
|
||||
@@ -2984,7 +2991,7 @@ mod _ssl {
|
||||
let (needs, state) = stream
|
||||
.get_ref()
|
||||
.expect("handshake called in bio mode; should only be called in socket mode")
|
||||
.socket_needs(&err, &timeout);
|
||||
.socket_needs(&err, &timeout, vm);
|
||||
match state {
|
||||
SelectRet::TimedOut => {
|
||||
// Clean up SNI ex_data before returning error
|
||||
@@ -3038,7 +3045,7 @@ mod _ssl {
|
||||
.get_ref()
|
||||
.expect("write called in bio mode; should only be called in socket mode");
|
||||
let timeout = socket_ref.timeout_deadline();
|
||||
let state = socket_ref.select(SslNeeds::Write, &timeout);
|
||||
let state = socket_ref.select(SslNeeds::Write, &timeout, vm);
|
||||
match state {
|
||||
SelectRet::TimedOut => {
|
||||
return Err(socket::timeout_error_msg(
|
||||
@@ -3058,7 +3065,7 @@ mod _ssl {
|
||||
let (needs, state) = stream
|
||||
.get_ref()
|
||||
.expect("write called in bio mode; should only be called in socket mode")
|
||||
.socket_needs(&err, &timeout);
|
||||
.socket_needs(&err, &timeout, vm);
|
||||
match state {
|
||||
SelectRet::TimedOut => {
|
||||
return Err(socket::timeout_error_msg(
|
||||
@@ -3229,7 +3236,7 @@ mod _ssl {
|
||||
let (needs, state) = stream
|
||||
.get_ref()
|
||||
.expect("read called in bio mode; should only be called in socket mode")
|
||||
.socket_needs(&err, &timeout);
|
||||
.socket_needs(&err, &timeout, vm);
|
||||
match state {
|
||||
SelectRet::TimedOut => {
|
||||
return Err(socket::timeout_error_msg(
|
||||
|
||||
+27
-20
@@ -25,6 +25,9 @@ mod compat;
|
||||
// SSL exception types (shared with openssl backend)
|
||||
mod error;
|
||||
|
||||
// Utilities for setting a Rustls cryptography provider.
|
||||
pub mod providers;
|
||||
|
||||
pub(crate) use _ssl::module_def;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
@@ -64,7 +67,6 @@ mod _ssl {
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
use rustls::crypto::aws_lc_rs::ALL_CIPHER_SUITES;
|
||||
use std::{
|
||||
collections::{HashMap, hash_map::DefaultHasher},
|
||||
io::BufRead,
|
||||
@@ -98,6 +100,8 @@ mod _ssl {
|
||||
get_cipher_encryption_desc, is_blocking_io_error, normalize_cipher_name, ssl_do_handshake,
|
||||
};
|
||||
|
||||
use super::providers::CryptoExt;
|
||||
|
||||
// Type aliases for better readability
|
||||
// Additional type alias for certificate/key pairs (SessionCache and SniCertName defined below)
|
||||
|
||||
@@ -635,7 +639,7 @@ mod _ssl {
|
||||
return Err("No cipher can be selected".to_string());
|
||||
}
|
||||
|
||||
let all_suites = ALL_CIPHER_SUITES;
|
||||
let all_suites = CryptoExt::get_ext().all_ciphers_or_default();
|
||||
let mut selected = Vec::new();
|
||||
|
||||
for part in cipher_str.split(':') {
|
||||
@@ -1058,6 +1062,8 @@ mod _ssl {
|
||||
|
||||
#[pymethod]
|
||||
fn load_cert_chain(&self, args: LoadCertChainArgs, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let crypto_ext = CryptoExt::get_ext();
|
||||
|
||||
// Parse certfile argument (str or bytes) to path
|
||||
let cert_path = Self::parse_path_arg(&args.certfile, vm)?;
|
||||
|
||||
@@ -1200,15 +1206,14 @@ mod _ssl {
|
||||
}
|
||||
|
||||
// Additional validation: Create CertifiedKey to ensure rustls accepts it
|
||||
let signing_key =
|
||||
rustls::crypto::aws_lc_rs::sign::any_supported_type(&key).map_err(|_| {
|
||||
vm.new_os_subtype_error(
|
||||
PySSLError::class(&vm.ctx).to_owned(),
|
||||
None,
|
||||
"[SSL: KEY_VALUES_MISMATCH] key values mismatch",
|
||||
)
|
||||
.upcast()
|
||||
})?;
|
||||
let signing_key = crypto_ext.any_supported_key(&key).map_err(|_| {
|
||||
vm.new_os_subtype_error(
|
||||
PySSLError::class(&vm.ctx).to_owned(),
|
||||
None,
|
||||
"[SSL: KEY_VALUES_MISMATCH] key values mismatch",
|
||||
)
|
||||
.upcast()
|
||||
})?;
|
||||
|
||||
let certified_key = CertifiedKey::new(full_chain.clone(), signing_key);
|
||||
if certified_key.keys_match().is_err() {
|
||||
@@ -1523,7 +1528,8 @@ mod _ssl {
|
||||
// Dynamically generate cipher list from rustls ALL_CIPHER_SUITES
|
||||
// This automatically includes all cipher suites supported by the current rustls version
|
||||
|
||||
let cipher_list = ALL_CIPHER_SUITES
|
||||
let cipher_list = CryptoExt::get_ext()
|
||||
.all_ciphers_or_default()
|
||||
.iter()
|
||||
.map(|suite| {
|
||||
// Extract cipher information using unified helper
|
||||
@@ -2217,6 +2223,8 @@ mod _ssl {
|
||||
(protocol,): Self::Args,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Self> {
|
||||
let crypto_ext = CryptoExt::get_ext();
|
||||
|
||||
// Validate protocol
|
||||
match protocol {
|
||||
PROTOCOL_TLS | PROTOCOL_TLS_CLIENT | PROTOCOL_TLS_SERVER | PROTOCOL_TLSv1_2
|
||||
@@ -2309,7 +2317,7 @@ mod _ssl {
|
||||
rustls_server_session_store: rustls::server::ServerSessionMemoryCache::new(
|
||||
SSL_SESSION_CACHE_SIZE,
|
||||
),
|
||||
server_ticketer: rustls::crypto::aws_lc_rs::Ticketer::new()
|
||||
server_ticketer: (crypto_ext.ticketer)()
|
||||
.expect("Failed to create shared ticketer for TLS 1.2 session resumption"),
|
||||
accept_count: AtomicUsize::new(0),
|
||||
session_hits: AtomicUsize::new(0),
|
||||
@@ -4864,35 +4872,34 @@ mod _ssl {
|
||||
|
||||
#[pyfunction]
|
||||
fn RAND_status() -> i32 {
|
||||
1 // Always have good randomness with aws-lc-rs
|
||||
1 // The configured rustls provider supplies cryptographic randomness.
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn RAND_add(_string: PyObjectRef, _entropy: f64) {
|
||||
// No-op: aws-lc-rs handles its own entropy
|
||||
// No-op: the configured rustls provider handles its own entropy.
|
||||
// Accept any type (str, bytes, bytearray)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn RAND_bytes(n: i64, vm: &VirtualMachine) -> PyResult<PyBytesRef> {
|
||||
use aws_lc_rs::rand::{SecureRandom, SystemRandom};
|
||||
|
||||
// Validate n is not negative
|
||||
if n < 0 {
|
||||
return Err(vm.new_value_error("num must be positive"));
|
||||
}
|
||||
|
||||
let n_usize = n as usize;
|
||||
let rng = SystemRandom::new();
|
||||
let mut buf = vec![0u8; n_usize];
|
||||
rng.fill(&mut buf)
|
||||
CryptoExt::get_provider()
|
||||
.secure_random
|
||||
.fill(&mut buf)
|
||||
.map_err(|_| vm.new_os_error("Failed to generate random bytes"))?;
|
||||
Ok(PyBytesRef::from(vm.ctx.new_bytes(buf)))
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn RAND_pseudo_bytes(n: i64, vm: &VirtualMachine) -> PyResult<(PyBytesRef, bool)> {
|
||||
// In rustls/aws-lc-rs, all random bytes are cryptographically strong
|
||||
// Rustls providers expose cryptographically strong random bytes.
|
||||
let bytes = RAND_bytes(n, vm)?;
|
||||
Ok((bytes, true))
|
||||
}
|
||||
|
||||
@@ -22,7 +22,10 @@ use rustpython_vm::{PyObjectRef, PyResult, VirtualMachine};
|
||||
use std::collections::HashSet;
|
||||
use x509_parser::prelude::*;
|
||||
|
||||
use super::_ssl::{VERIFY_X509_PARTIAL_CHAIN, VERIFY_X509_STRICT};
|
||||
use super::{
|
||||
_ssl::{VERIFY_X509_PARTIAL_CHAIN, VERIFY_X509_STRICT},
|
||||
providers::CryptoExt,
|
||||
};
|
||||
|
||||
// Certificate Verification Constants
|
||||
|
||||
@@ -1268,9 +1271,7 @@ pub(super) fn validate_cert_key_match(
|
||||
|
||||
// For rustls, the actual validation happens when creating CertifiedKey
|
||||
// We can attempt to create a signing key to verify the key is valid
|
||||
use rustls::crypto::aws_lc_rs::sign::any_supported_type;
|
||||
|
||||
match any_supported_type(private_key) {
|
||||
match CryptoExt::get_ext().any_supported_key(private_key) {
|
||||
Ok(_signing_key) => {
|
||||
// If we can create a signing key, the private key is valid
|
||||
// Rustls will validate the cert-key match when building config
|
||||
|
||||
@@ -20,22 +20,24 @@ use crate::vm::VirtualMachine;
|
||||
use alloc::sync::Arc;
|
||||
use parking_lot::RwLock as ParkingRwLock;
|
||||
use rustls::Connection;
|
||||
use rustls::RootCertStore;
|
||||
use rustls::client::ClientConfig;
|
||||
use rustls::crypto::SupportedKxGroup;
|
||||
use rustls::crypto::{CryptoProvider, SupportedKxGroup};
|
||||
use rustls::pki_types::{CertificateDer, CertificateRevocationListDer, PrivateKeyDer};
|
||||
use rustls::server::ResolvesServerCert;
|
||||
use rustls::server::ServerConfig;
|
||||
use rustls::server::{ProducesTickets, ResolvesServerCert, ServerConfig, WebPkiClientVerifier};
|
||||
use rustls::sign::CertifiedKey;
|
||||
use rustls::{RootCertStore, SupportedCipherSuite};
|
||||
use rustpython_vm::builtins::{PyBaseException, PyBaseExceptionRef};
|
||||
use rustpython_vm::convert::IntoPyException;
|
||||
use rustpython_vm::function::ArgBytesLike;
|
||||
use rustpython_vm::{AsObject, Py, PyObjectRef, PyPayload, PyResult, TryFromObject};
|
||||
use std::io::Read;
|
||||
use std::sync::Once;
|
||||
|
||||
use super::providers::CryptoExt;
|
||||
|
||||
// Import PySSLSocket from parent module
|
||||
use super::_ssl::{PySSLSocket, SSL3_RT_MAX_PACKET_SIZE, VERIFY_X509_STRICT};
|
||||
use super::_ssl::{
|
||||
PySSLSocket, SSL3_RT_MAX_PACKET_SIZE, VERIFY_X509_PARTIAL_CHAIN, VERIFY_X509_STRICT,
|
||||
};
|
||||
|
||||
// Import error types and helper functions from error module
|
||||
use super::error::{
|
||||
@@ -43,23 +45,6 @@ use super::error::{
|
||||
create_ssl_want_read_error, create_ssl_want_write_error, create_ssl_zero_return_error,
|
||||
};
|
||||
|
||||
// CryptoProvider Initialization:
|
||||
|
||||
/// Ensure the default CryptoProvider is installed (thread-safe, runs once)
|
||||
///
|
||||
/// This is necessary because rustls 0.23+ requires a process-level CryptoProvider
|
||||
/// to be installed before using default_provider(). We use Once to ensure this
|
||||
/// happens exactly once, even if called from multiple threads.
|
||||
static INIT_PROVIDER: Once = Once::new();
|
||||
|
||||
fn ensure_default_provider() {
|
||||
INIT_PROVIDER.call_once(|| {
|
||||
let _ = rustls::crypto::CryptoProvider::install_default(
|
||||
rustls::crypto::aws_lc_rs::default_provider(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// OpenSSL Constants:
|
||||
|
||||
// OpenSSL error library codes (include/openssl/err.h)
|
||||
@@ -449,7 +434,7 @@ pub(super) struct ServerConfigOptions {
|
||||
/// Session storage for server-side session resumption
|
||||
pub session_storage: Option<Arc<rustls::server::ServerSessionMemoryCache>>,
|
||||
/// Shared ticketer for TLS 1.2 session tickets (stateless resumption)
|
||||
pub ticketer: Option<Arc<dyn rustls::server::ProducesTickets>>,
|
||||
pub ticketer: Option<Arc<dyn ProducesTickets>>,
|
||||
}
|
||||
|
||||
/// Options for creating a client TLS configuration
|
||||
@@ -482,15 +467,14 @@ pub(super) struct ClientConfigOptions {
|
||||
/// This helper function consolidates the duplicated CryptoProvider creation logic
|
||||
/// for both server and client configurations.
|
||||
fn create_custom_crypto_provider(
|
||||
cipher_suites: Option<Vec<rustls::SupportedCipherSuite>>,
|
||||
kx_groups: Option<Vec<&'static dyn rustls::crypto::SupportedKxGroup>>,
|
||||
) -> Arc<rustls::crypto::CryptoProvider> {
|
||||
use rustls::crypto::aws_lc_rs::{ALL_CIPHER_SUITES, ALL_KX_GROUPS};
|
||||
let default_provider = rustls::crypto::aws_lc_rs::default_provider();
|
||||
cipher_suites: Option<Vec<SupportedCipherSuite>>,
|
||||
kx_groups: Option<Vec<&'static dyn SupportedKxGroup>>,
|
||||
) -> Arc<CryptoProvider> {
|
||||
let default_provider = CryptoExt::get_provider();
|
||||
|
||||
Arc::new(rustls::crypto::CryptoProvider {
|
||||
cipher_suites: cipher_suites.unwrap_or_else(|| ALL_CIPHER_SUITES.to_vec()),
|
||||
kx_groups: kx_groups.unwrap_or_else(|| ALL_KX_GROUPS.to_vec()),
|
||||
Arc::new(CryptoProvider {
|
||||
cipher_suites: cipher_suites.unwrap_or_else(|| default_provider.cipher_suites.clone()),
|
||||
kx_groups: kx_groups.unwrap_or_else(|| default_provider.kx_groups.clone()),
|
||||
signature_verification_algorithms: default_provider.signature_verification_algorithms,
|
||||
secure_random: default_provider.secure_random,
|
||||
key_provider: default_provider.key_provider,
|
||||
@@ -502,11 +486,6 @@ fn create_custom_crypto_provider(
|
||||
/// This abstracts the complex rustls ServerConfig building logic,
|
||||
/// matching SSL_CTX initialization for server sockets.
|
||||
pub(super) fn create_server_config(options: ServerConfigOptions) -> Result<ServerConfig, String> {
|
||||
use rustls::server::WebPkiClientVerifier;
|
||||
|
||||
// Ensure default CryptoProvider is installed
|
||||
ensure_default_provider();
|
||||
|
||||
// Create custom crypto provider using helper function
|
||||
let custom_provider = create_custom_crypto_provider(
|
||||
options.protocol_settings.cipher_suites.clone(),
|
||||
@@ -684,9 +663,6 @@ fn apply_alpn_with_fallback(config_alpn: &mut Vec<Vec<u8>>, alpn_protocols: &[Ve
|
||||
/// This abstracts the complex rustls ClientConfig building logic,
|
||||
/// matching SSL_CTX initialization for client sockets.
|
||||
pub(super) fn create_client_config(options: ClientConfigOptions) -> Result<ClientConfig, String> {
|
||||
// Ensure default CryptoProvider is installed
|
||||
ensure_default_provider();
|
||||
|
||||
// Create custom crypto provider using helper function
|
||||
let custom_provider = create_custom_crypto_provider(
|
||||
options.protocol_settings.cipher_suites.clone(),
|
||||
@@ -754,7 +730,6 @@ pub(super) fn create_client_config(options: ClientConfigOptions) -> Result<Clien
|
||||
};
|
||||
|
||||
// Wrap with PartialChainVerifier if VERIFY_X509_PARTIAL_CHAIN is set
|
||||
const VERIFY_X509_PARTIAL_CHAIN: i32 = 0x80000;
|
||||
let verifier = if options.verify_flags & VERIFY_X509_PARTIAL_CHAIN != 0 {
|
||||
use crate::ssl::cert::PartialChainVerifier;
|
||||
Arc::new(PartialChainVerifier::new(
|
||||
@@ -2123,8 +2098,7 @@ pub(super) fn curve_name_to_kx_group(
|
||||
curve: &str,
|
||||
) -> Result<Vec<&'static dyn SupportedKxGroup>, String> {
|
||||
// Get the default crypto provider's key exchange groups
|
||||
let provider = rustls::crypto::aws_lc_rs::default_provider();
|
||||
let all_groups = &provider.kx_groups;
|
||||
let all_groups = CryptoExt::get_ext().all_kx_or_default();
|
||||
|
||||
match curve {
|
||||
// P-256 (also known as secp256r1 or prime256v1)
|
||||
@@ -2149,14 +2123,12 @@ pub(super) fn curve_name_to_kx_group(
|
||||
.map(|g| vec![*g])
|
||||
.ok_or_else(|| "X25519 not supported by crypto provider".to_owned()),
|
||||
// P-521 (also known as secp521r1 or prime521v1)
|
||||
// Now supported with aws-lc-rs crypto provider
|
||||
"prime521v1" | "secp521r1" => all_groups
|
||||
.iter()
|
||||
.find(|g| g.name() == rustls::NamedGroup::secp521r1)
|
||||
.map(|g| vec![*g])
|
||||
.ok_or_else(|| "secp521r1 not supported by crypto provider".to_owned()),
|
||||
// X448
|
||||
// Now supported with aws-lc-rs crypto provider
|
||||
"X448" | "x448" => all_groups
|
||||
.iter()
|
||||
.find(|g| g.name() == rustls::NamedGroup::X448)
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
//! Utilities for user-settable cryptography providers.
|
||||
//!
|
||||
//! This has two main moving parts: [`CryptoProvider`] and [`CryptoExt`]. [`CryptoProvider`]
|
||||
//! is always implemented by the cryptography crate because it's a trait from Rustls. RustPython
|
||||
//! needs some extra data such as all of the cipher suites supported by an implementation.
|
||||
//! The [`CryptoExt`] table stores that extra data if it exists and provides convenience methods
|
||||
//! as a fallback.
|
||||
//!
|
||||
//! Both the [`CryptoProvider`] and [`CryptoExt`] are process-level structs that need to be
|
||||
//! set before any TLS operations. [`CryptoExt::set_provider`] is thread-safe and runs once.
|
||||
//! It sets both once per process.
|
||||
|
||||
use alloc::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use rustls::{
|
||||
Error, SignatureScheme, SupportedCipherSuite,
|
||||
crypto::{CryptoProvider, SupportedKxGroup},
|
||||
pki_types::PrivateKeyDer,
|
||||
server::ProducesTickets,
|
||||
sign::SigningKey,
|
||||
};
|
||||
|
||||
static CRYPTO_EXT: OnceLock<CryptoExt> = OnceLock::new();
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct CryptoExt {
|
||||
pub all_cipher_suites: Option<&'static [SupportedCipherSuite]>,
|
||||
pub all_kx_groups: Option<&'static [&'static dyn SupportedKxGroup]>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub any_supported_key: Option<fn(&PrivateKeyDer<'_>) -> Result<Arc<dyn SigningKey>, Error>>,
|
||||
pub ticketer: fn() -> Result<Arc<dyn ProducesTickets>, Error>,
|
||||
}
|
||||
|
||||
impl CryptoExt {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn get_ext() -> &'static Self {
|
||||
CRYPTO_EXT
|
||||
.get()
|
||||
.expect("A CryptoProvider must be set before TLS")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn get_provider() -> &'static CryptoProvider {
|
||||
CryptoProvider::get_default().expect("A CryptoProvider must be set before TLS")
|
||||
}
|
||||
|
||||
/// Returns all [`SupportedCipherSuite`] or the provider's defaults.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if a [`CryptoProvider`] hasn't been set.
|
||||
#[must_use]
|
||||
pub fn all_ciphers_or_default(&self) -> &'static [SupportedCipherSuite] {
|
||||
self.all_cipher_suites.unwrap_or_else(|| {
|
||||
CryptoProvider::get_default()
|
||||
.expect("A CryptoProvider has been set if CryptoExt is set")
|
||||
.cipher_suites
|
||||
.as_slice()
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns all [`SupportedKxGroup`] or the provider's defaults.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if a [`CryptoProvider`] hasn't been set.
|
||||
#[must_use]
|
||||
pub fn all_kx_or_default(&self) -> &'static [&'static dyn SupportedKxGroup] {
|
||||
self.all_kx_groups.unwrap_or_else(|| {
|
||||
CryptoProvider::get_default()
|
||||
.expect("A CryptoProvider has been set if CryptoExt is set")
|
||||
.kx_groups
|
||||
.as_slice()
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the first supported [`SigningKey`] for a [`PrivateKeyDer`].
|
||||
///
|
||||
/// Ideally, this function should be provided by the backend implementation or
|
||||
/// the user. This fallback filters out insecure algorithms then picks the first available key
|
||||
/// if it exists.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if a [`CryptoProvider`] hasn't been set.
|
||||
pub fn any_supported_key(&self, der: &PrivateKeyDer<'_>) -> Result<Arc<dyn SigningKey>, Error> {
|
||||
self.any_supported_key.map_or_else(
|
||||
|| {
|
||||
let provider = CryptoProvider::get_default()
|
||||
.expect("A CryptoProvider has been set if CryptoExt is set");
|
||||
let key = provider.key_provider.load_private_key(der.clone_key())?;
|
||||
|
||||
for scheme in provider
|
||||
.signature_verification_algorithms
|
||||
.mapping
|
||||
.iter()
|
||||
.filter_map(|(scheme, _)| {
|
||||
(!matches!(
|
||||
scheme,
|
||||
SignatureScheme::RSA_PKCS1_SHA1
|
||||
| SignatureScheme::ECDSA_SHA1_Legacy
|
||||
| SignatureScheme::Unknown(_),
|
||||
))
|
||||
.then_some(*scheme)
|
||||
})
|
||||
{
|
||||
if key.choose_scheme(&[scheme]).is_some() {
|
||||
return Ok(key);
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::General(
|
||||
"failed to parse private key as RSA, ECDSA, or EdDSA".into(),
|
||||
))
|
||||
},
|
||||
|f| f(der),
|
||||
)
|
||||
}
|
||||
|
||||
/// Set a process-level [`CryptoProvider`] and [`CryptoExt`].
|
||||
///
|
||||
/// A provider must be set before any cryptographic operations. All crypto ops panic if a provider
|
||||
/// is unset.
|
||||
pub fn set_provider(provider: CryptoProvider, extension: Self) -> Result<(), Error> {
|
||||
provider
|
||||
.install_default()
|
||||
.map_err(|_| Error::General("A default CryptoProvider is already set".into()))?;
|
||||
CRYPTO_EXT
|
||||
.set(extension)
|
||||
.map_err(|_| Error::General("A CryptoExt is already set".into()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
//! Example project to demonstrate how to set a custom rustls provider for RustPython.
|
||||
|
||||
// spell-checker: ignore graviola
|
||||
|
||||
use std::env;
|
||||
|
||||
use rustls::crypto::ring;
|
||||
use rustpython_pylib::FROZEN_STDLIB;
|
||||
use rustpython_stdlib::{ssl::providers::CryptoExt, stdlib_module_defs};
|
||||
use rustpython_vm::Interpreter;
|
||||
|
||||
const SCRIPT: &str = r#"
|
||||
import urllib.request
|
||||
|
||||
with urllib.request.urlopen("https://python.org") as response:
|
||||
assert response.status == 200
|
||||
"#;
|
||||
|
||||
fn main() {
|
||||
let provider = env::args()
|
||||
.skip(1)
|
||||
.find_map(|arg| match &*arg {
|
||||
"--ring" => Some("ring"),
|
||||
"--graviola" => Some("graviola"),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or("ring");
|
||||
|
||||
match provider {
|
||||
"ring" => {
|
||||
let ext = CryptoExt {
|
||||
all_cipher_suites: Some(ring::ALL_CIPHER_SUITES),
|
||||
all_kx_groups: Some(ring::ALL_KX_GROUPS),
|
||||
any_supported_key: Some(ring::sign::any_supported_type),
|
||||
ticketer: ring::Ticketer::new,
|
||||
};
|
||||
CryptoExt::set_provider(ring::default_provider(), ext).unwrap();
|
||||
println!("Using ring for cryptography");
|
||||
}
|
||||
"graviola" => {
|
||||
let ext = CryptoExt {
|
||||
all_cipher_suites: Some(rustls_graviola::suites::ALL_CIPHER_SUITES),
|
||||
all_kx_groups: Some(rustls_graviola::kx::ALL_KX_GROUPS),
|
||||
any_supported_key: None,
|
||||
ticketer: rustls_graviola::Ticketer::new,
|
||||
};
|
||||
CryptoExt::set_provider(rustls_graviola::default_provider(), ext).unwrap();
|
||||
println!("Using Graviola for cryptography");
|
||||
}
|
||||
unsupported => panic!("Unsupported provider: {unsupported}"),
|
||||
}
|
||||
|
||||
let builder = Interpreter::builder(Default::default());
|
||||
let defs = stdlib_module_defs(&builder.ctx);
|
||||
let result = builder
|
||||
.add_native_modules(&defs)
|
||||
.add_frozen_modules(FROZEN_STDLIB)
|
||||
.build()
|
||||
.run(|vm| {
|
||||
let scope = vm.new_scope_with_builtins();
|
||||
vm.run_block_expr(scope, SCRIPT).map(|_| ())
|
||||
});
|
||||
|
||||
assert_eq!(0, result);
|
||||
}
|
||||
@@ -14,6 +14,8 @@ impl InterpreterBuilderExt for InterpreterBuilder {
|
||||
fn init_stdlib(self) -> Self {
|
||||
let defs = rustpython_stdlib::stdlib_module_defs(&self.ctx);
|
||||
let builder = self.add_native_modules(&defs);
|
||||
#[cfg(all(feature = "ssl-rustls-aws-lc", not(target_arch = "wasm32")))]
|
||||
let builder = builder.init_hook(install_default_tls_provider);
|
||||
|
||||
cfg_select! {
|
||||
feature = "freeze-stdlib" => {
|
||||
@@ -26,6 +28,20 @@ impl InterpreterBuilderExt for InterpreterBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "ssl-rustls-aws-lc", not(target_arch = "wasm32")))]
|
||||
fn install_default_tls_provider(_vm: &mut crate::VirtualMachine) {
|
||||
use rustls::crypto::aws_lc_rs;
|
||||
use rustpython_stdlib::ssl::providers::CryptoExt;
|
||||
|
||||
let ext = CryptoExt {
|
||||
all_cipher_suites: Some(aws_lc_rs::ALL_CIPHER_SUITES),
|
||||
all_kx_groups: Some(aws_lc_rs::ALL_KX_GROUPS),
|
||||
any_supported_key: Some(aws_lc_rs::sign::any_supported_type),
|
||||
ticketer: aws_lc_rs::Ticketer::new,
|
||||
};
|
||||
let _ = CryptoExt::set_provider(aws_lc_rs::default_provider(), ext);
|
||||
}
|
||||
|
||||
/// Set stdlib_dir for frozen standard library
|
||||
#[cfg(all(feature = "stdlib", feature = "freeze-stdlib"))]
|
||||
fn set_frozen_stdlib_dir(vm: &mut crate::VirtualMachine) {
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ pub use shell::run_shell;
|
||||
not(any(feature = "ssl-rustls", feature = "ssl-openssl"))
|
||||
))]
|
||||
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\" is now enabled by either \"ssl-rustls\" or \"ssl-openssl\". Do not manually pass \"ssl\" feature. To enable ssl-openssl, use --no-default-features to disable ssl-rustls*"
|
||||
);
|
||||
|
||||
/// The main cli of the `rustpython` interpreter. This function will return `std::process::ExitCode`
|
||||
|
||||
Reference in new issue
Block a user