diff --git a/App/iOS/Delegates/AppDelegate.swift b/App/iOS/Delegates/AppDelegate.swift index af821bda8e1..cb29f55b52a 100644 --- a/App/iOS/Delegates/AppDelegate.swift +++ b/App/iOS/Delegates/AppDelegate.swift @@ -107,6 +107,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } SystemUtils.onFirstRun() + + // Clean Logger for Secure content state + DebugLogger.cleanLogger(for: .secureState) + return true } @@ -215,7 +219,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { SceneDelegate.shouldHandleUrpLookup = true } else { log.error("Failed to initialize user referral program") - UrpLog.log("Failed to initialize user referral program") + DebugLogger.log(for: .urp, text: "Failed to initialize user referral program") } if Preferences.URP.installAttributionLookupOutstanding.value == nil { diff --git a/Sources/Brave/Frontend/Browser/BrowserViewController.swift b/Sources/Brave/Frontend/Browser/BrowserViewController.swift index 6028791a98f..d95d12741a1 100644 --- a/Sources/Brave/Frontend/Browser/BrowserViewController.swift +++ b/Sources/Brave/Frontend/Browser/BrowserViewController.swift @@ -1812,6 +1812,7 @@ public class BrowserViewController: UIViewController { } tab.secureContentState = .mixedContent + logSecureContentState(tab: tab, path: path) } if let url = tab.webView?.url, @@ -1822,18 +1823,22 @@ public class BrowserViewController: UIViewController { if ErrorPageHelper.certificateError(for: url) != 0 { // Cert validation takes precedence over all other errors tab.secureContentState = .invalidCert + logSecureContentState(tab: tab, path: path, details: "Cert validation takes precedence over all other errors") } else if NetworkErrorPageHandler.isNetworkError(errorCode: ErrorPageHelper.errorCode(for: url)) { // Network error takes precedence over missing cert // Because we cannot determine if a cert is missing yet, if we cannot connect to the server // Our network interstitial page shows tab.secureContentState = .localhost + logSecureContentState(tab: tab, path: path, details: "Network error takes precedence over missing cert") } else { // Since it's not a cert error explicitly, and it's not a network error, and the cert is missing (no serverTrust), // then we display .missingSSL tab.secureContentState = .missingSSL + logSecureContentState(tab: tab, path: path, details: "Certificate is missing (no serverTrust)") } } else if url.isReaderModeURL || InternalURL.isValid(url: url) { tab.secureContentState = .localhost + logSecureContentState(tab: tab, path: path, details: "Reader Mode or Internal URL") } } @@ -1846,14 +1851,18 @@ public class BrowserViewController: UIViewController { } tab.secureContentState = .unknown - - guard let serverTrust = tab.webView?.serverTrust else { - if let url = tab.webView?.url ?? tab.url { + logSecureContentState(tab: tab, path: path) + + guard let url = webView.url, + let serverTrust = webView.serverTrust else { + if let url = webView.url { if InternalURL.isValid(url: url), let internalUrl = InternalURL(url), (internalUrl.isAboutURL || internalUrl.isAboutHomeURL) { tab.secureContentState = .localhost + logSecureContentState(tab: tab, path: path, details: "Internal URL aboutURL or is aboutHomeURL") + if tabManager.selectedTab === tab { updateToolbarSecureContentState(.localhost) } @@ -1867,15 +1876,19 @@ public class BrowserViewController: UIViewController { if ErrorPageHelper.certificateError(for: url) != 0 { // Cert validation takes precedence over all other errors tab.secureContentState = .invalidCert + + logSecureContentState(tab: tab, path: path, details: "Cert validation takes precedence over all other errors") } else if NetworkErrorPageHandler.isNetworkError(errorCode: ErrorPageHelper.errorCode(for: url)) { // Network error takes precedence over missing cert // Because we cannot determine if a cert is missing yet, if we cannot connect to the server // Our network interstitial page shows tab.secureContentState = .localhost + logSecureContentState(tab: tab, path: path, details: "Network error takes precedence over missing cert") } else { // Since it's not a cert error explicitly, and it's not a network error, and the cert is missing (no serverTrust), // then we display .missingSSL tab.secureContentState = .missingSSL + logSecureContentState(tab: tab, path: path, details: "Certificate is missing (no serverTrust)") } if tabManager.selectedTab === tab { @@ -1886,6 +1899,8 @@ public class BrowserViewController: UIViewController { if url.isReaderModeURL || InternalURL.isValid(url: url) { tab.secureContentState = .localhost + logSecureContentState(tab: tab, path: path, details: "Reader Mode or Internal URL") + if tabManager.selectedTab === tab { updateToolbarSecureContentState(.localhost) } @@ -1894,9 +1909,11 @@ public class BrowserViewController: UIViewController { // All our checks failed, we show the page as insecure tab.secureContentState = .missingSSL + logSecureContentState(tab: tab, path: path, details: "All our checks failed, we show the page as insecure") } else { // When there is no URL, it's likely a new tab. tab.secureContentState = .localhost + logSecureContentState(tab: tab, path: path, details: "When there is no URL, it's likely a new tab") } if tabManager.selectedTab === tab { @@ -1905,15 +1922,17 @@ public class BrowserViewController: UIViewController { break } - guard let scheme = tab.webView?.url?.scheme, - let host = tab.webView?.url?.host else { + guard let scheme = url.scheme, + let host = url.host else { tab.secureContentState = .unknown + logSecureContentState(tab: tab, path: path, details: "No webview URL host scheme)") + self.updateURLBar() return } let port: Int - if let urlPort = tab.webView?.url?.port { + if let urlPort = url.port { port = urlPort } else if scheme == "https" { port = 443 @@ -1928,16 +1947,20 @@ public class BrowserViewController: UIViewController { // Cert is valid! if result == 0 { tab.secureContentState = .secure + logSecureContentState(tab: tab, path: path, details: "Cert is valid!") } else if result == Int32.min { // Cert is valid but should be validated by the system // Let the system handle it and we'll show an error if the system cannot validate it try await BraveCertificateUtils.evaluateTrust(serverTrust, for: host) tab.secureContentState = .secure + logSecureContentState(tab: tab, path: path, details: "Cert is valid but should be validated by the system") } else { tab.secureContentState = .invalidCert + logSecureContentState(tab: tab, path: path, details: "Invalid Cert") } } catch { tab.secureContentState = .invalidCert + logSecureContentState(tab: tab, path: path, details: "Verify Trust Error") } self.updateURLBar() @@ -1949,6 +1972,24 @@ public class BrowserViewController: UIViewController { } } + func logSecureContentState(tab: Tab, path: KVOConstants? = nil, details: String? = nil) { + var text = """ + Tab URL: \(tab.url?.absoluteString ?? "Empty Tab URL") + Tab VebView URL: \(tab.webView?.url?.absoluteString ?? "Empty Webview URL") + Secure State: \(tab.secureContentState.rawValue) + """ + + if let keyPath = path?.keyPath { + text.append("\n Value Observed: \(keyPath)\n") + } + + if let extraDetails = details { + text.append("\n Extra Details: \(extraDetails)\n") + } + + DebugLogger.log(for: .secureState, text: text) + } + func updateUIForReaderHomeStateForTab(_ tab: Tab) { updateURLBar() toolbarVisibilityViewModel.toolbarState = .expanded @@ -2007,10 +2048,8 @@ public class BrowserViewController: UIViewController { browser.tabManager.addTabsForURLs([url], zombie: false, isPrivate: isPrivate) } - public func switchToTabForURLOrOpen(_ url: URL, isPrivate: Bool = false, isPrivileged: Bool, isExternal: Bool = false) { - if !isExternal { - popToBVC() - } + public func switchToTabForURLOrOpen(_ url: URL, isPrivate: Bool = false, isPrivileged: Bool) { + popToBVC(isAnimated: false) if let tab = tabManager.getTabForURL(url, isPrivate: isPrivate) { tabManager.selectTab(tab) @@ -2128,11 +2167,11 @@ public class BrowserViewController: UIViewController { present(settingsNavigationController, animated: true) } - func popToBVC(completion: (() -> Void)? = nil) { + func popToBVC(isAnimated: Bool = true, completion: (() -> Void)? = nil) { guard let currentViewController = navigationController?.topViewController else { return } - currentViewController.dismiss(animated: true, completion: completion) + currentViewController.dismiss(animated: isAnimated, completion: completion) if currentViewController != self { _ = self.navigationController?.popViewController(animated: true) @@ -2321,7 +2360,10 @@ public class BrowserViewController: UIViewController { activities.append(addSearchEngineActivity) } - if let secureState = tabManager.selectedTab?.secureContentState, secureState != .missingSSL && secureState != .unknown { + if let tabURL = tabManager.selectedTab?.webView?.url, tabManager.selectedTab?.webView?.serverTrust != nil || ErrorPageHelper.hasCertificates(for: tabURL) { + if let selectedTab = tabManager.selectedTab { + logSecureContentState(tab: selectedTab, details: "Display Certificate Activity Settings") + } let displayCertificateActivity = BasicMenuActivity(title: Strings.displayCertificate, braveSystemImage: "leo.lock.plain") { [weak self] in self?.displayPageCertificateInfo() } diff --git a/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController+WKNavigationDelegate.swift b/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController+WKNavigationDelegate.swift index f9572dccb6c..b5e75b2e1ee 100644 --- a/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController+WKNavigationDelegate.swift +++ b/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController+WKNavigationDelegate.swift @@ -67,15 +67,6 @@ extension BrowserViewController: WKNavigationDelegate { // check if web view is loading a different origin than the one currently loaded if let selectedTab = tabManager.selectedTab, selectedTab.url?.origin != webView.url?.origin { - if let url = webView.url { - if !InternalURL.isValid(url: url) { - // reset secure content state to unknown until page can be evaluated - selectedTab.sslPinningError = nil - selectedTab.sslPinningTrust = nil - selectedTab.secureContentState = .unknown - updateToolbarSecureContentState(.unknown) - } - } // new site has a different origin, hide wallet icon. tabManager.selectedTab?.isWalletIconVisible = false @@ -585,16 +576,21 @@ extension BrowserViewController: WKNavigationDelegate { download.delegate = self } - nonisolated public func webView(_ webView: WKWebView, respondTo challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { - + @MainActor + public func webView(_ webView: WKWebView, respondTo challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { // If this is a certificate challenge, see if the certificate has previously been // accepted by the user. let host = challenge.protectionSpace.host let origin = "\(host):\(challenge.protectionSpace.port)" if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust, - let trust = challenge.protectionSpace.serverTrust, - let cert = (SecTrustCopyCertificateChain(trust) as? [SecCertificate])?.first, profile.certStore.containsCertificate(cert, forOrigin: origin) { - return (.useCredential, URLCredential(trust: trust)) + let trust = challenge.protectionSpace.serverTrust { + let cert = await Task.detached { + return (SecTrustCopyCertificateChain(trust) as? [SecCertificate])?.first + }.value + + if let cert = cert, profile.certStore.containsCertificate(cert, forOrigin: origin) { + return (.useCredential, URLCredential(trust: trust)) + } } // Certificate Pinning @@ -615,14 +611,9 @@ extension BrowserViewController: WKNavigationDelegate { // Let the system handle it and we'll show an error if the system cannot validate it if result == Int32.min { // Cert is POTENTIALLY invalid and cannot be pinned - - await MainActor.run { - // Handle the potential error later in `didFailProvisionalNavigation` - self.tab(for: webView)?.sslPinningTrust = serverTrust - } - // Let WebKit handle the request and validate the cert - // This is the same as calling `BraveCertificateUtils.evaluateTrust` + + // This is the same as calling `BraveCertificateUtils.evaluateTrust` but with more error info provided by WebKit return (.performDefaultHandling, nil) } @@ -633,17 +624,14 @@ extension BrowserViewController: WKNavigationDelegate { let underlyingError = NSError(domain: kCFErrorDomainCFNetwork as String, code: Int(errorCode), userInfo: ["_kCFStreamErrorCodeKey": Int(errorCode)]) - - let error = await NSError(domain: kCFErrorDomainCFNetwork as String, - code: Int(errorCode), - userInfo: [NSURLErrorFailingURLErrorKey: webView.url as Any, - "NSErrorPeerCertificateChainKey": certificateChain, - NSUnderlyingErrorKey: underlyingError]) - - await MainActor.run { - // Handle the error later in `didFailProvisionalNavigation` - self.tab(for: webView)?.sslPinningError = error - } + let error = NSError(domain: kCFErrorDomainCFNetwork as String, + code: Int(errorCode), + userInfo: [NSURLErrorFailingURLErrorKey: webView.url as Any, + "NSErrorPeerCertificateChainKey": certificateChain, + NSUnderlyingErrorKey: underlyingError]) + + // Handle the error later in `didFailProvisionalNavigation` + self.tab(for: webView)?.sslPinningError = error return (.cancelAuthenticationChallenge, nil) } @@ -653,35 +641,34 @@ extension BrowserViewController: WKNavigationDelegate { let protectionSpace = challenge.protectionSpace let credential = challenge.proposedCredential let previousFailureCount = challenge.previousFailureCount - return await Task { @MainActor in - guard protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic || - protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPDigest || - protectionSpace.authenticationMethod == NSURLAuthenticationMethodNTLM, - let tab = tab(for: webView) - else { - return (.performDefaultHandling, nil) - } + + guard protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic || + protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPDigest || + protectionSpace.authenticationMethod == NSURLAuthenticationMethodNTLM, + let tab = tab(for: webView) + else { + return (.performDefaultHandling, nil) + } - // The challenge may come from a background tab, so ensure it's the one visible. - tabManager.selectTab(tab) - - do { - let credentials = try await Authenticator.handleAuthRequest( - self, - credential: credential, - protectionSpace: protectionSpace, - previousFailureCount: previousFailureCount - ) - - if BasicAuthCredentialsManager.validDomains.contains(host) { - BasicAuthCredentialsManager.setCredential(origin: origin, credential: credentials.credentials) - } - - return (.useCredential, credentials.credentials) - } catch { - return (.rejectProtectionSpace, nil) + // The challenge may come from a background tab, so ensure it's the one visible. + tabManager.selectTab(tab) + + do { + let credentials = try await Authenticator.handleAuthRequest( + self, + credential: credential, + protectionSpace: protectionSpace, + previousFailureCount: previousFailureCount + ) + + if BasicAuthCredentialsManager.validDomains.contains(host) { + BasicAuthCredentialsManager.setCredential(origin: origin, credential: credentials.credentials) } - }.value + return (.useCredential, credentials.credentials) + } catch { + return (.rejectProtectionSpace, nil) + } + } public func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { @@ -694,6 +681,8 @@ extension BrowserViewController: WKNavigationDelegate { // However, WebKit does NOT trigger the `serverTrust` observer when the URL changes, but the trust has not. // WebKit also does NOT trigger the `serverTrust` observer when the page is actually insecure (non-https). // So manually trigger it with the current trust. + logSecureContentState(tab: tab, details: "ObserveValue trigger in didCommit") + observeValue(forKeyPath: KVOConstants.serverTrust.keyPath, of: webView, change: [.newKey: webView.serverTrust as Any, .kindKey: 1], @@ -795,16 +784,6 @@ extension BrowserViewController: WKNavigationDelegate { public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { guard let tab = tab(for: webView) else { return } - // WebKit does not update certs on cancellation of a frame load - // So manually trigger the notification with the current cert - // Also, when Chromium cert validation passes, BUT Apple cert validation fails, the request is cancelled automatically by WebKit - // In such a case, the webView.serverTrust is `nil`. The only time we have a valid trust is when we received the challenge - // so we need to update the URL-Bar to show that serverTrust when WebKit's is nil. - observeValue(forKeyPath: KVOConstants.serverTrust.keyPath, - of: webView, - change: [.newKey: webView.serverTrust ?? tab.sslPinningTrust as Any, .kindKey: 1], - context: nil) - // Ignore the "Frame load interrupted" error that is triggered when we cancel a request // to open an external application and hand it over to UIApplication.openURL(). The result // will be that we switch to the external app, for example the app store, while keeping the @@ -836,19 +815,9 @@ extension BrowserViewController: WKNavigationDelegate { if let url = error.userInfo[NSURLErrorFailingURLErrorKey] as? URL { - // The certificate came from the WebKit SSL Handshake validation and the cert is untrusted - if webView.serverTrust == nil, let serverTrust = tab.sslPinningTrust, error.userInfo["NSErrorPeerCertificateChainKey"] == nil { - // Build a cert chain error to display in the cert viewer in such cases, as we aren't given one by WebKit - var userInfo = error.userInfo - userInfo["NSErrorPeerCertificateChainKey"] = SecTrustCopyCertificateChain(serverTrust) as? [SecCertificate] ?? [] - userInfo["NSErrorPeerUntrustedByApple"] = true - error = NSError(domain: error.domain, code: error.code, userInfo: userInfo) + if tab == self.tabManager.selectedTab { + self.topToolbar.hideProgressBar() } - - ErrorPageHelper(certStore: profile.certStore).loadPage(error, forUrl: url, inWebView: webView) - // Submitting same errornous URL using toolbar will cause progress bar get stuck - // Reseting the progress bar in case there is an error is necessary - topToolbar.hideProgressBar() // If the local web server isn't working for some reason (Brave cellular data is // disabled in settings, for example), we'll fail to load the session restore URL. @@ -913,16 +882,7 @@ extension BrowserViewController { // External dialog should not be shown for non-active tabs #6687 - #7835 let isVisibleTab = tab?.isTabVisible() == true - // Check user trying to open on NTP like external link browsing - var isAboutHome = false - if let url = tab?.url { - isAboutHome = InternalURL(url)?.isAboutHomeURL == true - } - - // Finally check non-active tab - let isNonActiveTab = isAboutHome ? false : tab?.url?.host != topToolbar.currentURL?.host - - if !isVisibleTab || isNonActiveTab { + if !isVisibleTab { return false } diff --git a/Sources/Brave/Frontend/Browser/Helpers/ErrorPageHelper.swift b/Sources/Brave/Frontend/Browser/Helpers/ErrorPageHelper.swift index 69991c63379..086644db544 100644 --- a/Sources/Brave/Frontend/Browser/Helpers/ErrorPageHelper.swift +++ b/Sources/Brave/Frontend/Browser/Helpers/ErrorPageHelper.swift @@ -105,11 +105,6 @@ class ErrorPageHelper { // 'timestamp' is used for the js reload logic URLQueryItem(name: "timestamp", value: "\(Int(Date().timeIntervalSince1970 * 1000))"), ] - - // The error came from WebKit's internal validation and the cert is untrusted - if error.userInfo["NSErrorPeerUntrustedByApple"] as? Bool == true { - queryItems.append(URLQueryItem(name: "peeruntrusted", value: "true")) - } // If this is an invalid certificate, show a certificate error allowing the // user to go back or continue. The certificate itself is encoded and added as @@ -183,6 +178,10 @@ extension ErrorPageHelper { return 0 } + static func hasCertificates(for url: URL) -> Bool { + return (url as NSURL).valueForQueryParameter(key: "badcerts") != nil + } + static func serverTrust(from errorURL: URL) throws -> SecTrust? { guard let internalUrl = InternalURL(errorURL), internalUrl.isErrorPage, diff --git a/Sources/Brave/Frontend/Browser/Interstitial Pages/CertificateErrorPageHandler.swift b/Sources/Brave/Frontend/Browser/Interstitial Pages/CertificateErrorPageHandler.swift index f0f882663eb..f2a599bc9f3 100644 --- a/Sources/Brave/Frontend/Browser/Interstitial Pages/CertificateErrorPageHandler.swift +++ b/Sources/Brave/Frontend/Browser/Interstitial Pages/CertificateErrorPageHandler.swift @@ -17,7 +17,7 @@ class CertificateErrorPageHandler: InterstitialPageHandler { } func response(for model: ErrorPageModel) -> (URLResponse, Data)? { - let hasCertificate = model.components.valueForQuery("certerror") != nil && model.components.valueForQuery("peeruntrusted") == nil + let hasCertificate = model.components.valueForQuery("certerror") != nil guard let asset = Bundle.module.path(forResource: "CertificateError", ofType: "html") else { assert(false) diff --git a/Sources/Brave/Frontend/Browser/NavigationRouter.swift b/Sources/Brave/Frontend/Browser/NavigationRouter.swift index 29706cdf34a..7c3f0b4a1ff 100644 --- a/Sources/Brave/Frontend/Browser/NavigationRouter.swift +++ b/Sources/Brave/Frontend/Browser/NavigationRouter.swift @@ -85,8 +85,7 @@ public enum NavigationPath: Equatable { private static func handleURL(url: URL?, isPrivate: Bool, with bvc: BrowserViewController) { if let newURL = url { - bvc.switchToTabForURLOrOpen(newURL, isPrivate: isPrivate, isPrivileged: false, isExternal: true) - bvc.popToBVC() + bvc.switchToTabForURLOrOpen(newURL, isPrivate: isPrivate, isPrivileged: false) } else { bvc.openBlankNewTab(attemptLocationFieldFocus: false, isPrivate: isPrivate) } diff --git a/Sources/Brave/Frontend/Browser/Tab.swift b/Sources/Brave/Frontend/Browser/Tab.swift index 8e1a621fd04..082b98ef986 100644 --- a/Sources/Brave/Frontend/Browser/Tab.swift +++ b/Sources/Brave/Frontend/Browser/Tab.swift @@ -57,14 +57,14 @@ protocol URLChangeDelegate { func tab(_ tab: Tab, urlDidChangeTo url: URL) } -enum TabSecureContentState { - case unknown - case localhost - case secure - case invalidCert - case missingSSL - case mixedContent - +enum TabSecureContentState: String { + case unknown = "Unknown" + case localhost = "Localhost" + case secure = "Secure" + case invalidCert = "InvalidCertificate" + case missingSSL = "SSL Certificate Missing" + case mixedContent = "Mixed Content" + var shouldDisplayWarning: Bool { switch self { case .unknown, .invalidCert, .missingSSL, .mixedContent: @@ -95,7 +95,6 @@ class Tab: NSObject { var secureContentState: TabSecureContentState = .unknown var sslPinningError: Error? - var sslPinningTrust: SecTrust? private let _syncTab: BraveSyncTab? private let _faviconDriver: FaviconDriver? diff --git a/Sources/Brave/Frontend/Settings/SettingsViewController.swift b/Sources/Brave/Frontend/Settings/SettingsViewController.swift index 92b1d4402c8..33995bb6cb6 100644 --- a/Sources/Brave/Frontend/Settings/SettingsViewController.swift +++ b/Sources/Brave/Frontend/Settings/SettingsViewController.swift @@ -723,6 +723,11 @@ class SettingsViewController: TableViewController { return Static.Section( header: .title(Strings.about), rows: [ + Row( + text: "Secure Content State Debug", + selection: { [unowned self] in + self.navigationController?.pushViewController(DebugLogViewController(type: .secureState), animated: true) + }, accessory: .disclosureIndicator, cellClass: MultilineValue1Cell.self), Row( text: version, selection: { [unowned self] in @@ -811,7 +816,7 @@ class SettingsViewController: TableViewController { Row( text: "View URP Logs", selection: { [unowned self] in - self.navigationController?.pushViewController(UrpLogsViewController(), animated: true) + self.navigationController?.pushViewController(DebugLogViewController(type: .urp), animated: true) }, accessory: .disclosureIndicator, cellClass: MultilineValue1Cell.self), Row(text: "URP Code: \(UserReferralProgram.getReferralCode() ?? "--")"), Row( diff --git a/Sources/BraveShared/DebugLogger.swift b/Sources/BraveShared/DebugLogger.swift new file mode 100644 index 00000000000..97ccaadafc8 --- /dev/null +++ b/Sources/BraveShared/DebugLogger.swift @@ -0,0 +1,119 @@ +// Copyright 2024 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import UIKit +import Shared + +public enum LoggerType { + case urp, secureState + + var prefsKey: String { + switch self { + case .urp: + return "urpLogs" + case .secureState: + return "secureStateLogs" + } + } +} + +public struct DebugLogger { + public static func log(for type: LoggerType, text: String) { + // Secure State Logger should not be invoked for public channels + if type == .secureState { + guard !AppConstants.buildChannel.isPublic else { + return + } + } + + var logs = UserDefaults.standard.string(forKey: type.prefsKey) ?? "" + + let date = Date() + let calendar = Calendar.current + let components = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: date) + + guard let year = components.year, let month = components.month, let day = components.day, let hour = components.hour, let minute = components.minute else { + return + } + let time = "\(year)-\(month)-\(day) \(hour):\(minute)" + + switch type { + case .secureState: + logs.append("------------------------------------\n\n") + logs.append(" [\(time)]\n\n \(text)\n") + case .urp: + logs.append("[\(time)] \(text)\n") + } + + UserDefaults.standard.set(logs, forKey: type.prefsKey) + } + + public static func cleanLogger(for type: LoggerType) { + UserDefaults.standard.removeObject(forKey: type.prefsKey) + } +} + +public class DebugLogViewController: UIViewController { + var loggerType: LoggerType + + public init(type: LoggerType) { + loggerType = type + + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func viewDidLoad() { + super.viewDidLoad() + + switch loggerType { + case .urp: + title = "URP Logs" + case .secureState: + title = "Secure Content State" + } + + let rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(shareButtonTapped)) + navigationItem.rightBarButtonItem = rightBarButtonItem + + let textView = UITextView() + textView.translatesAutoresizingMaskIntoConstraints = false + textView.isEditable = false + + view.addSubview(textView) + + NSLayoutConstraint.activate([ + textView.topAnchor.constraint( + equalTo: view.topAnchor, constant: 8), + textView.leadingAnchor.constraint( + equalTo: view.leadingAnchor, constant: 8), + textView.trailingAnchor.constraint( + equalTo: view.trailingAnchor, constant: -8), + textView.bottomAnchor.constraint( + equalTo: view.bottomAnchor, constant: -8) + ]) + + guard let logs = UserDefaults.standard.string(forKey: loggerType.prefsKey) else { return } + let title = "Secure Content Logs\n\n" + + textView.text = title + logs + } + + @objc func shareButtonTapped() { + guard let logs = UserDefaults.standard.string(forKey: loggerType.prefsKey) else { return } + + // Create an activity view controller with the text to share + let activityViewController = UIActivityViewController(activityItems: [logs], applicationActivities: nil) + + // Present the activity view controller + if let popoverController = activityViewController.popoverPresentationController { + popoverController.barButtonItem = navigationItem.rightBarButtonItem + } + present(activityViewController, animated: true, completion: nil) + } +} diff --git a/Sources/CertificateUtilities/BraveCertificateUtils.swift b/Sources/CertificateUtilities/BraveCertificateUtils.swift index 1efd9acc228..8ca3d930e05 100644 --- a/Sources/CertificateUtilities/BraveCertificateUtils.swift +++ b/Sources/CertificateUtilities/BraveCertificateUtils.swift @@ -211,7 +211,8 @@ public extension BraveCertificateUtils { } return serverTrust! } - + + /// Verifies ServerTrust using Apple's APIs which validates also the X509 Certificate against the System Trusts static func evaluateTrust(_ trust: SecTrust, for host: String?) async throws { try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in BraveCertificateUtils.evaluationQueue.async { @@ -230,6 +231,7 @@ public extension BraveCertificateUtils { } } + /// Verifies ServerTrust using Brave-Core which verifies only SSL Pinning Status static func verifyTrust(_ trust: SecTrust, host: String, port: Int) async -> Int { return Int(BraveCertificateUtility.verifyTrust(trust, host: host, port: port)) } diff --git a/Sources/Growth/DAU.swift b/Sources/Growth/DAU.swift index c697c437873..c8e64af49f6 100644 --- a/Sources/Growth/DAU.swift +++ b/Sources/Growth/DAU.swift @@ -5,6 +5,7 @@ import Shared import BraveCore import os.log import Preferences +import BraveShared public class DAU { @@ -92,7 +93,7 @@ public class DAU { @objc public func sendPingToServerInternal() { guard let paramsAndPrefs = paramsAndPrefsSetup(for: Date()) else { Logger.module.debug("dau, no changes detected, no server ping") - UrpLog.log("dau, no changes detected, no server ping") + DebugLogger.log(for: .urp, text: "dau, no changes detected, no server ping") return } @@ -112,8 +113,8 @@ public class DAU { } Logger.module.debug("send ping to server, url: \(pingRequestUrl)") - UrpLog.log("send ping to server, url: \(pingRequestUrl)") - + DebugLogger.log(for: .urp, text: "send ping to server, url: \(pingRequestUrl)") + var request = URLRequest(url: pingRequestUrl) for (key, value) in paramsAndPrefs.headers { request.setValue(value, forHTTPHeaderField: key) @@ -126,7 +127,7 @@ public class DAU { if let e = error { Logger.module.error("status update error: \(e.localizedDescription)") - UrpLog.log("status update error: \(e)") + DebugLogger.log(for: .urp, text: "status update error: \(e)") return } @@ -212,7 +213,7 @@ public class DAU { if let referralCode = UserReferralProgram.getReferralCode() { params.append(URLQueryItem(name: "ref", value: referralCode)) - UrpLog.log("DAU ping with added ref, params: \(params)") + DebugLogger.log(for: .urp, text: "DAU ping with added ref, params: \(params)") } let lastPingTimestamp = [Int((date).timeIntervalSince1970)] diff --git a/Sources/Growth/URP/AdAttributionData.swift b/Sources/Growth/URP/AdAttributionData.swift index 8f47c018998..891a03b9db2 100644 --- a/Sources/Growth/URP/AdAttributionData.swift +++ b/Sources/Growth/URP/AdAttributionData.swift @@ -4,6 +4,7 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. import os.log +import BraveShared public struct AdAttributionData { // A value of true returns if a user clicks an Apple Search Ads impression up to 30 days before your app download. @@ -43,14 +44,14 @@ extension AdAttributionData { // They will indicate if the Apple Searhs Ads is clicked and for which campaign guard let attribution = json["attribution"] as? Bool else { Logger.module.error("Failed to unwrap json to Ad Attribution property.") - UrpLog.log("Failed to unwrap json to Ad Attribution property. \(json)") + DebugLogger.log(for: .urp, text: "Failed to unwrap json to Ad Attribution property. \(json)") throw SerializationError.missing("Attribution Context") } guard let campaignId = json["campaignId"] as? Int else { Logger.module.error("Failed to unwrap json to Campaign Id property.") - UrpLog.log("Failed to unwrap json to Campaign Id property. \(json)") + DebugLogger.log(for: .urp, text: "Failed to unwrap json to Campaign Id property. \(json)") throw SerializationError.missing("Campaign Id") } diff --git a/Sources/Growth/URP/ReferralData.swift b/Sources/Growth/URP/ReferralData.swift index ae8e4332222..58684fe840b 100644 --- a/Sources/Growth/URP/ReferralData.swift +++ b/Sources/Growth/URP/ReferralData.swift @@ -4,6 +4,7 @@ import Foundation import Shared import SwiftyJSON import os.log +import BraveShared struct ReferralData { @@ -25,7 +26,7 @@ struct ReferralData { init?(json: JSON) { guard let downloadId = json["download_id"].string, let code = json["referral_code"].string else { Logger.module.error("Failed to unwrap json to Referral struct.") - UrpLog.log("Failed to unwrap json to Referral struct. \(json)") + DebugLogger.log(for: .urp, text: "Failed to unwrap json to Referral struct. \(json)") return nil } diff --git a/Sources/Growth/URP/UrpLogsViewController.swift b/Sources/Growth/URP/UrpLogsViewController.swift deleted file mode 100644 index ac7ed0e5868..00000000000 --- a/Sources/Growth/URP/UrpLogsViewController.swift +++ /dev/null @@ -1,46 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import UIKit -import SnapKit - -public struct UrpLog { - static let prefsKey = "urpLogs" - - public static func log(_ text: String) { - var logs = UserDefaults.standard.string(forKey: prefsKey) ?? "" - - let date = Date() - let calendar = Calendar.current - let components = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: date) - - guard let year = components.year, let month = components.month, let day = components.day, let hour = components.hour, let minute = components.minute else { - return - } - - let time = "\(year)-\(month)-\(day) \(hour):\(minute)" - logs.append("[\(time)] \(text)\n") - - UserDefaults.standard.set(logs, forKey: prefsKey) - } -} - -public class UrpLogsViewController: UIViewController { - lazy var logsTextView: UITextView = { - let textView = UITextView() - textView.isEditable = false - return textView - }() - - public override func viewDidLoad() { - super.viewDidLoad() - - view.addSubview(logsTextView) - logsTextView.snp.makeConstraints { make in - make.top.equalTo(self.view.safeAreaLayoutGuide.snp.top).offset(8) - make.left.right.bottom.equalTo(self.view).inset(8) - } - - guard let logs = UserDefaults.standard.string(forKey: UrpLog.prefsKey) else { return } - logsTextView.text = logs - } -} diff --git a/Sources/Growth/URP/UrpService.swift b/Sources/Growth/URP/UrpService.swift index 1d15af655e2..5091bf7a1a0 100644 --- a/Sources/Growth/URP/UrpService.swift +++ b/Sources/Growth/URP/UrpService.swift @@ -42,7 +42,7 @@ struct UrpService { func referralCodeLookup(refCode: String?, completion: @escaping (ReferralData?, UrpError?) -> Void) { guard var endPoint = URL(string: host) else { completion(nil, .endpointError) - UrpLog.log("Host not a url: \(host)") + DebugLogger.log(for: .urp, text: "Host not a url: \(host)") return } @@ -63,16 +63,16 @@ struct UrpService { Logger.module.debug("Referral code lookup response: \(String(data: data, encoding: .utf8) ?? "nil")") } - UrpLog.log("Referral code lookup response: \(data)") - + DebugLogger.log(for: .urp, text: "Referral code lookup response: \(data)") + let json = JSON(data) let referral = ReferralData(json: json) completion(referral, nil) case .failure(let error): Logger.module.error("Referral code lookup response: \(error.localizedDescription)") - UrpLog.log("Referral code lookup response: \(error.localizedDescription)") - + DebugLogger.log(for: .urp, text: "Referral code lookup response: \(error.localizedDescription)") + completion(nil, .endpointError) } } @@ -88,7 +88,7 @@ struct UrpService { do { let (result, _) = try await sessionManager.adServicesAttributionApiRequest(endPoint: endPoint, rawData: attributionDataToken) - UrpLog.log("Ad Attribution response: \(result)") + DebugLogger.log(for: .urp, text: "Ad Attribution response: \(result)") if let resultData = result as? Data { let jsonResponse = try JSONSerialization.jsonObject(with: resultData, options: []) as? [String: Any] diff --git a/Sources/Growth/URP/UserReferralProgram.swift b/Sources/Growth/URP/UserReferralProgram.swift index e6af1550fdd..a30157f04db 100644 --- a/Sources/Growth/URP/UserReferralProgram.swift +++ b/Sources/Growth/URP/UserReferralProgram.swift @@ -8,6 +8,7 @@ import Preferences import WebKit import os.log import AdServices +import BraveShared public class UserReferralProgram { @@ -52,20 +53,20 @@ public class UserReferralProgram { guard let urpService = UrpService(host: host, apiKey: apiKey, adServicesURL: adServicesURLString) else { return nil } - UrpLog.log("URP init, host: \(host)") - + DebugLogger.log(for: .urp, text: "URP init, host: \(host)") + self.service = urpService } /// Looks for referral and returns its landing page if possible. public func referralLookup(refCode: String? = nil, completion: @escaping (_ refCode: String?, _ offerUrl: String?) -> Void) { - UrpLog.log("first run referral lookup") - + DebugLogger.log(for: .urp, text: "first run referral lookup") + let referralBlock: (ReferralData?, UrpError?) -> Void = { [weak self] referral, error in guard let self = self else { return } if error == Growth.UrpError.endpointError { - UrpLog.log("URP look up had endpoint error, will retry on next launch.") + DebugLogger.log(for: .urp, text: "URP look up had endpoint error, will retry on next launch.") self.referralLookupRetry.timer?.invalidate() self.referralLookupRetry.timer = nil @@ -90,14 +91,14 @@ public class UserReferralProgram { Preferences.URP.referralLookupOutstanding.value = false guard let ref = referral else { Logger.module.info("No referral code found") - UrpLog.log("No referral code found") + DebugLogger.log(for: .urp, text: "No referral code found") completion(nil, nil) return } if ref.isExtendedUrp() { completion(ref.referralCode, ref.offerPage) - UrpLog.log("Extended referral code found, opening landing page: \(ref.offerPage ?? "404")") + DebugLogger.log(for: .urp, text: "Extended referral code found, opening landing page: \(ref.offerPage ?? "404")") // We do not want to persist referral data for extended URPs return } @@ -108,7 +109,7 @@ public class UserReferralProgram { self.referralLookupRetry.timer?.invalidate() self.referralLookupRetry.timer = nil - UrpLog.log("Found referral: downloadId: \(ref.downloadId), code: \(ref.referralCode)") + DebugLogger.log(for: .urp, text: "Found referral: downloadId: \(ref.downloadId), code: \(ref.referralCode)") // In case of network errors or getting `isFinalized = false`, we retry the api call. self.initRetryPingConnection(numberOfTimes: 30) @@ -157,13 +158,13 @@ public class UserReferralProgram { public func pingIfEnoughTimePassed() { if !DeviceInfo.hasConnectivity() { - UrpLog.log("No internet connection, not sending update ping.") + DebugLogger.log(for: .urp, text: "No internet connection, not sending update ping.") return } guard let downloadId = Preferences.URP.downloadId.value else { Logger.module.info("Could not retrieve download id model from preferences.") - UrpLog.log("Update ping, no download id found.") + DebugLogger.log(for: .urp, text: "Update ping, no download id found.") return } @@ -176,11 +177,11 @@ public class UserReferralProgram { if todayInSeconds <= checkDate { Logger.module.debug("Not enough time has passed for referral ping.") - UrpLog.log("Not enough time has passed for referral ping.") + DebugLogger.log(for: .urp, text: "Not enough time has passed for referral ping.") return } - UrpLog.log("Update ping") + DebugLogger.log(for: .urp, text: "Update ping") service.checkIfAuthorizedForGrant(with: downloadId) { initialized, error in guard let counter = Preferences.URP.retryCountdown.value else { Logger.module.error("Could not retrieve retry countdown from preferences.") @@ -190,29 +191,29 @@ public class UserReferralProgram { var shouldRemoveData = false if error == .downloadIdNotFound { - UrpLog.log("Download id not found on server.") + DebugLogger.log(for: .urp, text: "Download id not found on server.") shouldRemoveData = true } if initialized == true { - UrpLog.log("Got initialized = true from server.") + DebugLogger.log(for: .urp, text: "Got initialized = true from server.") shouldRemoveData = true } // Last retry attempt if counter <= 1 { - UrpLog.log("Last retry and failed to get data from server.") + DebugLogger.log(for: .urp, text: "Last retry and failed to get data from server.") shouldRemoveData = true } if shouldRemoveData { - UrpLog.log("Removing all referral data from device") - + DebugLogger.log(for: .urp, text: "Removing all referral data from device") + Preferences.URP.downloadId.value = nil Preferences.URP.nextCheckDate.value = nil Preferences.URP.retryCountdown.value = nil } else { - UrpLog.log("Network error or isFinalized returned false, decrementing retry counter and trying again next time.") + DebugLogger.log(for: .urp, text: "Network error or isFinalized returned false, decrementing retry counter and trying again next time.") // Decrement counter, next retry happens on next day Preferences.URP.retryCountdown.value = counter - 1 Preferences.URP.nextCheckDate.value = checkDate + 1.days @@ -226,12 +227,12 @@ public class UserReferralProgram { Date().timeIntervalSince1970 >= referralCodeDeleteDate { Preferences.URP.referralCode.value = nil Preferences.URP.referralCodeDeleteDate.value = nil - UrpLog.log("Enough time has passed, removing referral code data") + DebugLogger.log(for: .urp, text: "Enough time has passed, removing referral code data") return nil } else if let referralCode = Preferences.URP.referralCode.value { // Appending ref code to dau ping if user used installed the app via user referral program. if Preferences.URP.referralCodeDeleteDate.value == nil { - UrpLog.log("Setting new date for deleting referral code.") + DebugLogger.log(for: .urp, text: "Setting new date for deleting referral code.") let timeToDelete = AppConstants.buildChannel.isPublic ? 90.days : 20.minutes Preferences.URP.referralCodeDeleteDate.value = Date().timeIntervalSince1970 + timeToDelete }