Pewarta Proxy

📋 Server Request & Proxy Logs

Loading active proxy settings...
Developed by Khairul Adha (adhexa.id)
Mengambil log server...

⚙️ Pengaturan Proxy Target

Skip to content

Commit 62ac0af

Browse files
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.
1 parent 69d1181 commit 62ac0af

10 files changed

Lines changed: 581 additions & 57 deletions

File tree

‎Cargo.lock‎

Lines changed: 348 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ threading = ["rustpython-vm/threading", "rustpython-stdlib/threading"]
2424
sqlite = ["rustpython-stdlib/sqlite"]
2525
ssl = []
2626
ssl-rustls = ["ssl", "rustpython-stdlib/ssl-rustls"]
27+
ssl-rustls-no-provider = ["ssl", "rustpython-stdlib/ssl-rustls-no-provider"]
2728
ssl-openssl = ["ssl", "rustpython-stdlib/ssl-openssl"]
2829
ssl-vendor = ["ssl-openssl", "rustpython-stdlib/ssl-vendor"]
2930
tkinter = ["rustpython-stdlib/tkinter"]
@@ -130,6 +131,7 @@ resolver = "2"
130131
members = [
131132
".",
132133
"crates/*",
134+
"examples/tls_providers",
133135
]
134136
exclude = ["pymath"]
135137

‎crates/stdlib/Cargo.toml‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ threading = ["rustpython-common/threading", "rustpython-vm/threading"]
1818
sqlite = ["dep:libsqlite3-sys"]
1919
# SSL backends - default to rustls
2020
ssl = []
21-
ssl-rustls = ["__ssl-rustls", "rustls/aws_lc_rs", "aws-lc-rs"]
21+
ssl-rustls = ["__ssl-rustls", "rustls/aws_lc_rs"]
2222
ssl-rustls-fips = ["__ssl-rustls", "rustls/aws_lc_rs", "aws-lc-rs", "aws-lc-rs/fips"]
23+
ssl-rustls-no-provider = ["__ssl-rustls", "rustls/custom-provider"]
2324
ssl-openssl = ["ssl", "openssl", "openssl-sys", "foreign-types-shared", "openssl-probe"]
2425
ssl-vendor = ["ssl-openssl", "openssl/vendored"]
2526
tkinter = ["dep:tk-sys", "dep:tcl-sys", "dep:widestring"]

‎crates/stdlib/src/lib.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ mod openssl;
108108
not(target_arch = "wasm32"),
109109
feature = "__ssl-rustls"
110110
))]
111-
mod ssl;
111+
pub mod ssl;
112112
#[cfg(all(feature = "ssl-openssl", feature = "__ssl-rustls"))]
113113
compile_error!("features \"ssl-openssl\" and the \"ssl-rustls-*\" family are mutually exclusive");
114114

‎crates/stdlib/src/ssl.rs‎

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ mod compat;
2626
mod error;
2727

2828
pub(crate) use _ssl::module_def;
29+
pub use compat::{CryptoExt, set_crypto_provider};
30+
31+
#[cfg(all(feature = "ssl-rustls", not(feature = "ssl-rustls-no-provider")))]
32+
pub use compat::set_aws_lc_provider;
2933

