diff --git a/package-lock.json b/package-lock.json index 83cb8d26..78ec5b50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13826,7 +13826,7 @@ }, "packages/plugin-runtime-types": { "name": "@yaakapp/api", - "version": "0.5.3", + "version": "0.6.0", "dependencies": { "@types/node": "^22.5.4" }, diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 05148f6a..d6511ad0 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -8052,8 +8052,6 @@ dependencies = [ "rand 0.9.0", "reqwest", "reqwest_cookie_store", - "rustls", - "rustls-platform-verifier", "serde", "serde_json", "tauri", @@ -8144,8 +8142,6 @@ dependencies = [ "prost", "prost-reflect", "prost-types", - "rustls", - "rustls-platform-verifier", "serde", "serde_json", "tauri", @@ -8155,6 +8151,7 @@ dependencies = [ "tonic", "tonic-reflection", "uuid", + "yaak-http", ] [[package]] @@ -8162,6 +8159,8 @@ name = "yaak-http" version = "0.1.0" dependencies = [ "regex", + "rustls", + "rustls-platform-verifier", "urlencoding", "yaak-models", ] @@ -8292,8 +8291,6 @@ dependencies = [ "futures-util", "log", "md5", - "rustls", - "rustls-platform-verifier", "serde", "serde_json", "tauri", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 03a5596e..b6afbf0b 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -50,8 +50,6 @@ mime_guess = "2.0.5" rand = "0.9.0" reqwest = { workspace = true, features = ["multipart", "cookies", "gzip", "brotli", "deflate", "json", "rustls-tls-manual-roots-no-provider"] } reqwest_cookie_store = "0.8.0" -rustls = { version = "0.23.25", default-features = false, features = ["custom-provider", "ring"] } -rustls-platform-verifier = "0.5.1" serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["raw_value"] } tauri = { workspace = true, features = ["devtools", "protocol-asset"] } @@ -93,6 +91,8 @@ tauri-plugin-shell = "2.2.1" tokio = "1.44.2" thiserror = "2.0.12" ts-rs = "10.1.0" +rustls = { version = "0.23.25", default-features = false } +rustls-platform-verifier = "0.5.1" yaak-common = { path = "yaak-common" } yaak-http = { path = "yaak-http" } yaak-models = { path = "yaak-models" } diff --git a/src-tauri/src/http_request.rs b/src-tauri/src/http_request.rs index b650bc92..49a571e9 100644 --- a/src-tauri/src/http_request.rs +++ b/src-tauri/src/http_request.rs @@ -9,9 +9,6 @@ use mime_guess::Mime; use reqwest::redirect::Policy; use reqwest::{Method, Response}; use reqwest::{Proxy, Url, multipart}; -use rustls::ClientConfig; -use rustls::crypto::ring; -use rustls_platform_verifier::BuilderVerifierExt; use serde_json::Value; use std::collections::BTreeMap; use std::path::PathBuf; @@ -112,22 +109,8 @@ pub async fn send_http_request( .referer(false) .tls_info(true); - if workspace.setting_validate_certificates { - // Use platform-native verifier to validate certificates - let arc_crypto_provider = Arc::new(ring::default_provider()); - let config = ClientConfig::builder_with_provider(arc_crypto_provider) - .with_safe_default_protocol_versions() - .unwrap() - .with_platform_verifier() - .with_no_client_auth(); - client_builder = client_builder.use_preconfigured_tls(config) - } else { - // Use rustls to skip validation because rustls_platform_verifier does not have this ability - client_builder = client_builder - .use_rustls_tls() - .danger_accept_invalid_hostnames(true) - .danger_accept_invalid_certs(true); - } + let tls_config = yaak_http::tls::get_config(workspace.setting_validate_certificates); + client_builder = client_builder.use_preconfigured_tls(tls_config); match settings.proxy { Some(ProxySetting::Disabled) => client_builder = client_builder.no_proxy(), diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 6e316b26..ab96b005 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -155,6 +155,7 @@ async fn cmd_grpc_reflect( let base_environment = app_handle.db().get_base_environment(&unrendered_request.workspace_id)?; + let workspace = app_handle.db().get_workspace(&unrendered_request.workspace_id)?; let req = render_grpc_request( &resolved_request, @@ -179,6 +180,7 @@ async fn cmd_grpc_reflect( &uri, &proto_files.iter().map(|p| PathBuf::from_str(p).unwrap()).collect(), &metadata, + workspace.setting_validate_certificates, ) .await .map_err(|e| GenericError(e.to_string()))?) @@ -201,6 +203,7 @@ async fn cmd_grpc_go( let resolved_request = resolve_grpc_request(&window, &unrendered_request)?; let base_environment = app_handle.db().get_base_environment(&unrendered_request.workspace_id)?; + let workspace = app_handle.db().get_workspace(&unrendered_request.workspace_id)?; let request = render_grpc_request( &resolved_request, @@ -263,6 +266,7 @@ async fn cmd_grpc_go( uri.as_str(), &proto_files.iter().map(|p| PathBuf::from_str(p).unwrap()).collect(), &metadata, + workspace.setting_validate_certificates, ) .await; @@ -296,7 +300,7 @@ async fn cmd_grpc_go( let cancelled_rx = cancelled_rx.clone(); let app_handle = app_handle.clone(); let window = window.clone(); - let workspace = base_environment.clone(); + let base_environment = base_environment.clone(); let environment = environment.clone(); let base_msg = base_msg.clone(); let method_desc = method_desc.clone(); @@ -326,7 +330,7 @@ async fn cmd_grpc_go( tauri::async_runtime::block_on(async { render_template( msg.as_str(), - &workspace, + &base_environment, environment.as_ref(), &PluginTemplateCallback::new( &app_handle, diff --git a/src-tauri/yaak-grpc/Cargo.toml b/src-tauri/yaak-grpc/Cargo.toml index b8c2bb12..d05d73ab 100644 --- a/src-tauri/yaak-grpc/Cargo.toml +++ b/src-tauri/yaak-grpc/Cargo.toml @@ -11,8 +11,6 @@ dunce = "1.0.4" hyper = "1.5.2" hyper-rustls = { version = "0.27.5", default-features = false, features = ["http2"] } hyper-util = { version = "0.1.10", default-features = false, features = ["client-legacy"] } -rustls = { version = "0.23.21", default-features = false, features = ["custom-provider", "ring"] } -rustls-platform-verifier = "0.5.0" log = "0.4.20" md5 = "0.7.0" prost = "0.13.4" @@ -27,3 +25,4 @@ tokio-stream = "0.1.14" tonic = { version = "0.12.3", default-features = false, features = ["transport"] } tonic-reflection = "0.12.3" uuid = { version = "1.7.0", features = ["v4"] } +yaak-http = { workspace = true } \ No newline at end of file diff --git a/src-tauri/yaak-grpc/src/client.rs b/src-tauri/yaak-grpc/src/client.rs index 5c185368..fbe978a9 100644 --- a/src-tauri/yaak-grpc/src/client.rs +++ b/src-tauri/yaak-grpc/src/client.rs @@ -26,13 +26,13 @@ pub struct AutoReflectionClient, BoxBod } impl AutoReflectionClient { - pub fn new(uri: &Uri) -> Self { + pub fn new(uri: &Uri, validate_certificates: bool) -> Self { let client_v1 = v1::server_reflection_client::ServerReflectionClient::with_origin( - get_transport(), + get_transport(validate_certificates), uri.clone(), ); let client_v1alpha = v1alpha::server_reflection_client::ServerReflectionClient::with_origin( - get_transport(), + get_transport(validate_certificates), uri.clone(), ); AutoReflectionClient { diff --git a/src-tauri/yaak-grpc/src/manager.rs b/src-tauri/yaak-grpc/src/manager.rs index c4e19efc..e915966b 100644 --- a/src-tauri/yaak-grpc/src/manager.rs +++ b/src-tauri/yaak-grpc/src/manager.rs @@ -181,10 +181,11 @@ impl GrpcHandle { uri: &str, proto_files: &Vec, metadata: &BTreeMap, + validate_certificates: bool, ) -> Result<(), String> { let pool = if proto_files.is_empty() { let full_uri = uri_from_str(uri)?; - fill_pool_from_reflection(&full_uri, metadata).await + fill_pool_from_reflection(&full_uri, metadata, validate_certificates).await } else { fill_pool_from_files(&self.app_handle, proto_files).await }?; @@ -199,9 +200,10 @@ impl GrpcHandle { uri: &str, proto_files: &Vec, metadata: &BTreeMap, + validate_certificates: bool, ) -> Result, String> { // Ensure reflection is up-to-date - self.reflect(id, uri, proto_files, metadata).await?; + self.reflect(id, uri, proto_files, metadata, validate_certificates).await?; let pool = self.get_pool(id, uri, proto_files).ok_or("Failed to get pool".to_string())?; Ok(self.services_from_pool(&pool)) @@ -238,12 +240,13 @@ impl GrpcHandle { uri: &str, proto_files: &Vec, metadata: &BTreeMap, + validate_certificates: bool, ) -> Result { - self.reflect(id, uri, proto_files, metadata).await?; + self.reflect(id, uri, proto_files, metadata, validate_certificates).await?; let pool = self.get_pool(id, uri, proto_files).ok_or("Failed to get pool")?; let uri = uri_from_str(uri)?; - let conn = get_transport(); + let conn = get_transport(validate_certificates); let connection = GrpcConnection { pool: pool.clone(), conn, diff --git a/src-tauri/yaak-grpc/src/reflection.rs b/src-tauri/yaak-grpc/src/reflection.rs index 1449a6d8..57c827b6 100644 --- a/src-tauri/yaak-grpc/src/reflection.rs +++ b/src-tauri/yaak-grpc/src/reflection.rs @@ -93,9 +93,10 @@ pub async fn fill_pool_from_files( pub async fn fill_pool_from_reflection( uri: &Uri, metadata: &BTreeMap, + validate_certificates: bool, ) -> Result { let mut pool = DescriptorPool::new(); - let mut client = AutoReflectionClient::new(uri); + let mut client = AutoReflectionClient::new(uri, validate_certificates); for service in list_services(&mut client, metadata).await? { if service == "grpc.reflection.v1alpha.ServerReflection" { diff --git a/src-tauri/yaak-grpc/src/transport.rs b/src-tauri/yaak-grpc/src/transport.rs index de30e06d..2541003b 100644 --- a/src-tauri/yaak-grpc/src/transport.rs +++ b/src-tauri/yaak-grpc/src/transport.rs @@ -2,25 +2,16 @@ use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder}; use hyper_util::client::legacy::connect::HttpConnector; use hyper_util::client::legacy::Client; use hyper_util::rt::TokioExecutor; -use rustls::crypto::ring; -use rustls::ClientConfig; -use rustls_platform_verifier::BuilderVerifierExt; -use std::sync::Arc; use tonic::body::BoxBody; -pub(crate) fn get_transport() -> Client, BoxBody> { - let arc_crypto_provider = Arc::new(ring::default_provider()); - let config = ClientConfig::builder_with_provider(arc_crypto_provider) - .with_safe_default_protocol_versions() - .unwrap() - .with_platform_verifier() - .with_no_client_auth(); +pub(crate) fn get_transport(validate_certificates: bool) -> Client, BoxBody> { + let tls_config = yaak_http::tls::get_config(validate_certificates); let mut http = HttpConnector::new(); http.enforce_http(false); let connector = - HttpsConnectorBuilder::new().with_tls_config(config).https_or_http().enable_http2().build(); + HttpsConnectorBuilder::new().with_tls_config(tls_config).https_or_http().enable_http2().build(); let client = Client::builder(TokioExecutor::new()) .pool_max_idle_per_host(0) diff --git a/src-tauri/yaak-http/Cargo.toml b/src-tauri/yaak-http/Cargo.toml index 40a6a2bc..78455a51 100644 --- a/src-tauri/yaak-http/Cargo.toml +++ b/src-tauri/yaak-http/Cargo.toml @@ -7,4 +7,6 @@ publish = false [dependencies] yaak-models = { workspace = true } regex = "1.11.0" +rustls = { workspace = true, default-features = false, features = ["ring"] } +rustls-platform-verifier = { workspace = true } urlencoding = "2.1.3" diff --git a/src-tauri/yaak-http/src/lib.rs b/src-tauri/yaak-http/src/lib.rs index ccb74637..156046d9 100644 --- a/src-tauri/yaak-http/src/lib.rs +++ b/src-tauri/yaak-http/src/lib.rs @@ -1,3 +1,5 @@ +pub mod tls; + use yaak_models::models::HttpUrlParameter; pub fn apply_path_placeholders( diff --git a/src-tauri/yaak-http/src/tls.rs b/src-tauri/yaak-http/src/tls.rs new file mode 100644 index 00000000..b5fdd300 --- /dev/null +++ b/src-tauri/yaak-http/src/tls.rs @@ -0,0 +1,77 @@ +use std::sync::Arc; +use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; +use rustls::{ClientConfig, DigitallySignedStruct, SignatureScheme}; +use rustls::crypto::ring; +use rustls::pki_types::{CertificateDer, ServerName, UnixTime}; +use rustls_platform_verifier::BuilderVerifierExt; + +pub fn get_config(validate_certificates: bool) -> ClientConfig { + let arc_crypto_provider = Arc::new(ring::default_provider()); + let config_builder = ClientConfig::builder_with_provider(arc_crypto_provider) + .with_safe_default_protocol_versions() + .unwrap(); + if validate_certificates { + // Use platform-native verifier to validate certificates + config_builder + .with_platform_verifier() + .with_no_client_auth() + } else { + config_builder + .dangerous() + .with_custom_certificate_verifier(Arc::new(NoVerifier)) + .with_no_client_auth() + } +} + +// Copied from reqwest: https://github.com/seanmonstar/reqwest/blob/595c80b1fbcdab73ac2ae93e4edc3406f453df25/src/tls.rs#L608 +#[derive(Debug)] +struct NoVerifier; + +impl ServerCertVerifier for NoVerifier { + fn verify_server_cert( + &self, + _end_entity: &CertificateDer, + _intermediates: &[CertificateDer], + _server_name: &ServerName, + _ocsp_response: &[u8], + _now: UnixTime, + ) -> Result { + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &CertificateDer, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + vec![ + SignatureScheme::RSA_PKCS1_SHA1, + SignatureScheme::ECDSA_SHA1_Legacy, + SignatureScheme::RSA_PKCS1_SHA256, + SignatureScheme::ECDSA_NISTP256_SHA256, + SignatureScheme::RSA_PKCS1_SHA384, + SignatureScheme::ECDSA_NISTP384_SHA384, + SignatureScheme::RSA_PKCS1_SHA512, + SignatureScheme::ECDSA_NISTP521_SHA512, + SignatureScheme::RSA_PSS_SHA256, + SignatureScheme::RSA_PSS_SHA384, + SignatureScheme::RSA_PSS_SHA512, + SignatureScheme::ED25519, + SignatureScheme::ED448, + ] + } +} diff --git a/src-tauri/yaak-ws/Cargo.toml b/src-tauri/yaak-ws/Cargo.toml index 8b786321..0501c742 100644 --- a/src-tauri/yaak-ws/Cargo.toml +++ b/src-tauri/yaak-ws/Cargo.toml @@ -9,8 +9,6 @@ publish = false futures-util = "0.3.31" log = "0.4.20" md5 = "0.7.0" -rustls = { version = "0.23.25", default-features = false, features = ["custom-provider", "ring"] } -rustls-platform-verifier = "0.5.1" serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } tauri = { workspace = true } diff --git a/src-tauri/yaak-ws/src/commands.rs b/src-tauri/yaak-ws/src/commands.rs index a1fe1c35..7230ac8b 100644 --- a/src-tauri/yaak-ws/src/commands.rs +++ b/src-tauri/yaak-ws/src/commands.rs @@ -196,6 +196,7 @@ pub(crate) async fn connect( }; let base_environment = app_handle.db().get_base_environment(&unrendered_request.workspace_id)?; + let workspace = app_handle.db().get_workspace(&unrendered_request.workspace_id)?; let resolved_request = resolve_websocket_request(&window, &unrendered_request)?; let request = render_websocket_request( &resolved_request, @@ -298,7 +299,13 @@ pub(crate) async fn connect( } } - let response = match ws_manager.connect(&connection.id, url.as_str(), headers, receive_tx).await + let response = match ws_manager.connect( + &connection.id, + url.as_str(), + headers, + receive_tx, + workspace.setting_validate_certificates, + ).await { Ok(r) => r, Err(e) => { diff --git a/src-tauri/yaak-ws/src/connect.rs b/src-tauri/yaak-ws/src/connect.rs index f2853ff9..2aa281c1 100644 --- a/src-tauri/yaak-ws/src/connect.rs +++ b/src-tauri/yaak-ws/src/connect.rs @@ -1,7 +1,4 @@ use log::info; -use rustls::crypto::ring; -use rustls::ClientConfig; -use rustls_platform_verifier::BuilderVerifierExt; use std::sync::Arc; use tauri::http::HeaderMap; use tokio::net::TcpStream; @@ -16,14 +13,10 @@ use tokio_tungstenite::{ pub(crate) async fn ws_connect( url: &str, headers: HeaderMap, + validate_certificates: bool, ) -> crate::error::Result<(WebSocketStream>, Response)> { info!("Connecting to WS {url}"); - let arc_crypto_provider = Arc::new(ring::default_provider()); - let config = ClientConfig::builder_with_provider(arc_crypto_provider) - .with_safe_default_protocol_versions() - .unwrap() - .with_platform_verifier() - .with_no_client_auth(); + let tls_config = yaak_http::tls::get_config(validate_certificates); let mut req = url.into_client_request()?; let req_headers = req.headers_mut(); @@ -37,7 +30,7 @@ pub(crate) async fn ws_connect( req, Some(WebSocketConfig::default()), false, - Some(Connector::Rustls(Arc::new(config))), + Some(Connector::Rustls(Arc::new(tls_config))), ) .await?; Ok((stream, response)) diff --git a/src-tauri/yaak-ws/src/manager.rs b/src-tauri/yaak-ws/src/manager.rs index 1bc1fe75..530109c8 100644 --- a/src-tauri/yaak-ws/src/manager.rs +++ b/src-tauri/yaak-ws/src/manager.rs @@ -31,12 +31,13 @@ impl WebsocketManager { url: &str, headers: HeaderMap, receive_tx: mpsc::Sender, + validate_certificates: bool, ) -> Result { let connections = self.connections.clone(); let connection_id = id.to_string(); let tx = receive_tx.clone(); - let (stream, response) = ws_connect(url, headers).await?; + let (stream, response) = ws_connect(url, headers, validate_certificates).await?; let (write, mut read) = stream.split(); connections.lock().await.insert(id.to_string(), write);