Skip to content

Commit 09f5cd3

Browse files
authored
[iOS] Url bar falsely showing insecure state (uplift to 1.63.x) (#22380)
Merge pull request #22343 from brave/bugfix/iOS-Certs-And-Secure-State
1 parent 48b9717 commit 09f5cd3

File tree

8 files changed

+146
-164
lines changed

8 files changed

+146
-164
lines changed

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,10 @@ extension BrowserViewController {
268268
}
269269

270270
// Display Certificate Activity
271-
if let secureState = tabManager.selectedTab?.secureContentState, secureState != .missingSSL && secureState != .unknown {
271+
if let tabURL = tabManager.selectedTab?.webView?.url,
272+
tabManager.selectedTab?.webView?.serverTrust != nil
273+
|| ErrorPageHelper.hasCertificates(for: tabURL)
274+
{
272275
activities.append(
273276
BasicMenuActivity(
274277
title: Strings.displayCertificate,
@@ -278,7 +281,7 @@ extension BrowserViewController {
278281
)
279282
}
280283

281-
// Report Web-compat Issue Actibity
284+
// Report Web-compat Issue Activity
282285
activities.append(
283286
BasicMenuActivity(
284287
title: Strings.Shields.reportABrokenSite,

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

Lines changed: 116 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,9 @@ extension BrowserViewController: WKNavigationDelegate {
6666

6767
// check if web view is loading a different origin than the one currently loaded
6868
if let selectedTab = tabManager.selectedTab,
69-
selectedTab.url?.origin != webView.url?.origin {
70-
if let url = webView.url {
71-
if !InternalURL.isValid(url: url) {
72-
// reset secure content state to unknown until page can be evaluated
73-
selectedTab.sslPinningError = nil
74-
selectedTab.sslPinningTrust = nil
75-
selectedTab.secureContentState = .unknown
76-
updateToolbarSecureContentState(.unknown)
77-
}
78-
}
79-
69+
selectedTab.url?.origin != webView.url?.origin
70+
{
71+
8072
// new site has a different origin, hide wallet icon.
8173
tabManager.selectedTab?.isWalletIconVisible = false
8274
// new site, reset connected addresses
@@ -606,16 +598,28 @@ extension BrowserViewController: WKNavigationDelegate {
606598
download.delegate = self
607599
}
608600

609-
nonisolated public func webView(_ webView: WKWebView, respondTo challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) {
601+
602+
@MainActor
603+
604+
public func webView(
605+
_ webView: WKWebView,
606+
respondTo challenge: URLAuthenticationChallenge
607+
) async -> (URLSession.AuthChallengeDisposition, URLCredential?) {
610608

611609
// If this is a certificate challenge, see if the certificate has previously been
612610
// accepted by the user.
613611
let host = challenge.protectionSpace.host
614612
let origin = "\(host):\(challenge.protectionSpace.port)"
615613
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
616-
let trust = challenge.protectionSpace.serverTrust,
617-
let cert = (SecTrustCopyCertificateChain(trust) as? [SecCertificate])?.first, profile.certStore.containsCertificate(cert, forOrigin: origin) {
618-
return (.useCredential, URLCredential(trust: trust))
614+
let trust = challenge.protectionSpace.serverTrust
615+
{
616+
let cert = await Task<SecCertificate?, Never>.detached {
617+
return (SecTrustCopyCertificateChain(trust) as? [SecCertificate])?.first
618+
}.value
619+
620+
if let cert = cert, profile.certStore.containsCertificate(cert, forOrigin: origin) {
621+
return (.useCredential, URLCredential(trust: trust))
622+
}
619623
}
620624

621625
// Certificate Pinning
@@ -636,36 +640,35 @@ extension BrowserViewController: WKNavigationDelegate {
636640
// Let the system handle it and we'll show an error if the system cannot validate it
637641
if result == Int32.min {
638642
// Cert is POTENTIALLY invalid and cannot be pinned
639-
640-
await MainActor.run {
641-
// Handle the potential error later in `didFailProvisionalNavigation`
642-
self.tab(for: webView)?.sslPinningTrust = serverTrust
643-
}
644-
643+
645644
// Let WebKit handle the request and validate the cert
646-
// This is the same as calling `BraveCertificateUtils.evaluateTrust`
645+
// This is the same as calling `BraveCertificateUtils.evaluateTrust` but with more error info provided by WebKit
647646
return (.performDefaultHandling, nil)
648647
}
649648

650649
// Cert is invalid and cannot be pinned
651650
Logger.module.error("CERTIFICATE_INVALID")
652651
let errorCode = CFNetworkErrors.braveCertificatePinningFailed.rawValue
653-
654-
let underlyingError = NSError(domain: kCFErrorDomainCFNetwork as String,
655-
code: Int(errorCode),
656-
userInfo: ["_kCFStreamErrorCodeKey": Int(errorCode)])
657-
658-
let error = await NSError(domain: kCFErrorDomainCFNetwork as String,
659-
code: Int(errorCode),
660-
userInfo: [NSURLErrorFailingURLErrorKey: webView.url as Any,
661-
"NSErrorPeerCertificateChainKey": certificateChain,
662-
NSUnderlyingErrorKey: underlyingError])
663-
664-
await MainActor.run {
665-
// Handle the error later in `didFailProvisionalNavigation`
666-
self.tab(for: webView)?.sslPinningError = error
667-
}
668-
652+
653+
let underlyingError = NSError(
654+
domain: kCFErrorDomainCFNetwork as String,
655+
code: Int(errorCode),
656+
userInfo: ["_kCFStreamErrorCodeKey": Int(errorCode)]
657+
)
658+
659+
let error = NSError(
660+
domain: kCFErrorDomainCFNetwork as String,
661+
code: Int(errorCode),
662+
userInfo: [
663+
NSURLErrorFailingURLErrorKey: webView.url as Any,
664+
"NSErrorPeerCertificateChainKey": certificateChain,
665+
NSUnderlyingErrorKey: underlyingError,
666+
]
667+
)
668+
669+
// Handle the error later in `didFailProvisionalNavigation`
670+
self.tab(for: webView)?.sslPinningError = error
671+
669672
return (.cancelAuthenticationChallenge, nil)
670673
}
671674
}
@@ -674,35 +677,38 @@ extension BrowserViewController: WKNavigationDelegate {
674677
let protectionSpace = challenge.protectionSpace
675678
let credential = challenge.proposedCredential
676679
let previousFailureCount = challenge.previousFailureCount
677-
return await Task { @MainActor in
678-
guard protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic ||
679-
protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPDigest ||
680-
protectionSpace.authenticationMethod == NSURLAuthenticationMethodNTLM,
681-
let tab = tab(for: webView)
682-
else {
683-
return (.performDefaultHandling, nil)
684-
}
685-
686-
// The challenge may come from a background tab, so ensure it's the one visible.
687-
tabManager.selectTab(tab)
688-
689-
do {
690-
let credentials = try await Authenticator.handleAuthRequest(
691-
self,
692-
credential: credential,
693-
protectionSpace: protectionSpace,
694-
previousFailureCount: previousFailureCount
680+
681+
guard
682+
protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic
683+
|| protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPDigest
684+
|| protectionSpace.authenticationMethod == NSURLAuthenticationMethodNTLM,
685+
let tab = tab(for: webView)
686+
else {
687+
return (.performDefaultHandling, nil)
688+
}
689+
690+
// The challenge may come from a background tab, so ensure it's the one visible.
691+
tabManager.selectTab(tab)
692+
693+
do {
694+
let credentials = try await Authenticator.handleAuthRequest(
695+
self,
696+
credential: credential,
697+
protectionSpace: protectionSpace,
698+
previousFailureCount: previousFailureCount
699+
)
700+
701+
if BasicAuthCredentialsManager.validDomains.contains(host) {
702+
BasicAuthCredentialsManager.setCredential(
703+
origin: origin,
704+
credential: credentials.credentials
695705
)
696-
697-
if BasicAuthCredentialsManager.validDomains.contains(host) {
698-
BasicAuthCredentialsManager.setCredential(origin: origin, credential: credentials.credentials)
699-
}
700-
701-
return (.useCredential, credentials.credentials)
702-
} catch {
703-
return (.rejectProtectionSpace, nil)
704706
}
705-
}.value
707+
708+
return (.useCredential, credentials.credentials)
709+
} catch {
710+
return (.rejectProtectionSpace, nil)
711+
}
706712
}
707713

708714
public func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
@@ -817,17 +823,7 @@ extension BrowserViewController: WKNavigationDelegate {
817823
/// Invoked when an error occurs while starting to load data for the main frame.
818824
public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
819825
guard let tab = tab(for: webView) else { return }
820-
821-
// WebKit does not update certs on cancellation of a frame load
822-
// So manually trigger the notification with the current cert
823-
// Also, when Chromium cert validation passes, BUT Apple cert validation fails, the request is cancelled automatically by WebKit
824-
// In such a case, the webView.serverTrust is `nil`. The only time we have a valid trust is when we received the challenge
825-
// so we need to update the URL-Bar to show that serverTrust when WebKit's is nil.
826-
observeValue(forKeyPath: KVOConstants.serverTrust.keyPath,
827-
of: webView,
828-
change: [.newKey: webView.serverTrust ?? tab.sslPinningTrust as Any, .kindKey: 1],
829-
context: nil)
830-
826+
831827
// Ignore the "Frame load interrupted" error that is triggered when we cancel a request
832828
// to open an external application and hand it over to UIApplication.openURL(). The result
833829
// will be that we switch to the external app, for example the app store, while keeping the
@@ -858,20 +854,10 @@ extension BrowserViewController: WKNavigationDelegate {
858854
}
859855

860856
if let url = error.userInfo[NSURLErrorFailingURLErrorKey] as? URL {
861-
862-
// The certificate came from the WebKit SSL Handshake validation and the cert is untrusted
863-
if webView.serverTrust == nil, let serverTrust = tab.sslPinningTrust, error.userInfo["NSErrorPeerCertificateChainKey"] == nil {
864-
// Build a cert chain error to display in the cert viewer in such cases, as we aren't given one by WebKit
865-
var userInfo = error.userInfo
866-
userInfo["NSErrorPeerCertificateChainKey"] = SecTrustCopyCertificateChain(serverTrust) as? [SecCertificate] ?? []
867-
userInfo["NSErrorPeerUntrustedByApple"] = true
868-
error = NSError(domain: error.domain, code: error.code, userInfo: userInfo)
857+
858+
if tab == self.tabManager.selectedTab {
859+
self.topToolbar.hideProgressBar()
869860
}
870-
871-
ErrorPageHelper(certStore: profile.certStore).loadPage(error, forUrl: url, inWebView: webView)
872-
// Submitting same errornous URL using toolbar will cause progress bar get stuck
873-
// Reseting the progress bar in case there is an error is necessary
874-
topToolbar.hideProgressBar()
875861

876862
// If the local web server isn't working for some reason (Brave cellular data is
877863
// disabled in settings, for example), we'll fail to load the session restore URL.
@@ -912,55 +898,48 @@ extension BrowserViewController {
912898
private func handleExternalURL(
913899
_ url: URL,
914900
tab: Tab?,
915-
navigationAction: WKNavigationAction) async -> Bool {
916-
// Do not open external links for child tabs automatically
917-
// The user must tap on the link to open it.
918-
if tab?.parent != nil && navigationAction.navigationType != .linkActivated {
919-
return false
920-
}
921-
922-
// Check if the current url of the caller has changed
923-
if let domain = tab?.url?.baseDomain,
924-
domain != tab?.externalAppURLDomain {
925-
tab?.externalAppAlertCounter = 0
926-
tab?.isExternalAppAlertSuppressed = false
927-
}
928-
929-
tab?.externalAppURLDomain = tab?.url?.baseDomain
930-
931-
// Do not try to present over existing warning
932-
if tab?.isExternalAppAlertPresented == true || tab?.isExternalAppAlertSuppressed == true {
933-
return false
934-
}
935-
936-
// External dialog should not be shown for non-active tabs #6687 - #7835
937-
let isVisibleTab = tab?.isTabVisible() == true
938-
939-
// Check user trying to open on NTP like external link browsing
940-
var isAboutHome = false
941-
if let url = tab?.url {
942-
isAboutHome = InternalURL(url)?.isAboutHomeURL == true
943-
}
944-
945-
// Finally check non-active tab
946-
let isNonActiveTab = isAboutHome ? false : tab?.url?.host != topToolbar.currentURL?.host
947-
948-
if !isVisibleTab || isNonActiveTab {
949-
return false
950-
}
951-
952-
var alertTitle = Strings.openExternalAppURLGenericTitle
953-
954-
if let displayHost = tab?.url?.withoutWWW.host {
955-
alertTitle = String(format: Strings.openExternalAppURLTitle, displayHost)
956-
}
957-
958-
// Handling condition when Tab is empty when handling an external URL we should remove the tab once the user decides
959-
let removeTabIfEmpty = { [weak self] in
960-
if let tab = tab, tab.url == nil {
961-
self?.tabManager.removeTab(tab)
962-
}
901+
navigationAction: WKNavigationAction
902+
) async -> Bool {
903+
// Do not open external links for child tabs automatically
904+
// The user must tap on the link to open it.
905+
if tab?.parent != nil && navigationAction.navigationType != .linkActivated {
906+
return false
907+
}
908+
909+
// Check if the current url of the caller has changed
910+
if let domain = tab?.url?.baseDomain,
911+
domain != tab?.externalAppURLDomain
912+
{
913+
tab?.externalAppAlertCounter = 0
914+
tab?.isExternalAppAlertSuppressed = false
915+
}
916+
917+
tab?.externalAppURLDomain = tab?.url?.baseDomain
918+
919+
// Do not try to present over existing warning
920+
if tab?.isExternalAppAlertPresented == true || tab?.isExternalAppAlertSuppressed == true {
921+
return false
922+
}
923+
924+
// External dialog should not be shown for non-active tabs #6687 - #7835
925+
let isVisibleTab = tab?.isTabVisible() == true
926+
927+
if !isVisibleTab {
928+
return false
929+
}
930+
931+
var alertTitle = Strings.openExternalAppURLGenericTitle
932+
933+
if let displayHost = tab?.url?.withoutWWW.host {
934+
alertTitle = String(format: Strings.openExternalAppURLTitle, displayHost)
935+
}
936+
937+
// Handling condition when Tab is empty when handling an external URL we should remove the tab once the user decides
938+
let removeTabIfEmpty = { [weak self] in
939+
if let tab = tab, tab.url == nil {
940+
self?.tabManager.removeTab(tab)
963941
}
942+
}
964943

965944
// Show the external sceheme invoke alert
966945
@MainActor

0 commit comments

Comments
 (0)