Fix "Validate TLS Certificates" option for WS and GRPC (#218)

This commit is contained in:
Andy Bao
2025-05-29 10:02:27 -04:00
committed by GitHub
parent 085b640b3c
commit bd1986f31f
17 changed files with 124 additions and 66 deletions

2
package-lock.json generated
View File

@@ -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
View File

@@ -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",

View File

@@ -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" }

View File

@@ -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(),

View File

@@ -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,

View File

@@ -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 }

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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" {

View File

@@ -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)

View File

@@ -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"

View File

@@ -1,3 +1,5 @@
pub mod tls;
use yaak_models::models::HttpUrlParameter;
pub fn apply_path_placeholders(

View 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,
]
}
}

View File

@@ -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 }

View File

@@ -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) => {

View File

@@ -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))

View File

@@ -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);