Skip to content

Commit 1f64f81

Browse files
authored
Merge pull request #22343 from brave/bugfix/iOS-Certs-And-Secure-State
2 parents 169e4fc + 71ab49a commit 1f64f81

File tree

8 files changed

+72
-134
lines changed

8 files changed

+72
-134
lines changed

ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+ShareActivity.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,10 +301,13 @@ extension BrowserViewController {
301301
}
302302

303303
// Display Certificate Activity
304-
if let selectedTab = tabManager.selectedTab,
305-
selectedTab.secureContentState != .missingSSL && selectedTab.secureContentState != .unknown
304+
if let tabURL = tabManager.selectedTab?.webView?.url,
305+
tabManager.selectedTab?.webView?.serverTrust != nil
306+
|| ErrorPageHelper.hasCertificates(for: tabURL)
306307
{
307-
logSecureContentState(tab: selectedTab, details: "Display Certificate Activity Settings")
308+
if let selectedTab = tabManager.selectedTab {
309+
logSecureContentState(tab: selectedTab, details: "Display Certificate Activity Settings")
310+
}
308311

309312
activities.append(
310313
BasicMenuActivity(
@@ -316,7 +319,7 @@ extension BrowserViewController {
316319
)
317320
}
318321

319-
// Report Web-compat Issue Actibity
322+
// Report Web-compat Issue Activity
320323
activities.append(
321324
BasicMenuActivity(
322325
title: Strings.Shields.reportABrokenSite,

ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+WKNavigationDelegate.swift

Lines changed: 46 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -69,21 +69,6 @@ extension BrowserViewController: WKNavigationDelegate {
6969
if let selectedTab = tabManager.selectedTab,
7070
selectedTab.url?.origin != webView.url?.origin
7171
{
72-
if let url = webView.url {
73-
if !InternalURL.isValid(url: url) {
74-
// reset secure content state to unknown until page can be evaluated
75-
selectedTab.sslPinningError = nil
76-
selectedTab.sslPinningTrust = nil
77-
selectedTab.secureContentState = .unknown
78-
logSecureContentState(
79-
tab: selectedTab,
80-
details:
81-
"DidStartProvisionalNavigation - Reset secure content state to unknown until page can be evaluated"
82-
)
83-
84-
updateToolbarSecureContentState(.unknown)
85-
}
86-
}
8772

8873
// new site has a different origin, hide wallet icon.
8974
tabManager.selectedTab?.isWalletIconVisible = false
@@ -746,7 +731,9 @@ extension BrowserViewController: WKNavigationDelegate {
746731
download.delegate = self
747732
}
748733

749-
nonisolated public func webView(
734+
@MainActor
735+
736+
public func webView(
750737
_ webView: WKWebView,
751738
respondTo challenge: URLAuthenticationChallenge
752739
) async -> (URLSession.AuthChallengeDisposition, URLCredential?) {
@@ -756,11 +743,16 @@ extension BrowserViewController: WKNavigationDelegate {
756743
let host = challenge.protectionSpace.host
757744
let origin = "\(host):\(challenge.protectionSpace.port)"
758745
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
759-
let trust = challenge.protectionSpace.serverTrust,
760-
let cert = (SecTrustCopyCertificateChain(trust) as? [SecCertificate])?.first,
761-
profile.certStore.containsCertificate(cert, forOrigin: origin)
746+
let trust = challenge.protectionSpace.serverTrust
762747
{
763-
return (.useCredential, URLCredential(trust: trust))
748+
749+
let cert = await Task<SecCertificate?, Never>.detached {
750+
return (SecTrustCopyCertificateChain(trust) as? [SecCertificate])?.first
751+
}.value
752+
753+
if let cert = cert, profile.certStore.containsCertificate(cert, forOrigin: origin) {
754+
return (.useCredential, URLCredential(trust: trust))
755+
}
764756
}
765757

766758
// Certificate Pinning
@@ -782,13 +774,8 @@ extension BrowserViewController: WKNavigationDelegate {
782774
if result == Int32.min {
783775
// Cert is POTENTIALLY invalid and cannot be pinned
784776

785-
await MainActor.run {
786-
// Handle the potential error later in `didFailProvisionalNavigation`
787-
self.tab(for: webView)?.sslPinningTrust = serverTrust
788-
}
789-
790777
// Let WebKit handle the request and validate the cert
791-
// This is the same as calling `BraveCertificateUtils.evaluateTrust`
778+
// This is the same as calling `BraveCertificateUtils.evaluateTrust` but with more error info provided by WebKit
792779
return (.performDefaultHandling, nil)
793780
}
794781

@@ -802,7 +789,7 @@ extension BrowserViewController: WKNavigationDelegate {
802789
userInfo: ["_kCFStreamErrorCodeKey": Int(errorCode)]
803790
)
804791

805-
let error = await NSError(
792+
let error = NSError(
806793
domain: kCFErrorDomainCFNetwork as String,
807794
code: Int(errorCode),
808795
userInfo: [
@@ -812,10 +799,8 @@ extension BrowserViewController: WKNavigationDelegate {
812799
]
813800
)
814801

815-
await MainActor.run {
816-
// Handle the error later in `didFailProvisionalNavigation`
817-
self.tab(for: webView)?.sslPinningError = error
818-
}
802+
// Handle the error later in `didFailProvisionalNavigation`
803+
self.tab(for: webView)?.sslPinningError = error
819804

820805
return (.cancelAuthenticationChallenge, nil)
821806
}
@@ -825,39 +810,38 @@ extension BrowserViewController: WKNavigationDelegate {
825810
let protectionSpace = challenge.protectionSpace
826811
let credential = challenge.proposedCredential
827812
let previousFailureCount = challenge.previousFailureCount
828-
return await Task { @MainActor in
829-
guard
830-
protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic
831-
|| protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPDigest
832-
|| protectionSpace.authenticationMethod == NSURLAuthenticationMethodNTLM,
833-
let tab = tab(for: webView)
834-
else {
835-
return (.performDefaultHandling, nil)
836-
}
837813

838-
// The challenge may come from a background tab, so ensure it's the one visible.
839-
tabManager.selectTab(tab)
814+
guard
815+
protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic
816+
|| protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPDigest
817+
|| protectionSpace.authenticationMethod == NSURLAuthenticationMethodNTLM,
818+
let tab = tab(for: webView)
819+
else {
820+
return (.performDefaultHandling, nil)
821+
}
840822

841-
do {
842-
let credentials = try await Authenticator.handleAuthRequest(
843-
self,
844-
credential: credential,
845-
protectionSpace: protectionSpace,
846-
previousFailureCount: previousFailureCount
847-
)
823+
// The challenge may come from a background tab, so ensure it's the one visible.
824+
tabManager.selectTab(tab)
848825

849-
if BasicAuthCredentialsManager.validDomains.contains(host) {
850-
BasicAuthCredentialsManager.setCredential(
851-
origin: origin,
852-
credential: credentials.credentials
853-
)
854-
}
826+
do {
827+
let credentials = try await Authenticator.handleAuthRequest(
828+
self,
829+
credential: credential,
830+
protectionSpace: protectionSpace,
831+
previousFailureCount: previousFailureCount
832+
)
855833

856-
return (.useCredential, credentials.credentials)
857-
} catch {
858-
return (.rejectProtectionSpace, nil)
834+
if BasicAuthCredentialsManager.validDomains.contains(host) {
835+
BasicAuthCredentialsManager.setCredential(
836+
origin: origin,
837+
credential: credentials.credentials
838+
)
859839
}
860-
}.value
840+
841+
return (.useCredential, credentials.credentials)
842+
} catch {
843+
return (.rejectProtectionSpace, nil)
844+
}
861845
}
862846

863847
public func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
@@ -991,20 +975,6 @@ extension BrowserViewController: WKNavigationDelegate {
991975
) {
992976
guard let tab = tab(for: webView) else { return }
993977

994-
// WebKit does not update certs on cancellation of a frame load
995-
// So manually trigger the notification with the current cert
996-
// Also, when Chromium cert validation passes, BUT Apple cert validation fails, the request is cancelled automatically by WebKit
997-
// In such a case, the webView.serverTrust is `nil`. The only time we have a valid trust is when we received the challenge
998-
// so we need to update the URL-Bar to show that serverTrust when WebKit's is nil.
999-
logSecureContentState(tab: tab, details: "ObserveValue trigger in didFailProvisionalNavigation")
1000-
1001-
observeValue(
1002-
forKeyPath: KVOConstants.serverTrust.keyPath,
1003-
of: webView,
1004-
change: [.newKey: webView.serverTrust ?? tab.sslPinningTrust as Any, .kindKey: 1],
1005-
context: nil
1006-
)
1007-
1008978
// Ignore the "Frame load interrupted" error that is triggered when we cancel a request
1009979
// to open an external application and hand it over to UIApplication.openURL(). The result
1010980
// will be that we switch to the external app, for example the app store, while keeping the
@@ -1036,23 +1006,10 @@ extension BrowserViewController: WKNavigationDelegate {
10361006

10371007
if let url = error.userInfo[NSURLErrorFailingURLErrorKey] as? URL {
10381008

1039-
// The certificate came from the WebKit SSL Handshake validation and the cert is untrusted
1040-
if webView.serverTrust == nil, let serverTrust = tab.sslPinningTrust,
1041-
error.userInfo["NSErrorPeerCertificateChainKey"] == nil
1042-
{
1043-
// Build a cert chain error to display in the cert viewer in such cases, as we aren't given one by WebKit
1044-
var userInfo = error.userInfo
1045-
userInfo["NSErrorPeerCertificateChainKey"] =
1046-
SecTrustCopyCertificateChain(serverTrust) as? [SecCertificate] ?? []
1047-
userInfo["NSErrorPeerUntrustedByApple"] = true
1048-
error = NSError(domain: error.domain, code: error.code, userInfo: userInfo)
1009+
if tab == self.tabManager.selectedTab {
1010+
self.topToolbar.hideProgressBar()
10491011
}
10501012

1051-
ErrorPageHelper(certStore: profile.certStore).loadPage(error, forUrl: url, inWebView: webView)
1052-
// Submitting same errornous URL using toolbar will cause progress bar get stuck
1053-
// Reseting the progress bar in case there is an error is necessary
1054-
topToolbar.hideProgressBar()
1055-
10561013
// If the local web server isn't working for some reason (Brave cellular data is
10571014
// disabled in settings, for example), we'll fail to load the session restore URL.
10581015
// We rely on loading that page to get the restore callback to reset the restoring
@@ -1118,16 +1075,7 @@ extension BrowserViewController {
11181075
// External dialog should not be shown for non-active tabs #6687 - #7835
11191076
let isVisibleTab = tab?.isTabVisible() == true
11201077

1121-
// Check user trying to open on NTP like external link browsing
1122-
var isAboutHome = false
1123-
if let url = tab?.url {
1124-
isAboutHome = InternalURL(url)?.isAboutHomeURL == true
1125-
}
1126-
1127-
// Finally check non-active tab
1128-
let isNonActiveTab = isAboutHome ? false : tab?.url?.host != topToolbar.currentURL?.host
1129-
1130-
if !isVisibleTab || isNonActiveTab {
1078+
if !isVisibleTab {
11311079
return false
11321080
}
11331081

ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController.swift

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2071,8 +2071,10 @@ public class BrowserViewController: UIViewController {
20712071
tab.secureContentState = .unknown
20722072
logSecureContentState(tab: tab, path: path)
20732073

2074-
guard let serverTrust = tab.webView?.serverTrust else {
2075-
if let url = tab.webView?.url ?? tab.url {
2074+
guard let url = webView.url,
2075+
let serverTrust = webView.serverTrust
2076+
else {
2077+
if let url = webView.url {
20762078
if InternalURL.isValid(url: url),
20772079
let internalUrl = InternalURL(url),
20782080
internalUrl.isAboutURL || internalUrl.isAboutHomeURL
@@ -2166,8 +2168,8 @@ public class BrowserViewController: UIViewController {
21662168
break
21672169
}
21682170

2169-
guard let scheme = tab.webView?.url?.scheme,
2170-
let host = tab.webView?.url?.host
2171+
guard let scheme = url.scheme,
2172+
let host = url.host
21712173
else {
21722174
tab.secureContentState = .unknown
21732175
logSecureContentState(tab: tab, path: path, details: "No webview URL host scheme)")
@@ -2177,7 +2179,7 @@ public class BrowserViewController: UIViewController {
21772179
}
21782180

21792181
let port: Int
2180-
if let urlPort = tab.webView?.url?.port {
2182+
if let urlPort = url.port {
21812183
port = urlPort
21822184
} else if scheme == "https" {
21832185
port = 443
@@ -2315,15 +2317,8 @@ public class BrowserViewController: UIViewController {
23152317
browser.tabManager.addTabsForURLs([url], zombie: false, isPrivate: isPrivate)
23162318
}
23172319

2318-
public func switchToTabForURLOrOpen(
2319-
_ url: URL,
2320-
isPrivate: Bool = false,
2321-
isPrivileged: Bool,
2322-
isExternal: Bool = false
2323-
) {
2324-
if !isExternal {
2325-
popToBVC()
2326-
}
2320+
public func switchToTabForURLOrOpen(_ url: URL, isPrivate: Bool = false, isPrivileged: Bool) {
2321+
popToBVC(isAnimated: false)
23272322

23282323
if let tab = tabManager.getTabForURL(url, isPrivate: isPrivate) {
23292324
tabManager.selectTab(tab)
@@ -2461,11 +2456,11 @@ public class BrowserViewController: UIViewController {
24612456
present(settingsNavigationController, animated: true)
24622457
}
24632458

2464-
func popToBVC(completion: (() -> Void)? = nil) {
2459+
func popToBVC(isAnimated: Bool = true, completion: (() -> Void)? = nil) {
24652460
guard let currentViewController = navigationController?.topViewController else {
24662461
return
24672462
}
2468-
currentViewController.dismiss(animated: true, completion: completion)
2463+
currentViewController.dismiss(animated: isAnimated, completion: completion)
24692464

24702465
if currentViewController != self {
24712466
_ = self.navigationController?.popViewController(animated: true)

ios/brave-ios/Sources/Brave/Frontend/Browser/Helpers/ErrorPageHelper.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,6 @@ class ErrorPageHelper {
104104
URLQueryItem(name: "timestamp", value: "\(Int(Date().timeIntervalSince1970 * 1000))"),
105105
]
106106

107-
// The error came from WebKit's internal validation and the cert is untrusted
108-
if error.userInfo["NSErrorPeerUntrustedByApple"] as? Bool == true {
109-
queryItems.append(URLQueryItem(name: "peeruntrusted", value: "true"))
110-
}
111-
112107
// If this is an invalid certificate, show a certificate error allowing the
113108
// user to go back or continue. The certificate itself is encoded and added as
114109
// a query parameter to the error page URL; we then read the certificate from
@@ -186,6 +181,10 @@ extension ErrorPageHelper {
186181
return 0
187182
}
188183

184+
static func hasCertificates(for url: URL) -> Bool {
185+
return (url as NSURL).valueForQueryParameter(key: "badcerts") != nil
186+
}
187+
189188
static func serverTrust(from errorURL: URL) throws -> SecTrust? {
190189
guard let internalUrl = InternalURL(errorURL),
191190
internalUrl.isErrorPage,

ios/brave-ios/Sources/Brave/Frontend/Browser/Interstitial Pages/CertificateErrorPageHandler.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@ class CertificateErrorPageHandler: InterstitialPageHandler {
1717
}
1818

1919
func response(for model: ErrorPageModel) -> (URLResponse, Data)? {
20-
let hasCertificate =
21-
model.components.valueForQuery("certerror") != nil
22-
&& model.components.valueForQuery("peeruntrusted") == nil
20+
let hasCertificate = model.components.valueForQuery("certerror") != nil
2321

2422
guard let asset = Bundle.module.path(forResource: "CertificateError", ofType: "html") else {
2523
assert(false)

ios/brave-ios/Sources/Brave/Frontend/Browser/NavigationRouter.swift

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,7 @@ public enum NavigationPath: Equatable {
9191

9292
private static func handleURL(url: URL?, isPrivate: Bool, with bvc: BrowserViewController) {
9393
if let newURL = url {
94-
bvc.switchToTabForURLOrOpen(
95-
newURL,
96-
isPrivate: isPrivate,
97-
isPrivileged: false,
98-
isExternal: true
99-
)
100-
bvc.popToBVC()
94+
bvc.switchToTabForURLOrOpen(newURL, isPrivate: isPrivate, isPrivileged: false)
10195
} else {
10296
bvc.openBlankNewTab(attemptLocationFieldFocus: false, isPrivate: isPrivate)
10397
}

ios/brave-ios/Sources/Brave/Frontend/Browser/Tab.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ class Tab: NSObject {
103103

104104
var secureContentState: TabSecureContentState = .unknown
105105
var sslPinningError: Error?
106-
var sslPinningTrust: SecTrust?
107106

108107
private let _syncTab: BraveSyncTab?
109108
private let _faviconDriver: FaviconDriver?

ios/brave-ios/Sources/CertificateUtilities/BraveCertificateUtils.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ extension BraveCertificateUtils {
220220
return serverTrust!
221221
}
222222

223+
/// Verifies ServerTrust using Apple's APIs which validates also the X509 Certificate against the System Trusts
223224
public static func evaluateTrust(_ trust: SecTrust, for host: String?) async throws {
224225
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
225226
BraveCertificateUtils.evaluationQueue.async {
@@ -241,6 +242,7 @@ extension BraveCertificateUtils {
241242
}
242243
}
243244

245+
/// Verifies ServerTrust using Brave-Core which verifies only SSL Pinning Status
244246
public static func verifyTrust(_ trust: SecTrust, host: String, port: Int) async -> Int {
245247
return Int(BraveCertificateUtility.verifyTrust(trust, host: host, port: port))
246248
}

0 commit comments

Comments
 (0)