Skip to content

fix: trust self-signed server certificates and separate connection errors - #58

Merged
mahype merged 1 commit into
mainfrom
fix/trust-server-certificates
Oct 22, 2026
Merged

mahype merged 1 commit into
mainfrom
fix/trust-server-certificates

Conversation

@mahype

@mahype mahype commented Oct 22, 2026

Copy link
Copy Markdown
Owner

Problem

Adding a Proton Mail Bridge account (127.0.0.1:1143, STARTTLS) failed even with the exact credentials from the Bridge. The Bridge presents a self-signed certificate. rustls refused it during the handshake, and the setup wizard then showed "The server did not accept these details. Check the password…" for every failed check. A correct login looked rejected, and nothing pointed to the certificate.

Changes

TLS (torromail-imap-tls)

  • The TLS handshake now runs before any IMAP or SMTP traffic. A refused certificate surfaces as CoreError::UntrustedCertificate { server, reason, sha256 } instead of an anonymous I/O error.
  • A custom verifier runs normal web PKI verification first. Only when that fails does it compare the leaf certificate's SHA-256 against the account's trusted fingerprints. Handshake signatures are still checked against the presented key.
  • Reason words: unknown_issuer (self-signed, including CaUsedAsEndEntity), expired, not_yet_valid, wrong_name, invalid.

Health / check

  • New outcome untrusted, added to the shared contract contracts/health-derive.json. It turns the dot red at once, the same way a refused login does, but is never reported as a password problem.

Configuration

  • MailAccount.trustedCertificates is stored in state.json, in both the Swift and Rust models.
  • The policy document publishes trusted_certificates in both the IMAP and SMTP blocks. A malformed list fails closed.

App

  • Setup wizard: a new certificate step shows the warning, server, reason and SHA-256 fingerprint, with a "Trust and Add" button. Trusting it re-runs the check.
  • Account detail: the same notice with "Trust This Certificate" appears when a certificate changes later.
  • Separate messages for:
    • rejected login
    • unreachable server (opens the server details)
    • failed encryption, i.e. SSL/TLS vs. STARTTLS (opens the server details)
    • other failures (with the original detail)
  • German localization included.

TUI

  • Recognizes untrusted and shows it as its own message. It has no trust action yet.

Tests

  • cargo test --workspace: 410 passed
  • New crates/torromail-imap-tls/tests/trusted_certificates.rs, run against an in-process TLS server with a self-signed CA:TRUE fixture:
    • the certificate is refused and the error carries its fingerprint
    • a trusted fingerprint connects, written with or without colons
    • a different fingerprint is still refused
    • a dead port is not reported as a certificate problem
  • Policy parsing tests; a shared policy contract case (Rust + Swift); health contract cases
  • TorroMailKitContract passes: certificate parsing and cause classification
  • swift build passes
  • Checked by hand against the real Proton Bridge: untrusted → fingerprint 0D:FD:C9:…:AC:24; trusted → login OK; Gmail unaffected

Not yet tested

  • Clicking through the wizard in a signed app build
  • The connection check only probes IMAP. If a server uses a different certificate for SMTP, that only shows up when sending. The Bridge uses the same certificate for both.

🤖 Generated with Claude Code

…rors

A local Proton Mail Bridge presents a self-signed certificate, which rustls
refused during the handshake. The setup wizard then blamed the password for
every failed check, so a correct login looked rejected.

- Run the TLS handshake eagerly with a verifier that falls back to
  user-trusted SHA-256 fingerprints when web PKI verification fails;
  refusals surface as CoreError::UntrustedCertificate (server, reason,
  fingerprint) instead of an anonymous I/O error.
- New health outcome `untrusted`: red at once, never blamed on the password.
- Trusted fingerprints are stored per account (`trustedCertificates`) and
  published as `trusted_certificates` in both connection blocks.
- The wizard shows a certificate step with a warning and the fingerprint
  ("Trust and Add"); the account detail offers the same decision when a
  certificate changes.
- Separate wizard messages for rejected login, unreachable server, failed
  encryption, and other failures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mahype
mahype merged commit e837472 into main Oct 22, 2026
4 checks passed
@mahype
mahype deleted the fix/trust-server-certificates branch October 22, 2026 12:46
@mahype mahype mentioned this pull request Oct 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant