Skip to content

Commit fdd0f81

Browse files
committed
v0.8.5: Check-CA actually checks Windows now (follow-up to #13)
User on issue #13 reported that even after installing the CA (and seeing it in the Windows cert manager UI), our 'Check CA' button still said 'NOT trusted'. Root cause: is_ca_trusted() on Windows was just returning false unconditionally — Check-CA has never worked on Windows. Fix: is_trusted_windows() now shells out to certutil: certutil -user -store Root 'MasterHttpRelayVPN' certutil -store Root 'MasterHttpRelayVPN' Checks both the user store (where our install_windows puts it by default) and the machine store (fallback path when user-store install is blocked). Requires certutil to print the cert name in stdout AND exit 0 — belt-and-suspenders against locales where certutil exits 0 even on an empty match. Also made the Check-CA UI message point users at the CA file path for cross-device install — the same user reported their Android V2rayNG client getting cert errors on our MITM-signed TLS leaves, which is the expected 'the phone doesn't trust our CA' scenario. The message now calls out the ca.crt path explicitly, and notes the Android 7+ user-CA restriction (Firefox Android works, Chrome and most apps don't trust user-installed CAs regardless). Not addressed (by design): - Replacing our CA keypair with Python-generated PEM fails to parse via rcgen. User tried this as a workaround before reporting. rcgen expects PKCS#8 PEM; Python's cryptography commonly emits PKCS#1 ('BEGIN RSA PRIVATE KEY'). Even if parsing worked, mixing an external CA with our leaf-issuing code would break the key match. Users should stick with our generated CA — that's the supported flow. The Python cross-contamination experiment is expected to fail; we don't document it as supported.
1 parent 2145825 commit fdd0f81

4 files changed

Lines changed: 52 additions & 6 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mhrv-rs"
3-
version = "0.8.4"
3+
version = "0.8.5"
44
edition = "2021"
55
description = "Rust port of MasterHttpRelayVPN -- DPI bypass via Google Apps Script relay with domain fronting"
66
license = "MIT"

src/bin/ui.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -772,9 +772,28 @@ impl eframe::App for App {
772772
ui.small(last_test_msg);
773773
}
774774
match ca_trusted {
775-
Some(true) => { ui.small("CA appears trusted."); },
776-
Some(false) => { ui.small("CA is NOT trusted in the system store. Click 'Install CA' (may require admin)."); },
777-
None => {},
775+
Some(true) => {
776+
ui.small("CA appears trusted on this machine.");
777+
}
778+
Some(false) => {
779+
ui.small(
780+
"CA is NOT trusted in the system store. Click 'Install CA' \
781+
(may require admin). If you already installed it and this \
782+
still says NO, you may be on an older build — v0.8.5+ \
783+
checks the Windows store correctly.",
784+
);
785+
}
786+
None => {}
787+
}
788+
if ca_trusted.is_some() {
789+
let ca_path = data_dir::data_dir().join("ca").join("ca.crt");
790+
ui.small(format!(
791+
"For other devices (Android, other PCs) connecting through this proxy: \
792+
copy {} and install as a trusted root on that device. On Android 7+ \
793+
most apps ignore user-installed CAs — Firefox Android works; Chrome \
794+
and many others don't.",
795+
ca_path.display()
796+
));
778797
}
779798

780799
ui.separator();

src/cert_installer.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ pub fn is_ca_trusted(path: &Path) -> bool {
5555
match std::env::consts::OS {
5656
"macos" => is_trusted_macos(),
5757
"linux" => is_trusted_linux(),
58-
"windows" => false,
58+
"windows" => is_trusted_windows(),
5959
_ => false,
6060
}
6161
}
@@ -303,6 +303,33 @@ fn is_trusted_linux() -> bool {
303303

304304
// ---------- Windows ----------
305305

306+
/// Check whether our CA is present in the Windows Trusted Root store.
307+
/// Looks in both the user store (no admin required to install) and the
308+
/// machine store. Returns true if `certutil -store ... MasterHttpRelayVPN`
309+
/// finds a match. Issue #13 follow-up: previously this always returned
310+
/// false on Windows, so the Check-CA button was misleading users into
311+
/// reinstalling a cert that was already trusted.
312+
fn is_trusted_windows() -> bool {
313+
// `certutil -user -store Root <name>` prints the matching cert entries
314+
// on success (stdout), and exits with a non-zero code plus a "Not
315+
// found" message if nothing matches. We also check stdout for the
316+
// cert name because certutil in some locales returns 0 even on no-
317+
// match, just with empty output.
318+
for args in [
319+
vec!["-user", "-store", "Root", CERT_NAME],
320+
vec!["-store", "Root", CERT_NAME],
321+
] {
322+
let out = Command::new("certutil").args(&args).output();
323+
if let Ok(o) = out {
324+
let stdout = String::from_utf8_lossy(&o.stdout);
325+
if o.status.success() && stdout.to_ascii_lowercase().contains(&CERT_NAME.to_ascii_lowercase()) {
326+
return true;
327+
}
328+
}
329+
}
330+
false
331+
}
332+
306333
fn install_windows(cert_path: &str) -> bool {
307334
// Per-user Root store (no admin required).
308335
let res = Command::new("certutil")

0 commit comments

Comments
 (0)