3034
#[allow(non_snake_case)]
3135
#[allow(non_upper_case_globals)]
@@ -37,7 +41,6 @@ mod _ssl {
3741
lock::{PyMutex, PyRwLock},
3842
},
3943
socket::{PySocket, SelectKind, sock_select, timeout_error_msg},
40-
ssl::compat,
4144
vm::{
4245
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
4346
VirtualMachine,
@@ -85,9 +88,6 @@ mod _ssl {
8588
};
8689
use sha2::{Digest, Sha256};
8790

88-
#[cfg(feature = "ssl-rustls")]
89-
use rustls::crypto::aws_lc_rs::{ALL_CIPHER_SUITES, Ticketer, sign};
90-
9191
// Import certificate operations module
9292
use super::cert;
9393

@@ -96,10 +96,10 @@ mod _ssl {
9696

9797
// Import compat module (OpenSSL compatibility layer)
9898
use super::compat::{
99-
ClientConfigOptions, MultiCertResolver, ProtocolSettings, ServerConfigOptions, SslError,
100-
TlsConnection, create_client_config, create_server_config, curve_name_to_kx_group,
101-
extract_cipher_info, get_cipher_encryption_desc, is_blocking_io_error,
102-
normalize_cipher_name, ssl_do_handshake,
99+
CRYPTO_EXT, ClientConfigOptions, MultiCertResolver, ProtocolSettings, ServerConfigOptions,
100+
SslError, TlsConnection, create_client_config, create_server_config,
101+
curve_name_to_kx_group, extract_cipher_info, get_cipher_encryption_desc,
102+
is_blocking_io_error, normalize_cipher_name, ssl_do_handshake,
103103
};
104104

105105
// Type aliases for better readability
@@ -615,7 +615,10 @@ mod _ssl {
615615
return Err("No cipher can be selected".to_string());
616616
}
617617

618-
let all_suites = ALL_CIPHER_SUITES;
618+
let crypto_ext = CRYPTO_EXT
619+
.get()
620+
.expect("A CryptoProvider must be set before SSL");
621+
let all_suites = crypto_ext.all_ciphers_or_default();
619622
let mut selected = Vec::new();
620623

621624
for part in cipher_str.split(':') {
@@ -1050,6 +1053,10 @@ mod _ssl {
10501053

10511054
#[pymethod]
10521055
fn load_cert_chain(&self, args: LoadCertChainArgs, vm: &VirtualMachine) -> PyResult<()> {
1056+
let crypto_ext = CRYPTO_EXT
1057+
.get()
1058+
.expect("A CryptoProvider must be set before SSL");
1059+
10531060
// Parse certfile argument (str or bytes) to path
10541061
let cert_path = Self::parse_path_arg(&args.certfile, vm)?;
10551062

@@ -1192,7 +1199,7 @@ mod _ssl {
11921199
}
11931200

11941201
// Additional validation: Create CertifiedKey to ensure rustls accepts it
1195-
let signing_key = sign::any_supported_type(&key).map_err(|_| {
1202+
let signing_key = crypto_ext.any_supported_key(&key).map_err(|_| {
11961203
vm.new_os_subtype_error(
11971204
PySSLError::class(&vm.ctx).to_owned(),
11981205
None,
@@ -1523,7 +1530,11 @@ mod _ssl {
15231530
// Dynamically generate cipher list from rustls ALL_CIPHER_SUITES
15241531
// This automatically includes all cipher suites supported by the current rustls version
15251532

1526-
let cipher_list = ALL_CIPHER_SUITES
1533+
let crypto_ext = CRYPTO_EXT
1534+
.get()
1535+
.expect("A CryptoProvider must be set before SSL");
1536+
let cipher_list = crypto_ext
1537+
.all_ciphers_or_default()
15271538
.iter()
15281539
.map(|suite| {
15291540
// Extract cipher information using unified helper
@@ -2203,6 +2214,10 @@ mod _ssl {
22032214
(protocol,): Self::Args,
22042215
vm: &VirtualMachine,
22052216
) -> PyResult<Self> {
2217+
let crypto_ext = CRYPTO_EXT
2218+
.get()
2219+
.expect("A CryptoProvider must be set before SSL");
2220+
22062221
// Validate protocol
22072222
match protocol {
22082223
PROTOCOL_TLS | PROTOCOL_TLS_CLIENT | PROTOCOL_TLS_SERVER | PROTOCOL_TLSv1_2
@@ -2297,7 +2312,7 @@ mod _ssl {
22972312
rustls_server_session_store: rustls::server::ServerSessionMemoryCache::new(
22982313
SSL_SESSION_CACHE_SIZE,
22992314
),
2300-
server_ticketer: Ticketer::new()
2315+
server_ticketer: (crypto_ext.ticketer)()
23012316
.expect("Failed to create shared ticketer for TLS 1.2 session resumption"),
23022317
accept_count: AtomicUsize::new(0),
23032318
session_hits: AtomicUsize::new(0),
@@ -4874,9 +4889,8 @@ mod _ssl {
48744889

48754890
#[pyfunction]
48764891
fn RAND_bytes(n: i64, vm: &VirtualMachine) -> PyResult<PyBytesRef> {
4877-
compat::ensure_default_provider();
48784892
let default_provider =
4879-
CryptoProvider::get_default().expect("A CryptoProvider should have been set earlier");
4893+
CryptoProvider::get_default().expect("A CryptoProvider must be set before SSL");
48804894

48814895
// Validate n is not negative
48824896
if n < 0 {

‎crates/stdlib/src/ssl/cert.rs‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,7 @@ use rustpython_vm::{PyObjectRef, PyResult, VirtualMachine};
2222
use std::collections::HashSet;
2323
use x509_parser::prelude::*;
2424

25-
use super::compat::{VERIFY_X509_PARTIAL_CHAIN, VERIFY_X509_STRICT};
26-
27-
#[cfg(feature = "ssl-rustls")]
28-
use rustls::crypto::aws_lc_rs::sign;
25+
use super::compat::{CRYPTO_EXT, VERIFY_X509_PARTIAL_CHAIN, VERIFY_X509_STRICT};
2926

3027
// Certificate Verification Constants
3128

@@ -1268,10 +1265,13 @@ pub(super) fn validate_cert_key_match(
12681265
if certs.is_empty() {
12691266
return Err("Certificate chain is empty".to_string());
12701267
}
1268+
let crypto_ext = CRYPTO_EXT
1269+
.get()
1270+
.expect("A CryptoProvider must be set before SSL");
12711271

12721272
// For rustls, the actual validation happens when creating CertifiedKey
12731273
// We can attempt to create a signing key to verify the key is valid
1274-
match sign::any_supported_type(private_key) {
1274+
match crypto_ext.any_supported_key(private_key) {
12751275
Ok(_signing_key) => {
12761276
// If we can create a signing key, the private key is valid
12771277
// Rustls will validate the cert-key match when building config

‎crates/stdlib/src/ssl/compat.rs‎

Lines changed: 126 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,20 @@ use crate::socket::{SelectKind, timeout_error_msg};
1515
use crate::vm::VirtualMachine;
1616
use alloc::sync::Arc;
1717
use parking_lot::RwLock as ParkingRwLock;
18-
use rustls::RootCertStore;
19-
use rustls::client::ClientConfig;
20-
use rustls::client::ClientConnection;
21-
use rustls::crypto::SupportedKxGroup;
18+
use rustls::client::{ClientConfig, ClientConnection};
19+
use rustls::crypto::{CryptoProvider, SupportedKxGroup};
2220
use rustls::pki_types::{CertificateDer, CertificateRevocationListDer, PrivateKeyDer};
23-
use rustls::server::ResolvesServerCert;
24-
use rustls::server::ServerConfig;
25-
use rustls::server::ServerConnection;
26-
use rustls::sign::CertifiedKey;
21+
use rustls::server::{
22+
ProducesTickets, ResolvesServerCert, ServerConfig, ServerConnection, WebPkiClientVerifier,
23+
};
24+
use rustls::sign::{CertifiedKey, SigningKey};
25+
use rustls::{RootCertStore, SignatureScheme, SupportedCipherSuite};
2726
use rustpython_vm::builtins::{PyBaseException, PyBaseExceptionRef};
2827
use rustpython_vm::convert::IntoPyException;
2928
use rustpython_vm::function::ArgBytesLike;
3029
use rustpython_vm::{AsObject, Py, PyObjectRef, PyPayload, PyResult, TryFromObject};
3130
use std::io::Read;
32-
use std::sync::Once;
31+
use std::sync::OnceLock;
3332

3433
// Import PySSLSocket from parent module
3534
use super::_ssl::PySSLSocket;
@@ -57,14 +56,114 @@ pub(super) const VERIFY_X509_PARTIAL_CHAIN: i32 = 0x80000;
5756
/// This is necessary because rustls 0.23+ requires a process-level CryptoProvider
5857
/// to be installed before using default_provider(). We use Once to ensure this
5958
/// happens exactly once, even if called from multiple threads.
60-
static INIT_PROVIDER: Once = Once::new();
61-
62-
pub(super) fn ensure_default_provider() {
63-
INIT_PROVIDER.call_once(|| {
64-
let _ = rustls::crypto::CryptoProvider::install_default(
65-
rustls::crypto::aws_lc_rs::default_provider(),
66-
);
67-
});
59+
pub(super) static CRYPTO_EXT: OnceLock<CryptoExt> = OnceLock::new();
60+
61+
#[derive(Clone, Copy)]
62+
pub struct CryptoExt {
63+
pub all_cipher_suites: Option<&'static [SupportedCipherSuite]>,
64+
pub all_kx_groups: Option<&'static [&'static dyn SupportedKxGroup]>,
65+
pub any_supported_key:
66+
Option<fn(&PrivateKeyDer<'_>) -> Result<Arc<dyn SigningKey>, rustls::Error>>,
67+
pub ticketer: fn() -> Result<Arc<dyn ProducesTickets>, rustls::Error>,
68+
}
69+
70+
impl CryptoExt {
71+
/// Returns all [`SupportedCipherSuite`] or the provider's defaults.
72+
///
73+
/// # Panics
74+
/// Panics if a [`CryptoProvider`] hasn't been set.
75+
pub fn all_ciphers_or_default(&self) -> &'static [SupportedCipherSuite] {
76+
self.all_cipher_suites.unwrap_or_else(|| {
77+
CryptoProvider::get_default()
78+
.expect("A CryptoProvider has been set if CryptoExt is set")
79+
.cipher_suites
80+
.as_slice()
81+
})
82+
}
83+
84+
/// Returns all [`SupportedKxGroup`] or the provider's defaults.
85+
///
86+
/// # Panics
87+
/// Panics if a [`CryptoProvider`] hasn't been set.
88+
pub fn all_kx_or_default(&self) -> &'static [&'static dyn SupportedKxGroup] {
89+
self.all_kx_groups.unwrap_or_else(|| {
90+
CryptoProvider::get_default()
91+
.expect("A CryptoProvider has been set if CryptoExt is set")
92+
.kx_groups
93+
.as_slice()
94+
})
95+
}
96+
97+
/// Return the first supported [`SigningKey`] for a [`PrivateKeyDer`].
98+
///
99+
/// Ideally, this function should be provided by the backend implementation or
100+
/// the user. This fallback filters out insecure algorithms then picks the first available key
101+
/// if it exists.
102+
///
103+
/// # Panics
104+
/// Panics if a [`CryptoProvider`] hasn't been set.
105+
pub fn any_supported_key(
106+
&self,
107+
der: &PrivateKeyDer<'_>,
108+
) -> Result<Arc<dyn SigningKey>, rustls::Error> {
109+
self.any_supported_key.map(|f| f(der)).unwrap_or_else(|| {
110+
let provider = CryptoProvider::get_default()
111+
.expect("A CryptoProvider has been set if CryptoExt is set");
112+
let key = provider.key_provider.load_private_key(der.clone_key())?;
113+
114+
for scheme in provider
115+
.signature_verification_algorithms
116+
.mapping
117+
.iter()
118+
.filter_map(|(scheme, _)| {
119+
(!matches!(
120+
scheme,
121+
SignatureScheme::RSA_PKCS1_SHA1
122+
| SignatureScheme::ECDSA_SHA1_Legacy
123+
| SignatureScheme::Unknown(_),
124+
))
125+
.then_some(*scheme)
126+
})
127+
{
128+
if key.choose_scheme(&[scheme]).is_some() {
129+
return Ok(key);
130+
}
131+
}
132+
133+
Err(rustls::Error::General(
134+
"failed to parse private key as RSA, ECDSA, or EcDSA".into(),
135+
))
136+
})
137+
}
138+
}
139+
140+
#[cfg(all(feature = "ssl-rustls", not(feature = "ssl-rustls-no-provider")))]
141+
pub fn set_aws_lc_provider() -> Result<(), rustls::Error> {
142+
use rustls::crypto::aws_lc_rs;
143+
let provider = aws_lc_rs::default_provider();
144+
let ext = CryptoExt {
145+
all_cipher_suites: Some(aws_lc_rs::ALL_CIPHER_SUITES),
146+
all_kx_groups: Some(aws_lc_rs::ALL_KX_GROUPS),
147+
any_supported_key: Some(aws_lc_rs::sign::any_supported_type),
148+
ticketer: aws_lc_rs::Ticketer::new,
149+
};
150+
set_crypto_provider(provider, ext)
151+
}
152+
153+
/// Set a process [`CryptoProvider`].
154+
///
155+
/// A provider must be set before any cryptographic operations. All crypto ops panic if a provider
156+
/// is unset. See [`set_aws_lc_provider`] for the default provider.
157+
pub fn set_crypto_provider(
158+
provider: CryptoProvider,
159+
extension: CryptoExt,
160+
) -> Result<(), rustls::Error> {
161+
provider
162+
.install_default()
163+
.map_err(|_| rustls::Error::General("A default CryptoProvider is already set".into()))?;
164+
CRYPTO_EXT
165+
.set(extension)
166+
.map_err(|_| rustls::Error::General("A CryptoExt is already set".into()))
68167
}
69168

70169
// OpenSSL Constants:
@@ -662,13 +761,13 @@ pub(super) struct ClientConfigOptions {
662761
/// This helper function consolidates the duplicated CryptoProvider creation logic
663762
/// for both server and client configurations.
664763
fn create_custom_crypto_provider(
665-
cipher_suites: Option<Vec<rustls::SupportedCipherSuite>>,
666-
kx_groups: Option<Vec<&'static dyn rustls::crypto::SupportedKxGroup>>,
667-
) -> Arc<rustls::crypto::CryptoProvider> {
668-
let default_provider = rustls::crypto::CryptoProvider::get_default()
669-
.expect("A CryptoProvider should have been set earlier");
764+
cipher_suites: Option<Vec<SupportedCipherSuite>>,
765+
kx_groups: Option<Vec<&'static dyn SupportedKxGroup>>,
766+
) -> Arc<CryptoProvider> {
767+
let default_provider =
768+
CryptoProvider::get_default().expect("A CryptoProvider must be set before SSL");
670769

671-
Arc::new(rustls::crypto::CryptoProvider {
770+
Arc::new(CryptoProvider {
672771
cipher_suites: cipher_suites.unwrap_or_else(|| default_provider.cipher_suites.clone()),
673772
kx_groups: kx_groups.unwrap_or_else(|| default_provider.kx_groups.clone()),
674773
signature_verification_algorithms: default_provider.signature_verification_algorithms,
@@ -682,11 +781,6 @@ fn create_custom_crypto_provider(
682781
/// This abstracts the complex rustls ServerConfig building logic,
683782
/// matching SSL_CTX initialization for server sockets.
684783
pub(super) fn create_server_config(options: ServerConfigOptions) -> Result<ServerConfig, String> {
685-
use rustls::server::WebPkiClientVerifier;
686-
687-
// Ensure default CryptoProvider is installed
688-
ensure_default_provider();
689-
690784
// Create custom crypto provider using helper function
691785
let custom_provider = create_custom_crypto_provider(
692786
options.protocol_settings.cipher_suites.clone(),
@@ -864,9 +958,6 @@ fn apply_alpn_with_fallback(config_alpn: &mut Vec<Vec<u8>>, alpn_protocols: &[Ve
864958
/// This abstracts the complex rustls ClientConfig building logic,
865959
/// matching SSL_CTX initialization for client sockets.
866960
pub(super) fn create_client_config(options: ClientConfigOptions) -> Result<ClientConfig, String> {
867-
// Ensure default CryptoProvider is installed
868-
ensure_default_provider();
869-
870961
// Create custom crypto provider using helper function
871962
let custom_provider = create_custom_crypto_provider(
872963
options.protocol_settings.cipher_suites.clone(),
@@ -2365,9 +2456,10 @@ pub(super) fn curve_name_to_kx_group(
23652456
curve: &str,
23662457
) -> Result<Vec<&'static dyn SupportedKxGroup>, String> {
23672458
// Get the default crypto provider's key exchange groups
2368-
let provider = rustls::crypto::CryptoProvider::get_default()
2369-
.expect("A CryptoProvider should have been set earlier");
2370-
let all_groups = &provider.kx_groups;
2459+
let crypto_ext = CRYPTO_EXT
2460+
.get()
2461+
.expect("A CryptoProvider must be set before SSL");
2462+
let all_groups = crypto_ext.all_kx_or_default();
23712463

23722464
match curve {
23732465
// P-256 (also known as secp256r1 or prime256v1)

0 commit comments

Comments
 (0)