Remove self-signed TLS option (system trust only); add session changelog

Parity with the Android client: drop the "Allow self-signed certificate"
escape hatch. The app now performs system TLS trust only — a self-signed
appliance must have its CA installed on the device (MDM/profile). A code-only
public-key pinning policy remains (no UI).

- ServerTrustEvaluator: remove ServerTrustPolicy.allowSelfSignedForHost and its
  challenge handler; keep .system (default) and .pinPublicKeySHA256.
- SettingsView: remove the self-signed toggle/state/prefill; makeConfig uses
  the default .system policy.
- MCMSConnection / APIConfiguration: update usage-sketch + doc comments.
- README / GUI-NOTES / MCMS_API.md: document system-trust-only; self-signed
  boxes need their CA installed on the device.
- Add CHANGELOG.md for this session, flagged for the Android port.

Note: configs previously persisted with the self-signed policy fail to decode
and reset to defaults (one-time re-entry of the server URL).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jon Vanvik 2026-05-31 17:15:38 +02:00
parent 7d8f1c7b2d
commit 6bb65d2aa3
8 changed files with 142 additions and 70 deletions

View file

@ -4,22 +4,18 @@ import CryptoKit
/// How to evaluate the server's TLS certificate.
///
/// MCMS appliances usually present a self-signed or internal-CA certificate.
/// Pick the *narrowest* policy that works for your deployment. Never ship
/// `.allowSelfSignedForHost` against a host you don't control.
/// The appliance must present a CA-trusted certificate a real cert, or an
/// internal CA installed on the device via MDM/configuration profile. There is
/// deliberately NO "accept self-signed" escape hatch: the app never bypasses
/// trust evaluation (parity with the Android client).
enum ServerTrustPolicy: Equatable, Codable {
/// Default OS trust evaluation (use this if the box has a real cert or a
/// CA your devices already trust via MDM/profile strongly preferred).
/// Default OS trust evaluation. The default policy and the only one the UI
/// produces.
case system
/// Accept the server's certificate ONLY for this exact host, after a normal
/// trust evaluation that ignores the "untrusted root" failure. Scoped escape
/// hatch for self-signed boxes on a controlled mgmt network/VPN.
case allowSelfSignedForHost(String)
/// Certificate/public-key pinning: trust only if the leaf's SHA-256
/// public-key hash (base64) matches one of these. The safest option for a
/// fixed appliance capture the hash once and pin it.
/// SubjectPublicKeyInfo hash (base64) matches one of these. Code-only (no UI);
/// the strictest option for a fixed appliance capture the hash once and pin it.
case pinPublicKeySHA256([String], host: String)
}
@ -49,19 +45,6 @@ final class ServerTrustEvaluator: NSObject, URLSessionDelegate {
case .system:
completionHandler(.performDefaultHandling, nil)
case .allowSelfSignedForHost(let allowedHost):
// Scoped escape hatch for self-signed appliances on a controlled
// network. Because a self-signed root fails normal evaluation, this
// accepts ANY certificate the allowed host presents including
// expired or name-mismatched ones. The only gate is the hostname, so
// use this only on hosts you control; prefer `.pinPublicKeySHA256`
// once you can capture the appliance's key hash.
guard host == allowedHost else {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
completionHandler(.useCredential, URLCredential(trust: trust))
case .pinPublicKeySHA256(let pins, let pinnedHost):
guard host == pinnedHost,
let leaf = Self.leafCertificate(from: trust),