import SwiftUI import ProxyCore struct CertificateView: View { @Environment(AppState.self) private var appState @State private var showRegenerateConfirmation = false @State private var isInstallingCert = false @State private var certServer: CertificateInstallServer? private var dateFormatter: DateFormatter { let f = DateFormatter() f.dateStyle = .medium return f } var body: some View { List { Section { HStack { Image(systemName: CertificateManager.shared.hasCA ? "checkmark.shield.fill" : "exclamationmark.shield") .font(.largeTitle) .foregroundStyle(CertificateManager.shared.hasCA ? .green : .orange) VStack(alignment: .leading) { Text(CertificateManager.shared.hasCA ? "Certificate Generated" : "Certificate not generated") .font(.headline) Text("The app owns the shared CA used for HTTPS decryption.") .font(.caption) .foregroundStyle(.secondary) } } .padding(.vertical, 8) } Section("Details") { LabeledContent("CA Certificate", value: "Proxy CA (\(UIDevice.current.name))") LabeledContent("Generated", value: formattedDate(CertificateManager.shared.caGeneratedDate)) LabeledContent("Expires", value: formattedDate(CertificateManager.shared.caExpirationDate)) LabeledContent("Fingerprint", value: abbreviatedFingerprint(CertificateManager.shared.caFingerprint)) } Section("Runtime") { LabeledContent("Extension Loaded Same CA", value: appState.hasSharedCertificate ? "Yes" : "No") LabeledContent("HTTPS Inspection Verified", value: appState.isHTTPSInspectionVerified ? "Yes" : "Not Yet") if let domain = appState.runtimeStatus.lastSuccessfulMITMDomain { LabeledContent("Last Verified Domain", value: domain) } if let lastError = appState.lastRuntimeError { VStack(alignment: .leading, spacing: 6) { Text("Latest Error") .font(.caption.weight(.semibold)) Text(lastError) .font(.caption) .foregroundStyle(.secondary) } } } Section { Button { installCertificate() } label: { HStack { Spacer() if isInstallingCert { ProgressView() .padding(.trailing, 8) } Text("Install Certificate to Settings") Spacer() } } .disabled(isInstallingCert || !CertificateManager.shared.hasCA) } footer: { Text("Downloads the CA certificate in Safari. After downloading, install it from Settings > General > VPN & Device Management, then enable trust in Settings > General > About > Certificate Trust Settings.") } Section { Button("Regenerate Certificate", role: .destructive) { showRegenerateConfirmation = true } .frame(maxWidth: .infinity) } } .navigationTitle("Certificate") .confirmationDialog("Regenerate Certificate?", isPresented: $showRegenerateConfirmation) { Button("Regenerate", role: .destructive) { CertificateManager.shared.regenerateCA() appState.isCertificateInstalled = CertificateManager.shared.hasCA } } message: { Text("This will create a new CA certificate. You will need to reinstall and trust it on your device.") } .onAppear { appState.isCertificateInstalled = CertificateManager.shared.hasCA } .onDisappear { certServer?.stop() certServer = nil } } private func formattedDate(_ date: Date?) -> String { guard let date else { return "N/A" } return dateFormatter.string(from: date) } private func abbreviatedFingerprint(_ fingerprint: String?) -> String { guard let fingerprint else { return "N/A" } if fingerprint.count <= 16 { return fingerprint } return "\(fingerprint.prefix(8))...\(fingerprint.suffix(8))" } private func installCertificate() { guard let derBytes = CertificateManager.shared.exportCACertificateDER() else { return } isInstallingCert = true // Start a local HTTP server that serves the certificate let server = CertificateInstallServer(certDER: Data(derBytes)) certServer = server server.start { port in Task { @MainActor in // Open Safari to our local server so the certificate can be downloaded and installed. if let url = URL(string: "http://localhost:\(port)/ProxyCA.cer") { UIApplication.shared.open(url) } isInstallingCert = false } } } } // MARK: - Local HTTP server for certificate installation import Network final class CertificateInstallServer: @unchecked Sendable { private let certDER: Data private var listener: NWListener? private let queue = DispatchQueue(label: "cert-install-server") init(certDER: Data) { self.certDER = certDER } func start(onReady: @escaping @Sendable (UInt16) -> Void) { do { let params = NWParameters.tcp listener = try NWListener(using: params, on: .any) listener?.stateUpdateHandler = { state in if case .ready = state, let port = self.listener?.port?.rawValue { DispatchQueue.main.async { onReady(port) } } } listener?.newConnectionHandler = { [weak self] connection in self?.handleConnection(connection) } listener?.start(queue: queue) } catch { print("[CertInstall] Failed to start server: \(error)") } } func stop() { listener?.cancel() listener = nil } private func handleConnection(_ connection: NWConnection) { connection.start(queue: queue) // Read the HTTP request (we don't really need to parse it) connection.receive(minimumIncompleteLength: 1, maximumLength: 4096) { [weak self] data, _, _, _ in guard let self else { return } // Respond with the certificate as a mobileconfig-style download let body = self.certDER let response = """ HTTP/1.1 200 OK\r Content-Type: application/x-x509-ca-cert\r Content-Disposition: attachment; filename="ProxyCA.cer"\r Content-Length: \(body.count)\r Connection: close\r \r\n """ var responseData = Data(response.utf8) responseData.append(body) connection.send(content: responseData, completion: .contentProcessed { _ in connection.cancel() // Stop the server after serving — one-shot DispatchQueue.main.asyncAfter(deadline: .now() + 2) { self.stop() } }) } } }