b/2864818 Prompt the SSL error dialog to user and proceed or cancel

the request. C++ side cl: https://android-git.corp.google.com/g/#change,84529

Change-Id: I1f4c69c6ddb92324a1ec3c193018e8d703454f56
This commit is contained in:
Huahui Wu
2010-12-08 15:24:55 -08:00
parent 3be80f2e6c
commit ad053cebc8
5 changed files with 383 additions and 239 deletions

View File

@@ -28,6 +28,8 @@ import android.net.Uri;
import android.net.WebAddress;
import android.net.http.ErrorStrings;
import android.net.http.SslCertificate;
import android.net.http.SslError;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.provider.OpenableColumns;
@@ -44,11 +46,15 @@ import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.net.URLEncoder;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;
import org.apache.harmony.security.provider.cert.X509CertImpl;
class BrowserFrame extends Handler {
private static final String LOGTAG = "webkit";
@@ -1101,6 +1107,47 @@ class BrowserFrame extends Handler {
mCallbackProxy.onReceivedHttpAuthRequest(handler, host, realm);
}
/**
* Called by JNI when the native HTTP(S) stack gets a invalid cert chain.
*
* We delegate the request to CallbackProxy, and route its response to
* {@link #nativeSslCertErrorProceed(int)} or
* {@link #nativeSslCertErrorCancel(int, int)}.
*/
private void reportSslCertError(final int handle, final int cert_error, byte cert_der[]) {
final SslError ssl_error;
try {
X509Certificate cert = new X509CertImpl(cert_der);
ssl_error = new SslError(cert_error, cert);
} catch (IOException e) {
// Can't get the cert, not much to do.
Log.e(LOGTAG, "Can't get the certificate from WebKit, cancling");
nativeSslCertErrorCancel(handle, cert_error);
return;
}
SslErrorHandler handler = new SslErrorHandler() {
@Override
public void proceed() {
SslCertLookupTable.getInstance().Allow(ssl_error);
nativeSslCertErrorProceed(handle);
}
@Override
public void cancel() {
SslCertLookupTable.getInstance().Deny(ssl_error);
nativeSslCertErrorCancel(handle, cert_error);
}
};
if (SslCertLookupTable.getInstance().IsAllowed(ssl_error)) {
nativeSslCertErrorProceed(handle);
} else {
mCallbackProxy.onReceivedSslError(handler, ssl_error);
}
}
/**
* Called by JNI when the native HTTP stack needs to download a file.
*
@@ -1246,4 +1293,7 @@ class BrowserFrame extends Handler {
private native void nativeAuthenticationProceed(int handle, String username, String password);
private native void nativeAuthenticationCancel(int handle);
private native void nativeSslCertErrorProceed(int handle);
private native void nativeSslCertErrorCancel(int handle, int cert_error);
}

View File

@@ -73,7 +73,7 @@ class Network {
* SSL error handler: takes care of synchronization of multiple async
* loaders with SSL-related problems.
*/
private SslErrorHandler mSslErrorHandler;
private SslErrorHandlerImpl mSslErrorHandler;
/**
* HTTP authentication handler: takes care of synchronization of HTTP
@@ -157,7 +157,7 @@ class Network {
getName().equals(WebViewCore.THREAD_NAME));
}
mContext = context;
mSslErrorHandler = new SslErrorHandler();
mSslErrorHandler = new SslErrorHandlerImpl();
mHttpAuthHandler = new HttpAuthHandlerImpl(this);
mRequestQueue = new RequestQueue(context);

View File

@@ -0,0 +1,52 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.webkit;
import android.os.Bundle;
import android.net.http.SslError;
/*
* A simple class to store the wrong certificates that user is aware but
* chose to proceed.
*/
class SslCertLookupTable {
private static SslCertLookupTable sTable;
private final Bundle table;
public static synchronized SslCertLookupTable getInstance() {
if (sTable == null) {
sTable = new SslCertLookupTable();
}
return sTable;
}
private SslCertLookupTable() {
table = new Bundle();
}
public void Allow(SslError ssl_error) {
table.putBoolean(ssl_error.toString(), true);
}
public void Deny(SslError ssl_error) {
table.putBoolean(ssl_error.toString(), false);
}
public boolean IsAllowed(SslError ssl_error) {
return table.getBoolean(ssl_error.toString());
}
}

View File

@@ -16,258 +16,28 @@
package android.webkit;
import junit.framework.Assert;
import android.net.http.SslError;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import java.util.LinkedList;
import java.util.ListIterator;
/**
* SslErrorHandler: class responsible for handling SSL errors. This class is
* passed as a parameter to BrowserCallback.displaySslErrorDialog and is meant
* to receive the user's response.
* SslErrorHandler: class responsible for handling SSL errors.
* This class is passed as a parameter to BrowserCallback.displaySslErrorDialog
* and is meant to receive the user's response.
*/
public class SslErrorHandler extends Handler {
/* One problem here is that there may potentially be multiple SSL errors
* coming from mutiple loaders. Therefore, we keep a queue of loaders
* that have SSL-related problems and process errors one by one in the
* order they were received.
*/
private static final String LOGTAG = "network";
/**
* Queue of loaders that experience SSL-related problems.
* Package-private constructor needed for API compatibility.
*/
private LinkedList<LoadListener> mLoaderQueue;
/**
* SSL error preference table.
*/
private Bundle mSslPrefTable;
// These are only used in the client facing SslErrorHandler.
private final SslErrorHandler mOriginHandler;
private final LoadListener mLoadListener;
// Message id for handling the response
private static final int HANDLE_RESPONSE = 100;
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case HANDLE_RESPONSE:
LoadListener loader = (LoadListener) msg.obj;
synchronized (SslErrorHandler.this) {
handleSslErrorResponse(loader, loader.sslError(),
msg.arg1 == 1);
mLoaderQueue.remove(loader);
fastProcessQueuedSslErrors();
}
break;
}
}
/**
* Creates a new error handler with an empty loader queue.
*/
/* package */ SslErrorHandler() {
mLoaderQueue = new LinkedList<LoadListener>();
mSslPrefTable = new Bundle();
// These are used by client facing SslErrorHandlers.
mOriginHandler = null;
mLoadListener = null;
}
/**
* Create a new error handler that will be passed to the client.
*/
private SslErrorHandler(SslErrorHandler origin, LoadListener listener) {
mOriginHandler = origin;
mLoadListener = listener;
}
/**
* Saves this handler's state into a map.
* @return True iff succeeds.
*/
/* package */ synchronized boolean saveState(Bundle outState) {
boolean success = (outState != null);
if (success) {
// TODO?
outState.putBundle("ssl-error-handler", mSslPrefTable);
}
return success;
}
/**
* Restores this handler's state from a map.
* @return True iff succeeds.
*/
/* package */ synchronized boolean restoreState(Bundle inState) {
boolean success = (inState != null);
if (success) {
success = inState.containsKey("ssl-error-handler");
if (success) {
mSslPrefTable = inState.getBundle("ssl-error-handler");
}
}
return success;
}
/**
* Clears SSL error preference table.
*/
/* package */ synchronized void clear() {
mSslPrefTable.clear();
}
/**
* Handles SSL error(s) on the way up to the user.
*/
/* package */ synchronized void handleSslErrorRequest(LoadListener loader) {
if (DebugFlags.SSL_ERROR_HANDLER) {
Log.v(LOGTAG, "SslErrorHandler.handleSslErrorRequest(): " +
"url=" + loader.url());
}
if (!loader.cancelled()) {
mLoaderQueue.offer(loader);
if (loader == mLoaderQueue.peek()) {
fastProcessQueuedSslErrors();
}
}
}
/**
* Check the preference table for a ssl error that has already been shown
* to the user.
*/
/* package */ synchronized boolean checkSslPrefTable(LoadListener loader,
SslError error) {
final String host = loader.host();
final int primary = error.getPrimaryError();
if (DebugFlags.SSL_ERROR_HANDLER) {
Assert.assertTrue(host != null && primary != 0);
}
if (mSslPrefTable.containsKey(host)) {
if (primary <= mSslPrefTable.getInt(host)) {
handleSslErrorResponse(loader, error, true);
return true;
}
}
return false;
}
/**
* Processes queued SSL-error confirmation requests in
* a tight loop while there is no need to ask the user.
*/
/* package */void fastProcessQueuedSslErrors() {
while (processNextLoader());
}
/**
* Processes the next loader in the queue.
* @return True iff should proceed to processing the
* following loader in the queue
*/
private synchronized boolean processNextLoader() {
LoadListener loader = mLoaderQueue.peek();
if (loader != null) {
// if this loader has been cancelled
if (loader.cancelled()) {
// go to the following loader in the queue. Make sure this
// loader has been removed from the queue.
mLoaderQueue.remove(loader);
return true;
}
SslError error = loader.sslError();
if (DebugFlags.SSL_ERROR_HANDLER) {
Assert.assertNotNull(error);
}
// checkSslPrefTable will handle the ssl error response if the
// answer is available. It does not remove the loader from the
// queue.
if (checkSslPrefTable(loader, error)) {
mLoaderQueue.remove(loader);
return true;
}
// if we do not have information on record, ask
// the user (display a dialog)
CallbackProxy proxy = loader.getFrame().getCallbackProxy();
proxy.onReceivedSslError(new SslErrorHandler(this, loader), error);
}
// the queue must be empty, stop
return false;
}
SslErrorHandler() {}
/**
* Proceed with the SSL certificate.
*/
public void proceed() {
mOriginHandler.sendMessage(
mOriginHandler.obtainMessage(
HANDLE_RESPONSE, 1, 0, mLoadListener));
}
public void proceed() {}
/**
* Cancel this request and all pending requests for the WebView that had
* the error.
*/
public void cancel() {
mOriginHandler.sendMessage(
mOriginHandler.obtainMessage(
HANDLE_RESPONSE, 0, 0, mLoadListener));
}
/**
* Handles SSL error(s) on the way down from the user.
*/
/* package */ synchronized void handleSslErrorResponse(LoadListener loader,
SslError error, boolean proceed) {
if (DebugFlags.SSL_ERROR_HANDLER) {
Assert.assertNotNull(loader);
Assert.assertNotNull(error);
}
if (DebugFlags.SSL_ERROR_HANDLER) {
Log.v(LOGTAG, "SslErrorHandler.handleSslErrorResponse():"
+ " proceed: " + proceed
+ " url:" + loader.url());
}
if (!loader.cancelled()) {
if (proceed) {
// update the user's SSL error preference table
int primary = error.getPrimaryError();
String host = loader.host();
if (DebugFlags.SSL_ERROR_HANDLER) {
Assert.assertTrue(host != null && primary != 0);
}
boolean hasKey = mSslPrefTable.containsKey(host);
if (!hasKey ||
primary > mSslPrefTable.getInt(host)) {
mSslPrefTable.putInt(host, primary);
}
}
loader.handleSslErrorResponse(proceed);
}
}
public void cancel() {}
}

