Skip to content

Commit

Permalink
Define CWVSSLErrorHandler
Browse files Browse the repository at this point in the history
This will be used to handle SSL errors like expired certificates.
The previous method WebClient::AllowCertificateError is no longer used.

Change-Id: If165e3f8d9e084b0bfdc40b8bba281bb163f7ddd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2786322
Commit-Queue: John Wu <jzw@chromium.org>
Reviewed-by: Hiroshi Ichikawa <ichikawa@chromium.org>
Cr-Commit-Position: refs/heads/master@{#868284}
  • Loading branch information
John Wu authored and Chromium LUCI CQ committed Mar 31, 2021
1 parent 9b84ea1 commit 6e9b2f5
Show file tree
Hide file tree
Showing 10 changed files with 367 additions and 2 deletions.
5 changes: 5 additions & 0 deletions ios/web_view/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ ios_web_view_public_headers = [
"public/cwv_preferences.h",
"public/cwv_preview_element_info.h",
"public/cwv_script_command.h",
"public/cwv_ssl_error_handler.h",
"public/cwv_ssl_status.h",
"public/cwv_sync_controller.h",
"public/cwv_sync_controller_data_source.h",
Expand Down Expand Up @@ -142,6 +143,8 @@ source_set("web_view_sources") {
"internal/cwv_preview_element_info_internal.h",
"internal/cwv_script_command.mm",
"internal/cwv_script_command_internal.h",
"internal/cwv_ssl_error_handler.mm",
"internal/cwv_ssl_error_handler_internal.h",
"internal/cwv_ssl_status.mm",
"internal/cwv_ssl_status_internal.h",
"internal/cwv_ssl_util.h",
Expand Down Expand Up @@ -317,6 +320,7 @@ source_set("web_view_sources") {
"//ios/web/public/init",
"//ios/web/public/js_messaging",
"//ios/web/public/security",
"//ios/web/public/session:session",
"//ios/web/public/web_view_only",
"//ios/web/public/webui",
"//net",
Expand Down Expand Up @@ -419,6 +423,7 @@ test("ios_web_view_unittests") {
"internal/cwv_html_element_unittest.mm",
"internal/cwv_preferences_unittest.mm",
"internal/cwv_preview_element_info_unittest.mm",
"internal/cwv_ssl_error_handler_unittest.mm",
"internal/cwv_ssl_status_unittest.mm",
"internal/cwv_web_view_configuration_unittest.mm",
"internal/cwv_web_view_unittest.mm",
Expand Down
79 changes: 79 additions & 0 deletions ios/web_view/internal/cwv_ssl_error_handler.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/web_view/internal/cwv_ssl_error_handler_internal.h"

#include "base/strings/sys_string_conversions.h"
#import "ios/web/public/navigation/navigation_manager.h"
#include "ios/web/public/session/session_certificate_policy_cache.h"
#import "ios/web_view/internal/cwv_ssl_status_internal.h"
#import "ios/web_view/internal/cwv_ssl_util.h"

#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif

@implementation CWVSSLErrorHandler {
web::WebState* _webState;
net::SSLInfo _SSLInfo;
void (^_errorPageHTMLCallback)(NSString*);
BOOL _overridden;
}

- (instancetype)initWithWebState:(web::WebState*)webState
URL:(NSURL*)URL
error:(NSError*)error
SSLInfo:(net::SSLInfo)SSLInfo
errorPageHTMLCallback:(void (^)(NSString*))errorPageHTMLCallback {
self = [super init];
if (self) {
_webState = webState;
_URL = URL;
_error = error;
_SSLInfo = SSLInfo;
_errorPageHTMLCallback = errorPageHTMLCallback;
_overridden = NO;
}
return self;
}

#pragma mark - Public Methods

- (BOOL)overridable {
// This is counterintuitive, but is consistent with //ios/chrome.
// A fatal error is overridable, and a non-fatal error is not overridable.
return _SSLInfo.is_fatal_cert_error;
}

- (CWVCertStatus)certStatus {
return CWVCertStatusFromNetCertStatus(_SSLInfo.cert_status);
}

- (void)displayErrorPageWithHTML:(NSString*)HTML {
if (!_errorPageHTMLCallback) {
return;
}

_errorPageHTMLCallback(HTML);
_errorPageHTMLCallback = nil;
}

- (void)overrideErrorAndReloadPage {
if (!self.overridable) {
return;
}

// web::SessionCertificatePolicyCache is null for tests.
web::SessionCertificatePolicyCache* policyCache =
_webState->GetSessionCertificatePolicyCache();
if (policyCache) {
policyCache->RegisterAllowedCertificate(_SSLInfo.cert,
base::SysNSStringToUTF8(_URL.host),
_SSLInfo.cert_status);
}
_webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
/*check_for_repost=*/true);
}

@end
33 changes: 33 additions & 0 deletions ios/web_view/internal/cwv_ssl_error_handler_internal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef IOS_WEB_VIEW_INTERNAL_CWV_SSL_ERROR_HANDLER_INTERNAL_H_
#define IOS_WEB_VIEW_INTERNAL_CWV_SSL_ERROR_HANDLER_INTERNAL_H_

#include "ios/web_view/public/cwv_ssl_error_handler.h"

#import "ios/web/public/web_state.h"
#include "net/ssl/ssl_info.h"

NS_ASSUME_NONNULL_BEGIN

@interface CWVSSLErrorHandler ()

// Designated initializer.
// |URL| The URL associated with the SSL error.
// |error| The NSError object describing the error.
// |SSLInfo| Contains details of the SSL error.
// |errorPageHTMLCallback| Callback to be invoked to display an error page.
- (instancetype)initWithWebState:(web::WebState*)webState
URL:(NSURL*)URL
error:(NSError*)error
SSLInfo:(net::SSLInfo)SSLInfo
errorPageHTMLCallback:(void (^)(NSString*))errorPageHTMLCallback
NS_DESIGNATED_INITIALIZER;

@end

NS_ASSUME_NONNULL_END

#endif // IOS_WEB_VIEW_INTERNAL_CWV_SSL_ERROR_HANDLER_INTERNAL_H_
123 changes: 123 additions & 0 deletions ios/web_view/internal/cwv_ssl_error_handler_unittest.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/web_view/internal/cwv_ssl_error_handler_internal.h"

#include <memory>

#import "ios/web/public/test/fakes/fake_navigation_manager.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#include "net/cert/cert_status_flags.h"
#include "net/ssl/ssl_info.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/gtest_mac.h"
#include "testing/platform_test.h"

#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif

namespace ios_web_view {

class CWVSSLErrorHandlerTest : public PlatformTest {
protected:
CWVSSLErrorHandlerTest() {}

private:
DISALLOW_COPY_AND_ASSIGN(CWVSSLErrorHandlerTest);
};

TEST_F(CWVSSLErrorHandlerTest, Initialization) {
web::FakeWebState web_state;
NSURL* URL = [NSURL URLWithString:@"https://www.chromium.org"];
NSDictionary* user_info =
@{NSLocalizedDescriptionKey : @"This is an error description."};
NSError* error = [NSError errorWithDomain:@"TestDomain"
code:-1
userInfo:user_info];
net::SSLInfo ssl_info;
ssl_info.is_fatal_cert_error = true;
ssl_info.cert_status = net::CERT_STATUS_REVOKED;
CWVSSLErrorHandler* ssl_error_handler =
[[CWVSSLErrorHandler alloc] initWithWebState:&web_state
URL:URL
error:error
SSLInfo:ssl_info
errorPageHTMLCallback:^(NSString* HTML){
// No op.
}];
EXPECT_NSEQ(URL, ssl_error_handler.URL);
EXPECT_NSEQ(error, ssl_error_handler.error);
EXPECT_TRUE(ssl_error_handler.overridable);
EXPECT_EQ(CWVCertStatusRevoked, ssl_error_handler.certStatus);
}

TEST_F(CWVSSLErrorHandlerTest, DisplayHTML) {
web::FakeWebState web_state;
NSURL* URL = [NSURL URLWithString:@"https://www.chromium.org"];
NSError* error = [NSError errorWithDomain:@"TestDomain" code:-1 userInfo:nil];
net::SSLInfo ssl_info;
__block NSString* displayed_html = nil;
CWVSSLErrorHandler* ssl_error_handler =
[[CWVSSLErrorHandler alloc] initWithWebState:&web_state
URL:URL
error:error
SSLInfo:ssl_info
errorPageHTMLCallback:^(NSString* HTML) {
displayed_html = HTML;
}];

[ssl_error_handler displayErrorPageWithHTML:@"This is a test error page."];
EXPECT_NSEQ(@"This is a test error page.", displayed_html);
}

TEST_F(CWVSSLErrorHandlerTest, CanOverrideAndReload) {
web::FakeWebState web_state;
auto navigation_manager = std::make_unique<web::FakeNavigationManager>();
web::FakeNavigationManager* navigation_manager_ptr = navigation_manager.get();
web_state.SetNavigationManager(std::move(navigation_manager));
NSURL* URL = [NSURL URLWithString:@"https://www.chromium.org"];
NSError* error = [NSError errorWithDomain:@"TestDomain" code:-1 userInfo:nil];
net::SSLInfo ssl_info;
ssl_info.is_fatal_cert_error = true;
ssl_info.cert_status = net::CERT_STATUS_REVOKED;
CWVSSLErrorHandler* ssl_error_handler =
[[CWVSSLErrorHandler alloc] initWithWebState:&web_state
URL:URL
error:error
SSLInfo:ssl_info
errorPageHTMLCallback:^(NSString* HTML){
// No op.
}];

EXPECT_TRUE(ssl_error_handler.overridable);
[ssl_error_handler overrideErrorAndReloadPage];
EXPECT_TRUE(navigation_manager_ptr->ReloadWasCalled());
}

TEST_F(CWVSSLErrorHandlerTest, CannotOverrideAndReload) {
web::FakeWebState web_state;
auto navigation_manager = std::make_unique<web::FakeNavigationManager>();
web::FakeNavigationManager* navigation_manager_ptr = navigation_manager.get();
web_state.SetNavigationManager(std::move(navigation_manager));
NSURL* URL = [NSURL URLWithString:@"https://www.chromium.org"];
NSError* error = [NSError errorWithDomain:@"TestDomain" code:-1 userInfo:nil];
net::SSLInfo ssl_info;
ssl_info.is_fatal_cert_error = false;
ssl_info.cert_status = net::CERT_STATUS_REVOKED;
CWVSSLErrorHandler* ssl_error_handler =
[[CWVSSLErrorHandler alloc] initWithWebState:&web_state
URL:URL
error:error
SSLInfo:ssl_info
errorPageHTMLCallback:^(NSString* HTML){
// No op.
}];

EXPECT_FALSE(ssl_error_handler.overridable);
[ssl_error_handler overrideErrorAndReloadPage];
EXPECT_FALSE(navigation_manager_ptr->ReloadWasCalled());
}

} // namespace ios_web_view
8 changes: 8 additions & 0 deletions ios/web_view/internal/web_view_web_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ class WebViewWebClient : public web::WebClient {
std::u16string GetPluginNotSupportedText() const override;
bool IsLegacyTLSAllowedForHost(web::WebState* web_state,
const std::string& hostname) override;
void PrepareErrorPage(web::WebState* web_state,
const GURL& url,
NSError* error,
bool is_post,
bool is_off_the_record,
const base::Optional<net::SSLInfo>& info,
int64_t navigation_id,
base::OnceCallback<void(NSString*)> callback) override;
bool EnableLongPressAndForceTouchHandling() const override;

private:
Expand Down
36 changes: 36 additions & 0 deletions ios/web_view/internal/web_view_web_client.mm
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <dispatch/dispatch.h>

#include "base/bind.h"
#include "base/check.h"
#include "base/mac/bundle_locations.h"
#include "base/strings/sys_string_conversions.h"
Expand All @@ -18,6 +19,7 @@
#include "ios/web/public/security/ssl_status.h"
#include "ios/web/public/thread/web_task_traits.h"
#include "ios/web/public/thread/web_thread.h"
#import "ios/web_view/internal/cwv_ssl_error_handler_internal.h"
#import "ios/web_view/internal/cwv_ssl_status_internal.h"
#import "ios/web_view/internal/cwv_ssl_util.h"
#import "ios/web_view/internal/cwv_web_view_internal.h"
Expand All @@ -26,6 +28,7 @@
#import "ios/web_view/internal/web_view_web_main_parts.h"
#import "ios/web_view/public/cwv_navigation_delegate.h"
#import "ios/web_view/public/cwv_web_view.h"
#import "net/base/mac/url_conversions.h"
#include "net/cert/cert_status_flags.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
Expand Down Expand Up @@ -129,6 +132,39 @@
return true;
}

void WebViewWebClient::PrepareErrorPage(
web::WebState* web_state,
const GURL& url,
NSError* error,
bool is_post,
bool is_off_the_record,
const base::Optional<net::SSLInfo>& info,
int64_t navigation_id,
base::OnceCallback<void(NSString*)> callback) {
DCHECK(error);

// TODO(crbug.com/1191799): Add support for handling legacy TLS.
CWVWebView* web_view = [CWVWebView webViewForWebState:web_state];
if (info.has_value() &&
[web_view.navigationDelegate
respondsToSelector:@selector(webView:handleSSLErrorWithHandler:)]) {
__block base::OnceCallback<void(NSString*)> error_html_callback =
std::move(callback);
CWVSSLErrorHandler* handler =
[[CWVSSLErrorHandler alloc] initWithWebState:web_state
URL:net::NSURLWithGURL(url)
error:error
SSLInfo:info.value()
errorPageHTMLCallback:^(NSString* HTML) {
std::move(error_html_callback).Run(HTML);
}];
[web_view.navigationDelegate webView:web_view
handleSSLErrorWithHandler:handler];
} else {
std::move(callback).Run(error.localizedDescription);
}
}

bool WebViewWebClient::EnableLongPressAndForceTouchHandling() const {
return CWVWebView.chromeLongPressAndForceTouchHandlingEnabled;
}
Expand Down
3 changes: 3 additions & 0 deletions ios/web_view/public/cwv_defines.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,7 @@
// Allows customization of CWVWebView's user agent string.
#define IOS_WEB_VIEW_SUPPORTS_CWV_WEB_VIEW_CUSTOM_USER_AGENT 1

// Allows handling of SSL errors with CWVSSLErrorHandler.
#define IOS_WEB_VIEW_SUPPORTS_CWV_SSL_ERROR_HANDLER 1

#endif // IOS_WEB_VIEW_PUBLIC_CWV_DEFINES_H_
11 changes: 9 additions & 2 deletions ios/web_view/public/cwv_navigation_delegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ NS_ASSUME_NONNULL_BEGIN

@class CWVDownloadTask;
@class CWVSSLStatus;
@class CWVSSLErrorHandler;
@class CWVWebView;

// The decision to pass back to the decision handler from
Expand Down Expand Up @@ -62,11 +63,15 @@ FOUNDATION_EXPORT CWV_EXPORT NSErrorUserInfoKey CWVCertStatusKey;
- (void)webViewDidFinishNavigation:(CWVWebView*)webView;

// Notifies the delegate that page load has failed.
// When the page load has failed due to an SSL certification error,
// -webView:didFailNavigationWithSSLError:overridable:decisionHandler:
// is called instead of this method.
- (void)webView:(CWVWebView*)webView didFailNavigationWithError:(NSError*)error;

// Notifies the delegate that page load failed due to a SSL error.
// |handler| can be used to help with communicating the error to the user, and
// potentially override and ignore it.
- (void)webView:(CWVWebView*)webView
handleSSLErrorWithHandler:(CWVSSLErrorHandler*)handler;

// Notifies the delegate that the page load has failed due to an SSL error. If
// |overridable| is YES, the method can ignore the error and reload the page by
// calling |decisionHandler| with CWVSSLErrorDecisionOverrideErrorAndReload. The
Expand All @@ -81,6 +86,8 @@ FOUNDATION_EXPORT CWV_EXPORT NSErrorUserInfoKey CWVCertStatusKey;
// CWVSSLErrorDecisionOverrideErrorAndReload, it must not be called
// synchronously in the method. It breaks status management and causes an
// assertion failure. It must be called asynchronously to avoid it.
//
// Deprecated: Use |webView:handleSSLErrorWithHandler:| instead.
- (void)webView:(CWVWebView*)webView
didFailNavigationWithSSLError:(NSError*)error
overridable:(BOOL)overridable
Expand Down

0 comments on commit 6e9b2f5

Please sign in to comment.