feat(ios): device client-cert mTLS — ClientTLS package, transport wiring, install UX (C-iOS)

- ios/Packages/ClientTLS: SecIdentity wrapper, PKCS12 importer (typed errors), keychain store
  (AfterFirstUnlockThisDeviceOnly), pure MutualTLSChallengeResponder truth table, cross-platform
  X.509 DER summary. 14/14 tests.
- Both transports (SessionCore URLSessionTermTransport, App URLSessionHTTPTransport) + SessionThumbnail
  take a lazy @Sendable ()->ClientIdentity? provider: WS resolves per-connect, HTTP per client-cert
  challenge, so a freshly-imported cert applies without an app relaunch. AppEnvironment injects
  { store.loadedIdentityOrNil() }.
- ClientCertScreen (.fileImporter([.pkcs12]) + passphrase -> import -> keychain), reachable via a
  设备证书 entry in SessionListScreen.hostMenu. PairingViewModel gates tunnel-host probes on cert
  presence and re-maps mTLS-reject to a clientCertRejected message.
Verified: ClientTLS 14/14, SessionCore 93/93, xcodegen + xcodebuild BUILD SUCCEEDED.
This commit is contained in:
Yaojia Wang
2026-07-07 09:42:12 +02:00
parent 5337281e85
commit e38e6d1689
25 changed files with 1510 additions and 30 deletions

View File

@@ -51,6 +51,9 @@ struct AdaptiveRootView: View {
) {
projectsSheet
}
.sheet(isPresented: $coordinator.isDeviceCertPresented) {
deviceCertSheet
}
}
// MARK: - Layout branch (the SOLE size-class consumer)
@@ -107,4 +110,20 @@ struct AdaptiveRootView: View {
}
}
}
// MARK: - Device certificate sheet (C-iOS-3, HIGH reachability fix)
/// NavigationStack`ClientCertScreen` `.navigationTitle`+
/// .p12 `ClientCertScreen`
/// `ClientCertViewModel`keychain store
@ViewBuilder private var deviceCertSheet: some View {
NavigationStack {
ClientCertScreen()
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button(RootCopy.done) { coordinator.isDeviceCertPresented = false }
}
}
}
}
}