View File

@@ -0,0 +1,272 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.webkit;
import junit.framework.Assert;
import android.net.http.SslError;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import java.util.LinkedList;
import java.util.ListIterator;
/**
* SslErrorHandler's implementation for Android Java HTTP stack.
* This class is not needed if the Chromium HTTP stack is used.
*/
class SslErrorHandlerImpl extends SslErrorHandler {
/* One problem here is that there may potentially be multiple SSL errors
* coming from multiple loaders. Therefore, we keep a queue of loaders
* that have SSL-related problems and process errors one by one in the
* order they were received.
*/
private static final String LOGTAG = "network";
/**
* Queue of loaders that experience SSL-related problems.
*/
private LinkedList<LoadListener> mLoaderQueue;
/**
* SSL error preference table.
*/
private Bundle mSslPrefTable;
// These are only used in the client facing SslErrorHandler.
private final SslErrorHandler mOriginHandler;
private final LoadListener mLoadListener;
// Message id for handling the response
private static final int HANDLE_RESPONSE = 100;
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case HANDLE_RESPONSE:
LoadListener loader = (LoadListener) msg.obj;
synchronized (SslErrorHandlerImpl.this) {
handleSslErrorResponse(loader, loader.sslError(),
msg.arg1 == 1);
mLoaderQueue.remove(loader);
fastProcessQueuedSslErrors();
}
break;
}
}
/**
* Creates a new error handler with an empty loader queue.
*/
/* package */ SslErrorHandlerImpl() {
mLoaderQueue = new LinkedList<LoadListener>();
mSslPrefTable = new Bundle();
// These are used by client facing SslErrorHandlers.
mOriginHandler = null;
mLoadListener = null;
}
/**
* Create a new error handler that will be passed to the client.
*/
private SslErrorHandlerImpl(SslErrorHandler origin, LoadListener listener) {
mOriginHandler = origin;
mLoadListener = listener;
}
/**
* Saves this handler's state into a map.
* @return True iff succeeds.
*/
/* package */ synchronized boolean saveState(Bundle outState) {
boolean success = (outState != null);
if (success) {
// TODO?
outState.putBundle("ssl-error-handler", mSslPrefTable);
}
return success;
}
/**
* Restores this handler's state from a map.
* @return True iff succeeds.
*/
/* package */ synchronized boolean restoreState(Bundle inState) {
boolean success = (inState != null);
if (success) {
success = inState.containsKey("ssl-error-handler");
if (success) {
mSslPrefTable = inState.getBundle("ssl-error-handler");
}
}
return success;
}
/**
* Clears SSL error preference table.
*/
/* package */ synchronized void clear() {
mSslPrefTable.clear();
}
/**
* Handles SSL error(s) on the way up to the user.
*/
/* package */ synchronized void handleSslErrorRequest(LoadListener loader) {
if (DebugFlags.SSL_ERROR_HANDLER) {
Log.v(LOGTAG, "SslErrorHandler.handleSslErrorRequest(): " +
"url=" + loader.url());
}
if (!loader.cancelled()) {
mLoaderQueue.offer(loader);
if (loader == mLoaderQueue.peek()) {
fastProcessQueuedSslErrors();
}
}
}
/**
* Check the preference table for a ssl error that has already been shown
* to the user.
*/
/* package */ synchronized boolean checkSslPrefTable(LoadListener loader,
SslError error) {
final String host = loader.host();
final int primary = error.getPrimaryError();
if (DebugFlags.SSL_ERROR_HANDLER) {
Assert.assertTrue(host != null && primary != 0);
}
if (mSslPrefTable.containsKey(host)) {
if (primary <= mSslPrefTable.getInt(host)) {
handleSslErrorResponse(loader, error, true);
return true;
}
}
return false;
}
/**
* Processes queued SSL-error confirmation requests in
* a tight loop while there is no need to ask the user.
*/
/* package */void fastProcessQueuedSslErrors() {
while (processNextLoader());
}
/**
* Processes the next loader in the queue.
* @return True iff should proceed to processing the
* following loader in the queue
*/
private synchronized boolean processNextLoader() {
LoadListener loader = mLoaderQueue.peek();
if (loader != null) {
// if this loader has been cancelled
if (loader.cancelled()) {
// go to the following loader in the queue. Make sure this
// loader has been removed from the queue.
mLoaderQueue.remove(loader);
return true;
}
SslError error = loader.sslError();
if (DebugFlags.SSL_ERROR_HANDLER) {
Assert.assertNotNull(error);
}
// checkSslPrefTable will handle the ssl error response if the
// answer is available. It does not remove the loader from the
// queue.
if (checkSslPrefTable(loader, error)) {
mLoaderQueue.remove(loader);
return true;
}
// if we do not have information on record, ask
// the user (display a dialog)
CallbackProxy proxy = loader.getFrame().getCallbackProxy();
proxy.onReceivedSslError(new SslErrorHandlerImpl(this, loader), error);
}
// the queue must be empty, stop
return false;
}
/**
* Proceed with the SSL certificate.
*/
public void proceed() {
mOriginHandler.sendMessage(
mOriginHandler.obtainMessage(
HANDLE_RESPONSE, 1, 0, mLoadListener));
}
/**
* Cancel this request and all pending requests for the WebView that had
* the error.
*/
public void cancel() {
mOriginHandler.sendMessage(
mOriginHandler.obtainMessage(
HANDLE_RESPONSE, 0, 0, mLoadListener));
}
/**
* Handles SSL error(s) on the way down from the user.
*/
/* package */ synchronized void handleSslErrorResponse(LoadListener loader,
SslError error, boolean proceed) {
if (DebugFlags.SSL_ERROR_HANDLER) {
Assert.assertNotNull(loader);
Assert.assertNotNull(error);
}
if (DebugFlags.SSL_ERROR_HANDLER) {
Log.v(LOGTAG, "SslErrorHandler.handleSslErrorResponse():"
+ " proceed: " + proceed
+ " url:" + loader.url());
}
if (!loader.cancelled()) {
if (proceed) {
// update the user's SSL error preference table
int primary = error.getPrimaryError();
String host = loader.host();
if (DebugFlags.SSL_ERROR_HANDLER) {
Assert.assertTrue(host != null && primary != 0);
}
boolean hasKey = mSslPrefTable.containsKey(host);
if (!hasKey ||
primary > mSslPrefTable.getInt(host)) {
mSslPrefTable.putInt(host, primary);
}
}
loader.handleSslErrorResponse(proceed);
}
}
}