Commit 8e5bcae5 authored by hush's avatar hush Committed by Commit bot

Remember user's decisions on SSL errors.

After CL: https://codereview.chromium.org/369703002, Android
WebView just has a NULL SSLHostStateDelegate. So it does not remember
any user decisions on SSL errors. This is a regression of behavior from
L (m37).

History:
JB behavior: Larger error codes are assumed to have higher severity. And
if the user has allowed an SSL error with a high severity, the user
won't be prompted for a lower severity SSL error.

K and L behavior: A specific SSL error will be allowed only if the error
bit field is a subset of previously allowed error codes.

trunk behavior for webview (without this patch): We don't remember
user's decision at all.

This CL:
Maintain the same behavior with K and L.

BUG=441065

Review URL: https://codereview.chromium.org/794023002

Cr-Commit-Position: refs/heads/master@{#308160}
parent 20355072
......@@ -164,6 +164,8 @@
'browser/aw_request_interceptor.h',
'browser/aw_resource_context.cc',
'browser/aw_resource_context.h',
'browser/aw_ssl_host_state_delegate.cc',
'browser/aw_ssl_host_state_delegate.h',
'browser/aw_result_codes.h',
'browser/aw_web_preferences_populater.cc',
'browser/aw_web_preferences_populater.h',
......
......@@ -347,7 +347,10 @@ content::PushMessagingService* AwBrowserContext::GetPushMessagingService() {
}
content::SSLHostStateDelegate* AwBrowserContext::GetSSLHostStateDelegate() {
return NULL;
if (!ssl_host_state_delegate_.get()) {
ssl_host_state_delegate_.reset(new AwSSLHostStateDelegate());
}
return ssl_host_state_delegate_.get();
}
void AwBrowserContext::RebuildTable(
......
......@@ -8,6 +8,7 @@
#include <vector>
#include "android_webview/browser/aw_download_manager_delegate.h"
#include "android_webview/browser/aw_ssl_host_state_delegate.h"
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/files/file_path.h"
......@@ -155,6 +156,7 @@ class AwBrowserContext : public content::BrowserContext,
data_reduction_proxy_settings_;
scoped_ptr<data_reduction_proxy::DataReductionProxyEventStore>
data_reduction_proxy_event_store_;
scoped_ptr<AwSSLHostStateDelegate> ssl_host_state_delegate_;
DISALLOW_COPY_AND_ASSIGN(AwBrowserContext);
};
......
// Copyright (c) 2014 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.
#include "android_webview/browser/aw_ssl_host_state_delegate.h"
#include "net/base/hash_value.h"
using content::SSLHostStateDelegate;
namespace android_webview {
namespace internal {
net::SHA256HashValue getChainFingerprint256(const net::X509Certificate& cert) {
net::SHA256HashValue fingerprint =
net::X509Certificate::CalculateChainFingerprint256(
cert.os_cert_handle(), cert.GetIntermediateCertificates());
return fingerprint;
}
CertPolicy::CertPolicy() {
}
CertPolicy::~CertPolicy() {
}
// For an allowance, we consider a given |cert| to be a match to a saved
// allowed cert if the |error| is an exact match to or subset of the errors
// in the saved CertStatus.
bool CertPolicy::Check(const net::X509Certificate& cert,
net::CertStatus error) const {
net::SHA256HashValue fingerprint = getChainFingerprint256(cert);
std::map<net::SHA256HashValue, net::CertStatus,
net::SHA256HashValueLessThan>::const_iterator allowed_iter =
allowed_.find(fingerprint);
if ((allowed_iter != allowed_.end()) && (allowed_iter->second & error) &&
((allowed_iter->second & error) == error)) {
return true;
}
return false;
}
void CertPolicy::Allow(const net::X509Certificate& cert,
net::CertStatus error) {
// If this same cert had already been saved with a different error status,
// this will replace it with the new error status.
net::SHA256HashValue fingerprint = getChainFingerprint256(cert);
allowed_[fingerprint] = error;
}
} // namespace internal
AwSSLHostStateDelegate::AwSSLHostStateDelegate() {
}
AwSSLHostStateDelegate::~AwSSLHostStateDelegate() {
}
void AwSSLHostStateDelegate::HostRanInsecureContent(const std::string& host,
int pid) {
// Intentional no-op for Android WebView.
}
bool AwSSLHostStateDelegate::DidHostRunInsecureContent(const std::string& host,
int pid) const {
// Intentional no-op for Android WebView.
return false;
}
void AwSSLHostStateDelegate::AllowCert(const std::string& host,
const net::X509Certificate& cert,
net::CertStatus error) {
cert_policy_for_host_[host].Allow(cert, error);
}
void AwSSLHostStateDelegate::Clear() {
cert_policy_for_host_.clear();
}
SSLHostStateDelegate::CertJudgment AwSSLHostStateDelegate::QueryPolicy(
const std::string& host,
const net::X509Certificate& cert,
net::CertStatus error,
bool* expired_previous_decision) {
return cert_policy_for_host_[host].Check(cert, error)
? SSLHostStateDelegate::ALLOWED
: SSLHostStateDelegate::DENIED;
}
} // namespace android_webview
// Copyright (c) 2014 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 ANDROID_WEBVIEW_BROWSER_AW_SSL_HOST_STATE_DELEGATE_H_
#define ANDROID_WEBVIEW_BROWSER_AW_SSL_HOST_STATE_DELEGATE_H_
#include <map>
#include <string>
#include "content/public/browser/ssl_host_state_delegate.h"
#include "net/base/hash_value.h"
#include "net/cert/cert_status_flags.h"
#include "net/cert/x509_certificate.h"
namespace android_webview {
namespace internal {
// This class maintains the policy for storing actions on certificate errors.
class CertPolicy {
public:
CertPolicy();
~CertPolicy();
// Returns true if the user has decided to proceed through the ssl error
// before. For a certificate to be allowed, it must not have any
// *additional* errors from when it was allowed.
bool Check(const net::X509Certificate& cert, net::CertStatus error) const;
// Causes the policy to allow this certificate for a given |error|. And
// remember the user's choice.
void Allow(const net::X509Certificate& cert, net::CertStatus error);
private:
// The set of fingerprints of allowed certificates.
std::map<net::SHA256HashValue, net::CertStatus, net::SHA256HashValueLessThan>
allowed_;
};
} // namespace internal
class AwSSLHostStateDelegate : public content::SSLHostStateDelegate {
public:
AwSSLHostStateDelegate();
virtual ~AwSSLHostStateDelegate();
// Records that |cert| is permitted to be used for |host| in the future, for
// a specified |error| type.
void AllowCert(const std::string& host,
const net::X509Certificate& cert,
net::CertStatus error) override;
void Clear() override;
// Queries whether |cert| is allowed or denied for |host| and |error|.
content::SSLHostStateDelegate::CertJudgment QueryPolicy(
const std::string& host,
const net::X509Certificate& cert,
net::CertStatus error,
bool* expired_previous_decision) override;
// Records that a host has run insecure content.
void HostRanInsecureContent(const std::string& host, int pid) override;
// Returns whether the specified host ran insecure content.
bool DidHostRunInsecureContent(const std::string& host,
int pid) const override;
private:
// Certificate policies for each host.
std::map<std::string, internal::CertPolicy> cert_policy_for_host_;
DISALLOW_COPY_AND_ASSIGN(AwSSLHostStateDelegate);
};
} // namespace android_webview
#endif // ANDROID_WEBVIEW_BROWSER_AW_SSL_HOST_STATE_DELEGATE_H_
......@@ -500,7 +500,7 @@ public class AwContentsTest extends AwTestBase {
}
}
// This is a meta test that we don't accidentally turn of hardware
// This is a meta test that we don't accidentally turn off hardware
// acceleration in instrumentation tests without notice. Do not add the
// @DisableHardwareAccelerationForTest annotation for this test.
@Feature({"AndroidWebView"})
......@@ -511,4 +511,55 @@ public class AwContentsTest extends AwTestBase {
assertTrue(testContainer.isHardwareAccelerated());
assertTrue(testContainer.isBackedByHardwareView());
}
// TODO(hush): more ssl tests. And put the ssl tests into a separate test
// class.
@Feature({"AndroidWebView"})
@SmallTest
// If the user allows the ssl error, the same ssl error will not trigger
// the onReceivedSslError callback; If the user denies it, the same ssl
// error will still trigger the onReceivedSslError callback.
public void testSslPreferences() throws Throwable {
final AwTestContainerView testContainer =
createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testContainer.getAwContents();
TestWebServer webServer = TestWebServer.startSsl();
final String pagePath = "/hello.html";
final String pageUrl =
webServer.setResponse(pagePath, "<html><body>hello world</body></html>", null);
final CallbackHelper onReceivedSslErrorHelper =
mContentsClient.getOnReceivedSslErrorHelper();
int onSslErrorCallCount = onReceivedSslErrorHelper.getCallCount();
loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
assertEquals(onSslErrorCallCount + 1, onReceivedSslErrorHelper.getCallCount());
assertEquals(1, webServer.getRequestCount(pagePath));
// Now load the page again. This time, we expect no ssl error, because
// user's decision should be remembered.
onSslErrorCallCount = onReceivedSslErrorHelper.getCallCount();
loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
assertEquals(onSslErrorCallCount, onReceivedSslErrorHelper.getCallCount());
// Now clear the ssl preferences then load the same url again. Expect to see
// onReceivedSslError getting called again.
awContents.clearSslPreferences();
onSslErrorCallCount = onReceivedSslErrorHelper.getCallCount();
loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
assertEquals(onSslErrorCallCount + 1, onReceivedSslErrorHelper.getCallCount());
// Now clear the stored decisions and tell the client to deny ssl errors.
awContents.clearSslPreferences();
mContentsClient.setAllowSslError(false);
onSslErrorCallCount = onReceivedSslErrorHelper.getCallCount();
loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
assertEquals(onSslErrorCallCount + 1, onReceivedSslErrorHelper.getCallCount());
// Now load the same page again. This time, we still expect onReceivedSslError,
// because we only remember user's decision if it is "allow".
onSslErrorCallCount = onReceivedSslErrorHelper.getCallCount();
loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
assertEquals(onSslErrorCallCount + 1, onReceivedSslErrorHelper.getCallCount());
}
}
......@@ -5,7 +5,9 @@
package org.chromium.android_webview.test;
import android.graphics.Picture;
import android.net.http.SslError;
import android.webkit.ConsoleMessage;
import android.webkit.ValueCallback;
import org.chromium.base.ThreadUtils;
import org.chromium.content.browser.test.util.CallbackHelper;
......@@ -19,9 +21,11 @@ import org.chromium.content.browser.test.util.TestCallbackHelperContainer.OnRece
*/
public class TestAwContentsClient extends NullContentsClient {
private String mUpdatedTitle;
private boolean mAllowSslError;
private final OnPageStartedHelper mOnPageStartedHelper;
private final OnPageFinishedHelper mOnPageFinishedHelper;
private final OnReceivedErrorHelper mOnReceivedErrorHelper;
private final CallbackHelper mOnReceivedSslErrorHelper;
private final OnDownloadStartHelper mOnDownloadStartHelper;
private final OnReceivedLoginRequestHelper mOnReceivedLoginRequestHelper;
private final OnEvaluateJavaScriptResultHelper mOnEvaluateJavaScriptResultHelper;
......@@ -37,6 +41,7 @@ public class TestAwContentsClient extends NullContentsClient {
mOnPageStartedHelper = new OnPageStartedHelper();
mOnPageFinishedHelper = new OnPageFinishedHelper();
mOnReceivedErrorHelper = new OnReceivedErrorHelper();
mOnReceivedSslErrorHelper = new CallbackHelper();
mOnDownloadStartHelper = new OnDownloadStartHelper();
mOnReceivedLoginRequestHelper = new OnReceivedLoginRequestHelper();
mOnEvaluateJavaScriptResultHelper = new OnEvaluateJavaScriptResultHelper();
......@@ -46,6 +51,7 @@ public class TestAwContentsClient extends NullContentsClient {
mShouldOverrideUrlLoadingHelper = new ShouldOverrideUrlLoadingHelper();
mDoUpdateVisitedHistoryHelper = new DoUpdateVisitedHistoryHelper();
mOnCreateWindowHelper = new OnCreateWindowHelper();
mAllowSslError = true;
}
public OnPageStartedHelper getOnPageStartedHelper() {
......@@ -60,6 +66,10 @@ public class TestAwContentsClient extends NullContentsClient {
return mOnReceivedErrorHelper;
}
public CallbackHelper getOnReceivedSslErrorHelper() {
return mOnReceivedSslErrorHelper;
}
public OnDownloadStartHelper getOnDownloadStartHelper() {
return mOnDownloadStartHelper;
}
......@@ -146,6 +156,16 @@ public class TestAwContentsClient extends NullContentsClient {
mOnReceivedErrorHelper.notifyCalled(errorCode, description, failingUrl);
}
@Override
public void onReceivedSslError(ValueCallback<Boolean> callback, SslError error) {
callback.onReceiveValue(mAllowSslError);
mOnReceivedSslErrorHelper.notifyCalled();
}
public void setAllowSslError(boolean allow) {
mAllowSslError = allow;
}
/**
* CallbackHelper for OnDownloadStart.
*/
......@@ -282,7 +302,7 @@ public class TestAwContentsClient extends NullContentsClient {
}
/**
* Callback helper for onScaleChangedScaled.
* Callback helper for AddMessageToConsole.
*/
public static class AddMessageToConsoleHelper extends CallbackHelper {
private int mLevel;
......@@ -325,7 +345,7 @@ public class TestAwContentsClient extends NullContentsClient {
}
/**
* Callback helper for onScaleChangedScaled.
* Callback helper for PictureListener.
*/
public static class PictureListenerHelper extends CallbackHelper {
// Generally null, depending on |invalidationOnly| in enableOnNewPicture()
......@@ -348,7 +368,7 @@ public class TestAwContentsClient extends NullContentsClient {
}
/**
* Callback helper for onScaleChangedScaled.
* Callback helper for ShouldOverrideUrlLoading.
*/
public static class ShouldOverrideUrlLoadingHelper extends CallbackHelper {
private String mShouldOverrideUrlLoadingUrl;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment