mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-01-11 20:00:29 +01:00
Fix "Validate TLS Certificates" option for WS and GRPC (#218)
This commit is contained in:
2
package-lock.json
generated
2
package-lock.json
generated
@@ -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"
|
||||
},
|
||||
|
||||
9
src-tauri/Cargo.lock
generated
9
src-tauri/Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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<R: Runtime>(
|
||||
.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(),
|
||||
|
||||
@@ -155,6 +155,7 @@ async fn cmd_grpc_reflect<R: Runtime>(
|
||||
|
||||
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<R: Runtime>(
|
||||
&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<R: Runtime>(
|
||||
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<R: Runtime>(
|
||||
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<R: Runtime>(
|
||||
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<R: Runtime>(
|
||||
tauri::async_runtime::block_on(async {
|
||||
render_template(
|
||||
msg.as_str(),
|
||||
&workspace,
|
||||
&base_environment,
|
||||
environment.as_ref(),
|
||||
&PluginTemplateCallback::new(
|
||||
&app_handle,
|
||||
|
||||
@@ -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 }
|
||||
@@ -26,13 +26,13 @@ pub struct AutoReflectionClient<T = Client<HttpsConnector<HttpConnector>, 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 {
|
||||
|
||||
@@ -181,10 +181,11 @@ impl GrpcHandle {
|
||||
uri: &str,
|
||||
proto_files: &Vec<PathBuf>,
|
||||
metadata: &BTreeMap<String, String>,
|
||||
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<PathBuf>,
|
||||
metadata: &BTreeMap<String, String>,
|
||||
validate_certificates: bool,
|
||||
) -> Result<Vec<ServiceDefinition>, 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<PathBuf>,
|
||||
metadata: &BTreeMap<String, String>,
|
||||
validate_certificates: bool,
|
||||
) -> Result<GrpcConnection, String> {
|
||||
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,
|
||||
|
||||
@@ -93,9 +93,10 @@ pub async fn fill_pool_from_files(
|
||||
pub async fn fill_pool_from_reflection(
|
||||
uri: &Uri,
|
||||
metadata: &BTreeMap<String, String>,
|
||||
validate_certificates: bool,
|
||||
) -> Result<DescriptorPool, String> {
|
||||
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" {
|
||||
|
||||
@@ -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<HttpsConnector<HttpConnector>, 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<HttpsConnector<HttpConnector>, 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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pub mod tls;
|
||||
|
||||
use yaak_models::models::HttpUrlParameter;
|
||||
|
||||
pub fn apply_path_placeholders(
|
||||
|
||||
77
src-tauri/yaak-http/src/tls.rs
Normal file
77
src-tauri/yaak-http/src/tls.rs
Normal file
@@ -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<ServerCertVerified, rustls::Error> {
|
||||
Ok(ServerCertVerified::assertion())
|
||||
}
|
||||
|
||||
fn verify_tls12_signature(
|
||||
&self,
|
||||
_message: &[u8],
|
||||
_cert: &CertificateDer,
|
||||
_dss: &DigitallySignedStruct,
|
||||
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
||||
Ok(HandshakeSignatureValid::assertion())
|
||||
}
|
||||
|
||||
fn verify_tls13_signature(
|
||||
&self,
|
||||
_message: &[u8],
|
||||
_cert: &CertificateDer,
|
||||
_dss: &DigitallySignedStruct,
|
||||
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
||||
Ok(HandshakeSignatureValid::assertion())
|
||||
}
|
||||
|
||||
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
|
||||
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,
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
|
||||
@@ -196,6 +196,7 @@ pub(crate) async fn connect<R: Runtime>(
|
||||
};
|
||||
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<R: Runtime>(
|
||||
}
|
||||
}
|
||||
|
||||
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) => {
|
||||
|
||||
@@ -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<HeaderValue>,
|
||||
validate_certificates: bool,
|
||||
) -> crate::error::Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, 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))
|
||||
|
||||
@@ -31,12 +31,13 @@ impl WebsocketManager {
|
||||
url: &str,
|
||||
headers: HeaderMap<HeaderValue>,
|
||||
receive_tx: mpsc::Sender<Message>,
|
||||
validate_certificates: bool,
|
||||
) -> Result<Response> {
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user