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 726c4770e6ed33293bcce2a3fec08142abbc13fc..137201aaf804940622fceb944224b731c4b84523 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 |
| +// 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. |
| @@ -270,6 +297,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; |
| @@ -319,6 +354,8 @@ WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) { |
| if (self) { |
| _certVerificationController.reset([[CRWCertVerificationController alloc] |
| initWithBrowserState:browserState]); |
| + _certVerificationErrors.reset( |
| + new CertVerificationErrorsCacheType(kMaxCertErrorsCount)); |
| } |
| return self; |
| } |
| @@ -565,6 +602,7 @@ WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) { |
| - (void)abortWebLoad { |
| [_wkWebView stopLoading]; |
| + _certVerificationErrors->Clear(); |
| } |
| - (void)resetLoadState { |
| @@ -857,19 +895,55 @@ 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; |
| + 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 leaf cert, |
|
Ryan Sleevi
2015/10/19 23:56:22
s/for leaf cert/for the leaf cert/
Eugene But (OOO till 7-30)
2015/10/21 04:01:02
Done.
|
| + // because cert chain in |didReceiveAuthenticationChallenge:| is OS |
|
Ryan Sleevi
2015/10/19 23:56:22
s/is OS/is the OS/
Ryan Sleevi
2015/10/19 23:56:22
s/because cert chain/because the cert chain/
Eugene But (OOO till 7-30)
2015/10/21 04:01:02
Done.
Eugene But (OOO till 7-30)
2015/10/21 04:01:03
Done.
|
| + // constructed chain, while |chain| is a chain from the server. |
|
Ryan Sleevi
2015/10/19 23:56:22
s/a chain/the chain/
Eugene But (OOO till 7-30)
2015/10/21 04:01:02
Done.
|
| + 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) { |
| + // Complete cert chain may not be available inside this method, so leaf |
| + // cert is used as a key to retrieve _certVerificationErrors as well as for |
| + // storing cert decision. |
|
Ryan Sleevi
2015/10/19 23:56:22
// The complete cert chain may not be available, s
Eugene But (OOO till 7-30)
2015/10/21 04:01:02
Done.
|
| + leafCert = web::CreateCertFromChain(@[ chain.firstObject ]); |
| + if (leafCert) { |
| + // This cache will be purged anyway so there is no need to use |Get|. |
|
Ryan Sleevi
2015/10/19 23:56:22
This is surprising/non-obvious, and doesn't seem t
Eugene But (OOO till 7-30)
2015/10/21 04:01:02
MRUCache allows cache to be bounded and evicts lea
Ryan Sleevi
2015/10/28 21:28:50
I suppose this wasn't clear, as I'm still uncertai
Eugene But (OOO till 7-30)
2015/10/29 00:39:14
_certVerificationErrors will be purged right after
|
| + 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) { |
| + // 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) |
| @@ -1012,6 +1086,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)) { |
| + // Cert is invalid and user has not agreed to proceed. Cache cert |
|
Ryan Sleevi
2015/10/19 23:56:22
// The cert is invalid and the user has not agreed
Eugene But (OOO till 7-30)
2015/10/21 04:01:03
Done.
|
| + // verification result with _certVerificationErrors storage, so it can be |
| + // later reused inside |didFailProvisionalNavigation:|. |
| + // Leaf cert (w/o any intermidiates) is used as a key, because chain inside |
| + // |didFailProvisionalNavigation:| differs (it will be server chain) and |
| + // using intermidiates may result in keys mismatch. |
|
Ryan Sleevi
2015/10/19 23:56:22
// The leaf cert is used as the key, because the c
Eugene But (OOO till 7-30)
2015/10/21 04:01:02
Done.
|
| + 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}); |
| + } |
| + } |
| + 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 |
| @@ -1427,11 +1539,13 @@ WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) { |
| [self handleLoadError:error inMainFrame:YES]; |
| [self discardPendingNavigationTypeForMainFrame]; |
| + _certVerificationErrors->Clear(); |
| } |
| - (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]); |
| @@ -1476,13 +1590,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); |
| @@ -1490,19 +1605,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]; |
| } |