Chromium Code Reviews| Index: ios/web/web_state/ui/crw_wk_web_view_web_controller.mm |
| diff --git a/ios/web/web_state/ui/crw_wk_web_view_web_controller.mm b/ios/web/web_state/ui/crw_wk_web_view_web_controller.mm |
| index ad627c89d8cfcac5694f1618315f0fa82b659a20..a0c3572aac2b8a38d24fffadb7791ab1b9ea21be 100644 |
| --- a/ios/web/web_state/ui/crw_wk_web_view_web_controller.mm |
| +++ b/ios/web/web_state/ui/crw_wk_web_view_web_controller.mm |
| @@ -6,6 +6,7 @@ |
| #import <WebKit/WebKit.h> |
| +#include "base/containers/mru_cache.h" |
| #include "base/ios/ios_util.h" |
| #include "base/ios/weak_nsobject.h" |
| #include "base/json/json_reader.h" |
| @@ -20,6 +21,7 @@ |
| #import "ios/web/navigation/crw_session_entry.h" |
| #include "ios/web/navigation/navigation_item_impl.h" |
| #include "ios/web/navigation/web_load_params.h" |
| +#include "ios/web/net/cert_host_pair.h" |
| #import "ios/web/net/crw_cert_verification_controller.h" |
| #include "ios/web/public/cert_store.h" |
| #include "ios/web/public/navigation_item.h" |
| @@ -43,12 +45,30 @@ |
| #import "ios/web/web_state/web_view_internal_creation_util.h" |
| #import "ios/web/web_state/wk_web_view_security_util.h" |
| #import "ios/web/webui/crw_web_ui_manager.h" |
| -#include "net/cert/x509_certificate.h" |
| #import "net/base/mac/url_conversions.h" |
| +#include "net/cert/x509_certificate.h" |
| #include "net/ssl/ssl_info.h" |
| #include "url/url_constants.h" |
| namespace { |
| + |
| +// Represents cert verification error, which happened inside |
| +// |webView:didReceiveAuthenticationChallenge:completionHandler:| and should |
| +// be checked inside |webView:didFailProvisionalNavigation:withError:|. |
| +struct CertVerificationError { |
| + BOOL is_recoverable; |
| + net::CertStatus status; |
| +}; |
| + |
| +// Type of Cache object for storing cert verification errors. |
| +typedef base::MRUCache<web::CertHostPair, CertVerificationError> |
| + CertVerificationErrorsCacheType; |
| + |
| +// Maximum number of errors to store in cert verification errors cache. |
| +// Cache holds errors only for pending navigation, so the actual number of |
|
Ryan Sleevi
2015/10/28 21:28:51
s/pending navigation/pending navigations/
Eugene But (OOO till 7-30)
2015/10/29 00:39:15
Done.
|
| +// stored errors is not expected to be high. |
| +const CertVerificationErrorsCacheType::size_type kMaxCertErrorsCount = 100; |
| + |
| // Extracts Referer value from WKNavigationAction request header. |
| NSString* GetRefererFromNavigationAction(WKNavigationAction* action) { |
| return [action.request valueForHTTPHeaderField:@"Referer"]; |
| @@ -144,6 +164,13 @@ WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) { |
| // Cancelled navigations should be simply discarded without handling any |
| // specific error. |
| BOOL _pendingNavigationCancelled; |
| + |
| + // CertVerification errors which happened inside |
| + // |webView:didReceiveAuthenticationChallenge:completionHandler:|. |
| + // Key is leaf-cert/host pair. This storage is used to carry calculated |
| + // cert status from |didReceiveAuthenticationChallenge:| to |
| + // |didFailProvisionalNavigation:| delegate method. |
| + scoped_ptr<CertVerificationErrorsCacheType> _certVerificationErrors; |
| } |
| // Response's MIME type of the last known navigation. |
| @@ -271,6 +298,14 @@ WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) { |
| - (void)updateSSLStatusForCurrentNavigationItem; |
| #endif |
| +// Used in webView:didReceiveAuthenticationChallenge:completionHandler: to reply |
| +// with NSURLSessionAuthChallengeDisposition and credentials. |
| +- (void)processAuthChallenge:(NSURLAuthenticationChallenge*)challenge |
| + forCertAcceptPolicy:(web::CertAcceptPolicy)policy |
| + certStatus:(net::CertStatus)certStatus |
| + completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, |
| + NSURLCredential*))completionHandler; |
| + |
| // Registers load request with empty referrer and link or client redirect |
| // transition based on user interaction state. |
| - (void)registerLoadRequest:(const GURL&)url; |
| @@ -320,6 +355,8 @@ WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) { |
| if (self) { |
| _certVerificationController.reset([[CRWCertVerificationController alloc] |
| initWithBrowserState:browserState]); |
| + _certVerificationErrors.reset( |
| + new CertVerificationErrorsCacheType(kMaxCertErrorsCount)); |
| } |
| return self; |
| } |
| @@ -566,6 +603,7 @@ WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) { |
| - (void)abortWebLoad { |
| [_wkWebView stopLoading]; |
| + _certVerificationErrors->Clear(); |
| } |
| - (void)resetLoadState { |
| @@ -858,19 +896,57 @@ WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) { |
| - (void)handleSSLCertError:(NSError*)error { |
| DCHECK(web::IsWKWebViewSSLCertError(error)); |
| - net::SSLInfo sslInfo; |
| - web::GetSSLInfoFromWKWebViewSSLCertError(error, &sslInfo); |
| - |
| - web::SSLStatus sslStatus; |
| - sslStatus.security_style = web::SECURITY_STYLE_AUTHENTICATION_BROKEN; |
| - sslStatus.cert_status = sslInfo.cert_status; |
| - sslStatus.cert_id = web::CertStore::GetInstance()->StoreCert( |
| - sslInfo.cert.get(), self.certGroupID); |
| + net::SSLInfo SSLInfo; |
|
Ryan Sleevi
2015/10/28 21:28:51
DANGER: This variable naming dangerously borders o
Eugene But (OOO till 7-30)
2015/10/29 00:39:15
Done.
|
| + web::GetSSLInfoFromWKWebViewSSLCertError(error, &SSLInfo); |
| + |
| + web::SSLStatus SSLStatus; |
| + SSLStatus.security_style = web::SECURITY_STYLE_AUTHENTICATION_BROKEN; |
| + SSLStatus.cert_status = SSLInfo.cert_status; |
| + SSLStatus.cert_id = web::CertStore::GetInstance()->StoreCert( |
| + SSLInfo.cert.get(), self.certGroupID); |
| + |
| + // Retrieve verification results from _certVerificationErrors cache to avoid |
| + // unnecessary recalculations. Verification results are cached for the leaf |
| + // cert, because the cert chain in |didReceiveAuthenticationChallenge:| is |
| + // the OS constructed chain, while |chain| is the chain from the server. |
| + NSArray* chain = error.userInfo[web::kNSErrorPeerCertificateChainKey]; |
| + NSString* host = [error.userInfo[web::kNSErrorFailingURLKey] host]; |
| + scoped_refptr<net::X509Certificate> leafCert; |
| + BOOL recoverable = NO; |
| + if (chain.count && host.length) { |
| + // The complete cert chain may not be available, so the leaf cert is used |
| + // as a key to retrieve _certVerificationErrors, as well as for storing the |
| + // cert decision. |
| + leafCert = web::CreateCertFromChain(@[ chain.firstObject ]); |
| + if (leafCert) { |
| + // This cache will be purged in the final stage of |
| + // |webView:didFailProvisionalNavigation:withError:| so there is no need |
| + // to use |Get|. |
|
Ryan Sleevi
2015/10/28 21:28:51
That doesn't seem like a terribly useful justifica
Eugene But (OOO till 7-30)
2015/10/29 00:39:14
Changed to Get.
|
| + auto error = _certVerificationErrors->Peek( |
| + {leafCert, base::SysNSStringToUTF8(host)}); |
| + if (error != _certVerificationErrors->end()) { |
| + SSLStatus.cert_status = error->second.status; |
| + recoverable = error->second.is_recoverable; |
| + } else { |
| + // TODO(eugenebut): Report UMA with cache size (crbug.com/541736). |
| + } |
| + } |
| + } |
| - [self.delegate presentSSLError:sslInfo |
| - forSSLStatus:sslStatus |
| - recoverable:NO |
| - callback:nullptr]; |
| + // Present SSL interstitial. |
| + [self.delegate presentSSLError:SSLInfo |
| + forSSLStatus:SSLStatus |
| + recoverable:recoverable |
| + callback:^(BOOL proceed) { |
| + if (proceed) { |
|
Ryan Sleevi
2015/10/28 21:28:51
What happens for !proceed?
Eugene But (OOO till 7-30)
2015/10/29 00:39:14
Embedder (chrome) will remove transient entry and
|
| + // The interstitial will be removed during reload. |
| + [_certVerificationController |
| + allowCert:leafCert |
| + forHost:host |
| + status:SSLStatus.cert_status]; |
| + [self loadCurrentURL]; |
| + } |
| + }]; |
| } |
| #endif // #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW) |
| @@ -1013,6 +1089,44 @@ WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) { |
| #endif // !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW) |
| +- (void)processAuthChallenge:(NSURLAuthenticationChallenge*)challenge |
| + forCertAcceptPolicy:(web::CertAcceptPolicy)policy |
| + certStatus:(net::CertStatus)certStatus |
| + completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, |
| + NSURLCredential*))completionHandler { |
| + SecTrustRef trust = challenge.protectionSpace.serverTrust; |
| + if (policy == web::CERT_ACCEPT_POLICY_RECOVERABLE_ERROR_ACCEPTED_BY_USER) { |
| + // Cert is invalid, but user agreed to proceed, override default behavior. |
| + completionHandler(NSURLSessionAuthChallengeUseCredential, |
| + [NSURLCredential credentialForTrust:trust]); |
| + return; |
| + } |
| + |
| + if (policy != web::CERT_ACCEPT_POLICY_ALLOW && |
| + SecTrustGetCertificateCount(trust)) { |
| + // The cert is invalid and the user has not agreed to proceed. Cache the |
| + // cert verification result in |_certVerificationErrors|, so that it can |
| + // later be reused inside |didFailProvisionalNavigation:|. |
| + // The leaf cert is used as the key, because the chain provided by |
| + // |didFailProvisionalNavigation:| will differ (it is the server-supplied |
| + // chain), thus if intermediates were considered, the keys would mismatch. |
| + scoped_refptr<net::X509Certificate> leafCert = |
| + net::X509Certificate::CreateFromHandle( |
| + SecTrustGetCertificateAtIndex(trust, 0), |
| + net::X509Certificate::OSCertHandles()); |
| + if (leafCert) { |
| + BOOL is_recoverable = |
| + policy == |
| + web::CERT_ACCEPT_POLICY_RECOVERABLE_ERROR_UNDECIDED_BY_USER; |
| + std::string host = |
| + base::SysNSStringToUTF8(challenge.protectionSpace.host); |
| + _certVerificationErrors->Put({leafCert, host}, |
| + {is_recoverable, certStatus}); |
|
Ryan Sleevi
2015/10/28 21:28:51
For what it's worth, https://chromium-cpp.appspot.
Eugene But (OOO till 7-30)
2015/10/29 00:39:15
Done.
|
| + } |
| + } |
| + completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil); |
| +} |
| + |
| - (void)registerLoadRequest:(const GURL&)url { |
| // If load request is registered via WKWebViewWebController, assume transition |
| // is link or client redirect as other transitions will already be registered |
| @@ -1428,11 +1542,13 @@ WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) { |
| [self handleLoadError:error inMainFrame:YES]; |
| [self discardPendingNavigationTypeForMainFrame]; |
| + _certVerificationErrors->Clear(); |
|
Ryan Sleevi
2015/10/28 21:28:51
I find this code confusing enough that I can't con
Eugene But (OOO till 7-30)
2015/10/29 00:39:14
Unfortunately I don't see a better way for clearin
stuartmorgan
2015/10/29 16:41:06
Right, I agree it's ideal that we have it in a bun
stuartmorgan
2015/10/29 22:21:15
*not ideal
|
| } |
| - (void)webView:(WKWebView *)webView |
| didCommitNavigation:(WKNavigation *)navigation { |
| DCHECK_EQ(_wkWebView, webView); |
| + _certVerificationErrors->Clear(); |
| // This point should closely approximate the document object change, so reset |
| // the list of injected scripts to those that are automatically injected. |
| _injectedScriptManagers.reset([[NSMutableSet alloc] init]); |
| @@ -1477,13 +1593,14 @@ WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) { |
| withError:(NSError *)error { |
| [self handleLoadError:WKWebViewErrorWithSource(error, NAVIGATION) |
| inMainFrame:YES]; |
| + _certVerificationErrors->Clear(); |
| } |
| -- (void)webView:(WKWebView *)webView |
| - didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge |
| +- (void)webView:(WKWebView*)webView |
| + didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge |
| completionHandler: |
| - (void (^)(NSURLSessionAuthChallengeDisposition disposition, |
| - NSURLCredential *credential))completionHandler { |
| + (void (^)(NSURLSessionAuthChallengeDisposition, |
| + NSURLCredential*))completionHandler { |
| if (![challenge.protectionSpace.authenticationMethod |
| isEqual:NSURLAuthenticationMethodServerTrust]) { |
| completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); |
| @@ -1491,19 +1608,25 @@ WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) { |
| } |
| SecTrustRef trust = challenge.protectionSpace.serverTrust; |
| - scoped_refptr<net::X509Certificate> cert = web::CreateCertFromTrust(trust); |
| - // TODO(eugenebut): pass SecTrustRef instead of cert. |
| + base::ScopedCFTypeRef<SecTrustRef> scopedTrust(trust, |
| + base::scoped_policy::RETAIN); |
| + base::WeakNSObject<CRWWKWebViewWebController> weakSelf(self); |
| [_certVerificationController |
| - decidePolicyForCert:cert |
| - host:challenge.protectionSpace.host |
| - completionHandler:^(web::CertAcceptPolicy policy, |
| - net::CertStatus status) { |
| - completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, |
| - nil); |
| - }]; |
| + decideLoadPolicyForTrust:scopedTrust |
| + host:challenge.protectionSpace.host |
| + completionHandler:^(web::CertAcceptPolicy policy, |
| + net::CertStatus status) { |
| + base::scoped_nsobject<CRWWKWebViewWebController> strongSelf( |
| + [weakSelf retain]); |
| + [strongSelf processAuthChallenge:challenge |
| + forCertAcceptPolicy:policy |
| + certStatus:status |
| + completionHandler:completionHandler]; |
| + }]; |
| } |
| - (void)webViewWebContentProcessDidTerminate:(WKWebView*)webView { |
| + _certVerificationErrors->Clear(); |
| [self webViewWebProcessDidCrash]; |
| } |