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 e5c88664c2057d26c56a89ea4e4b249886d6e8fb..7d1b1d5ef5ba80e33ecf47e3b336f8ad3015fa3a 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 |
| @@ -11,6 +11,7 @@ |
| #include "base/json/json_reader.h" |
| #import "base/mac/scoped_nsobject.h" |
| #include "base/macros.h" |
| +#include "base/metrics/histogram_macros.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/values.h" |
| #import "ios/net/http_response_headers_util.h" |
| @@ -250,6 +251,21 @@ WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) { |
| // Clears all activity indicator tasks for this web controller. |
| - (void)clearActivityIndicatorTasks; |
| +// Updates |security_style| and |cert_status| for the NavigationItem with ID |
| +// |navigationItemID|, if URL and certificate chain still match |host| and |
| +// |certChain|. |
| +- (void)updateSSLStatusForNavigationItemWithID:(int)navigationItemID |
| + certChain:(NSArray*)chain |
| + host:(NSString*)host |
| + withSecurityStyle:(web::SecurityStyle)style |
| + certStatus:(net::CertStatus)certStatus; |
| + |
| +// Asynchronously obtains SSL status from given |certChain| and |host| and |
| +// updates current navigation item. Before scheduling update changes SSLStatus' |
| +// cert_status and security_style to default. |
| +- (void)scheduleSSLStatusUpdateUsingCertChain:(NSArray*)chain |
| + host:(NSString*)host; |
| + |
| #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW) |
| // Updates SSL status for the current navigation item based on the information |
| // provided by web view. |
| @@ -864,47 +880,133 @@ WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) { |
| clearNetworkTasksForGroup:[self activityIndicatorGroupID]]; |
| } |
| +- (void)updateSSLStatusForNavigationItemWithID:(int)navigationItemID |
| + certChain:(NSArray*)chain |
| + host:(NSString*)host |
| + withSecurityStyle:(web::SecurityStyle)style |
| + certStatus:(net::CertStatus)certStatus { |
| + web::NavigationManager* navigationManager = |
| + self.webStateImpl->GetNavigationManager(); |
| + |
| + // The searched item almost always be the last one, so walk backward rather |
| + // than forward. |
| + for (int i = navigationManager->GetEntryCount() - 1; 0 <= i; i--) { |
| + web::NavigationItem* item = navigationManager->GetItemAtIndex(i); |
| + if (item->GetUniqueID() != navigationItemID) |
| + continue; |
| + |
| + // NavigationItem's UniqueID is preserved even after redirects, so |
| + // checking that cert and URL match is necessary. |
| + scoped_refptr<net::X509Certificate> cert(web::CreateCertFromChain(chain)); |
| + int certID = |
| + web::CertStore::GetInstance()->StoreCert(cert.get(), self.certGroupID); |
| + std::string GURLHost = base::SysNSStringToUTF8(host); |
| + web::SSLStatus& SSLStatus = item->GetSSL(); |
| + if (SSLStatus.cert_id == certID && item->GetURL().host() == GURLHost) { |
| + web::SSLStatus previousSSLStatus = item->GetSSL(); |
| + SSLStatus.cert_status = certStatus; |
| + SSLStatus.security_style = style; |
| + if (navigationManager->GetCurrentEntryIndex() == i && |
| + !previousSSLStatus.Equals(SSLStatus)) { |
| + [self didUpdateSSLStatusForCurrentNavigationItem]; |
| + } |
| + } |
| + return; |
| + } |
| +} |
| + |
| +- (void)scheduleSSLStatusUpdateUsingCertChain:(NSArray*)chain |
| + host:(NSString*)host { |
| + // Use Navigation Item's unique ID to locate requested item after |
| + // obtaining cert status asynchronously. |
| + int itemID = self.webStateImpl->GetNavigationManager() |
| + ->GetLastCommittedItem() |
| + ->GetUniqueID(); |
| + |
| + base::WeakNSObject<CRWWKWebViewWebController> weakSelf(self); |
| + void (^SSLStatusResponse)(web::SecurityStyle, net::CertStatus) = |
| + ^(web::SecurityStyle style, net::CertStatus certStatus) { |
| + base::scoped_nsobject<CRWWKWebViewWebController> strongSelf( |
| + [weakSelf retain]); |
| + if (!strongSelf || [strongSelf isBeingDestroyed]) { |
| + return; |
| + } |
| + [strongSelf updateSSLStatusForNavigationItemWithID:itemID |
| + certChain:chain |
| + host:host |
| + withSecurityStyle:style |
| + certStatus:certStatus]; |
| + }; |
| + |
| + [_certVerificationController |
| + querySSLStatusForTrust:web::CreateServerTrustFromChain(chain, host) |
| + host:host |
| + completionHandler:SSLStatusResponse]; |
| +} |
| + |
| #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW) |
| + |
| - (void)updateSSLStatusForCurrentNavigationItem { |
| if ([self isBeingDestroyed]) |
| return; |
| - DCHECK(self.webStateImpl); |
| web::NavigationItem* item = |
| self.webStateImpl->GetNavigationManagerImpl().GetLastCommittedItem(); |
| if (!item) |
| return; |
| web::SSLStatus previousSSLStatus = item->GetSSL(); |
| - web::SSLStatus& SSLStatus = item->GetSSL(); |
| + |
| + // Starting from iOS9 WKWebView blocks active mixed content, so if |
| + // |hasOnlySecureContent| returns NO it means passive content. |
| + item->GetSSL().content_status = |
| + [_wkWebView hasOnlySecureContent] |
| + ? web::SSLStatus::NORMAL_CONTENT |
| + : web::SSLStatus::DISPLAYED_INSECURE_CONTENT; |
| + |
| + // Try updating SSLStatus for current NavigationItem asynchronously. |
| + scoped_refptr<net::X509Certificate> cert; |
| if (item->GetURL().SchemeIsCryptographic()) { |
| - // TODO(eugenebut): Do not set security style to authenticated once |
| - // proceeding with bad ssl cert is implemented. |
| - SSLStatus.security_style = web::SECURITY_STYLE_AUTHENTICATED; |
| - SSLStatus.content_status = [_wkWebView hasOnlySecureContent] |
| - ? web::SSLStatus::NORMAL_CONTENT |
| - : web::SSLStatus::DISPLAYED_INSECURE_CONTENT; |
| - |
| - if (base::ios::IsRunningOnIOS9OrLater()) { |
| - scoped_refptr<net::X509Certificate> cert(web::CreateCertFromChain( |
| - [_wkWebView performSelector:@selector(certificateChain)])); |
| - if (cert) { |
| - SSLStatus.cert_id = web::CertStore::GetInstance()->StoreCert( |
| - cert.get(), self.certGroupID); |
| - } else { |
| - SSLStatus.security_style = web::SECURITY_STYLE_UNAUTHENTICATED; |
| - SSLStatus.cert_id = 0; |
| + NSArray* chain = [_wkWebView certificateChain]; |
| + cert = web::CreateCertFromChain(chain); |
| + if (cert) { |
| + int oldCertID = item->GetSSL().cert_id; |
| + std::string oldHost = item->GetSSL().cert_status_host; |
| + item->GetSSL().cert_id = web::CertStore::GetInstance()->StoreCert( |
| + cert.get(), self.certGroupID); |
| + item->GetSSL().cert_status_host = _documentURL.host(); |
| + // Only recompute the SSLStatus information if the certificate or host has |
| + // since changed. Host can be changed in case of redirect. |
| + if (oldCertID != item->GetSSL().cert_id || |
| + oldHost != item->GetSSL().cert_status_host) { |
| + // Real SSL status is unknown, reset cert status and security style. |
| + // they will be asynchronously updated in |
|
davidben
2015/10/16 00:15:20
they -> They
Eugene But (OOO till 7-30)
2015/10/16 02:25:35
Done.
|
| + // |scheduleSSLStatusUpdateUsingCertChain|. |
| + item->GetSSL().cert_status = net::CertStatus(); |
| + item->GetSSL().security_style = web::SECURITY_STYLE_UNKNOWN; |
| + |
| + NSString* host = base::SysUTF8ToNSString(_documentURL.host()); |
| + [self scheduleSSLStatusUpdateUsingCertChain:chain host:host]; |
| } |
| } |
| - } else { |
| - SSLStatus.security_style = web::SECURITY_STYLE_UNAUTHENTICATED; |
| - SSLStatus.cert_id = 0; |
| } |
| - if (!previousSSLStatus.Equals(SSLStatus)) { |
| + if (!cert) { |
| + item->GetSSL().cert_id = 0; |
| + if (!item->GetURL().SchemeIsCryptographic()) { |
| + // HTTP or other non-secure connection. |
| + item->GetSSL().security_style = web::SECURITY_STYLE_UNAUTHENTICATED; |
| + } else { |
| + // HTTPS, no certificate (this use-case has not been observed). |
| + item->GetSSL().security_style = web::SECURITY_STYLE_UNKNOWN; |
| + } |
| + } |
| + |
| + if (!previousSSLStatus.Equals(item->GetSSL())) { |
| [self didUpdateSSLStatusForCurrentNavigationItem]; |
| } |
| } |
| + |
| #endif // !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW) |
| - (void)registerLoadRequest:(const GURL&)url { |
| @@ -923,6 +1025,7 @@ WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) { |
| - (void)URLDidChangeWithoutDocumentChange:(const GURL&)newURL { |
| DCHECK(newURL == net::GURLWithNSURL([_wkWebView URL])); |
| + DCHECK_EQ(_documentURL.host(), newURL.host()); |
| _documentURL = newURL; |
| // If called during window.history.pushState or window.history.replaceState |
| // JavaScript evaluation, only update the document URL. This callback does not |
| @@ -1356,6 +1459,14 @@ WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) { |
| #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW) |
| [self updateSSLStatusForCurrentNavigationItem]; |
| #endif |
| + |
| + // Report cases where SSL cert is missing for a secure connection. |
| + if (_documentURL.SchemeIsCryptographic()) { |
| + scoped_refptr<net::X509Certificate> cert = |
| + web::CreateCertFromChain([_wkWebView certificateChain]); |
| + UMA_HISTOGRAM_BOOLEAN("WebController.WKWebViewHasCertForSecureConnection", |
| + cert); |
| + } |
| } |
| - (void)webView:(WKWebView *)webView |
| @@ -1389,6 +1500,7 @@ WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) { |
| SecTrustRef trust = challenge.protectionSpace.serverTrust; |
| scoped_refptr<net::X509Certificate> cert = web::CreateCertFromTrust(trust); |
| + // TODO(eugenebut): pass SecTrustRef instead of cert. |
| [_certVerificationController |
| decidePolicyForCert:cert |
| host:challenge.protectionSpace.host |