Desktop 0.4.26 native WebSocket ignores macOS system-trusted private CAs
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## Summary
Buzz Desktop 0.4.26 cannot connect its native WebSocket to a self-hosted relay whose certificate chains to a private CA trusted by macOS. HTTPS in Safari/curl and the WebView's authenticated HTTP bridge work, but Desktop stays at "Reconnecting to relay…".
The desktop's custom WebSocket uses `tokio-tungstenite` with compiled Mozilla WebPKI roots and does not load native system roots, so a CA installed in the macOS System keychain is not available to this connection.
## Environment
- Buzz Desktop: 0.4.26
- Tag commit: `0096d710ed2e6abab19aaf7cdc14e3ee603d7ec8`
- macOS app: product/process `Buzz`, bundle identifier `xyz.block.buzz.app`
- Relay: self-hosted behind Caddy with an internal CA
- Stored relay URL: a redacted private DNS name using `wss://`
- The Caddy root is installed and trusted in the macOS system trust store
- Relay CORS includes both the HTTPS origin and `tauri://localhost`
No identities, tokens, keys, or raw client storage are included here.
## Reproduction
1. Serve Buzz through Caddy using a certificate issued by Caddy's internal CA.
2. Install that root in the macOS System keychain and verify Safari/curl can load the HTTPS endpoint without a certificate error.
3. Configure Desktop 0.4.26 for the corresponding `wss://` relay.
4. Observe onboarding HTTP requests succeed, followed by the persistent reconnecting state.
To isolate the native transport, I also ran a minimal client against the live endpoint using the same versions/features and initialization as Desktop:
- `tokio-tungstenite = 0.29`, feature `rustls-tls-webpki-roots`
- `rustls = 0.23`, features `aws_lc_rs,std`
- `rustls::crypto::aws_lc_rs::default_provider().install_default()`
- `tokio_tungstenite::connect_async(...)`
The raw result was:
```text
IO error: invalid peer certificate: UnknownIssuer
Io(Custom { kind: InvalidData, error: InvalidCertificate(UnknownIssuer) })
```
In the synchronized six-second reproduction window, Caddy and Relay each emitted zero log lines. The failure is therefore before the HTTP WebSocket upgrade, NIP-42 AUTH, membership checks, subscription replay, or the stall watchdog.
## Source evidence
- `desktop/src-tauri/src/native_websocket.rs` registers the custom `plugin:websocket|connect` command and calls `tokio_tungstenite::connect_async`:
https://github.com/block/buzz/blob/0096d710ed2e6abab19aaf7cdc14e3ee603d7ec8/desktop/src-tauri/src/native_websocket.rs
- The workspace selects `rustls-tls-webpki-roots`:
https://github.com/block/buzz/blob/0096d710ed2e6abab19aaf7cdc14e3ee603d7ec8/Cargo.toml#L113
- The desktop crate independently selects the same feature:
https://github.com/block/buzz/blob/0096d710ed2e6abab19aaf7cdc14e3ee603d7ec8/desktop/src-tauri/Cargo.toml#L73
- The native WebSocket path does not load `rustls-native-certs` or otherwise populate Rustls with native roots.
- `resetConnection` changes the label to `reconnecting` even when `hasConnectedOnce` is false:
https://github.com/block/buzz/blob/0096d710ed2e6abab19aaf7cdc14e3ee603d7ec8/desktop/src/shared/api/relayClientSession.ts#L1018-L1050
- `normalizeRelayError` replaces a non-`Error` Tauri rejection string with the generic fallback, hiding the useful certificate detail:
https://github.com/block/buzz/blob/0096d710ed2e6abab19aaf7cdc14e3ee603d7ec8/desktop/src/shared/api/relayClientSession.ts#L675-L677
## Expected behavior
The packaged macOS app's native WSS client should honor certificates rooted in the macOS system trust store, while continuing to trust the normal public WebPKI set.
## Suggested fix/test
Load native certificates (for example with `rustls-native-certs`) into the Rustls root store used by the custom WebSocket connector, merging them with the existing WebPKI roots as appropriate. Please add a regression test in which a WSS server uses a private test CA trusted through the platform/native-root loading path; require the TLS and WebSocket handshake to succeed.
It would also help to preserve the raw Tauri rejection text in `normalizeRelayError`, so transport failures remain diagnosable.
Contributor guide
Assessment
This issue has not been assessed yet.