Add CTS tests for client cert error handling
Bug 11375270
Change-Id: Ia8a18916797db0ffe76e06e4d1893b0a492ecdc6
diff --git a/libs/testserver/src/android/webkit/cts/CtsTestServer.java b/libs/testserver/src/android/webkit/cts/CtsTestServer.java
index e4ac46e..5112d7f5 100644
--- a/libs/testserver/src/android/webkit/cts/CtsTestServer.java
+++ b/libs/testserver/src/android/webkit/cts/CtsTestServer.java
@@ -84,6 +84,7 @@
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLSession;
import javax.net.ssl.X509TrustManager;
@@ -124,6 +125,13 @@
public static final String MESSAGE_403 = "403 forbidden";
public static final String MESSAGE_404 = "404 not found";
+ public enum SslMode {
+ INSECURE,
+ NO_CLIENT_AUTH,
+ WANTS_CLIENT_AUTH,
+ NEEDS_CLIENT_AUTH,
+ }
+
private static Hashtable<Integer, String> sReasons;
private ServerThread mServerThread;
@@ -131,7 +139,7 @@
private AssetManager mAssets;
private Context mContext;
private Resources mResources;
- private boolean mSsl;
+ private SslMode mSsl;
private MimeTypeMap mMap;
private Vector<String> mQueries;
private ArrayList<HttpEntity> mRequestEntities;
@@ -166,19 +174,30 @@
* @throws Exception
*/
public CtsTestServer(Context context, boolean ssl) throws Exception {
+ this(context, ssl ? SslMode.NO_CLIENT_AUTH : SslMode.INSECURE);
+ }
+
+ /**
+ * Create and start a local HTTP server instance.
+ * @param context The application context to use for fetching assets.
+ * @param sslMode Whether to use SSL, and if so, what client auth (if any) to use.
+ * @throws Exception
+ */
+ public CtsTestServer(Context context, SslMode sslMode) throws Exception {
mContext = context;
mAssets = mContext.getAssets();
mResources = mContext.getResources();
- mSsl = ssl;
+ mSsl = sslMode;
mRequestEntities = new ArrayList<HttpEntity>();
mMap = MimeTypeMap.getSingleton();
mQueries = new Vector<String>();
mServerThread = new ServerThread(this, mSsl);
- if (mSsl) {
- mServerUri = "https://localhost:" + mServerThread.mSocket.getLocalPort();
+ if (mSsl == SslMode.INSECURE) {
+ mServerUri = "http:";
} else {
- mServerUri = "http://localhost:" + mServerThread.mSocket.getLocalPort();
+ mServerUri = "https:";
}
+ mServerUri += "//localhost:" + mServerThread.mSocket.getLocalPort();
mServerThread.start();
}
@@ -217,7 +236,9 @@
private URLConnection openConnection(URL url)
throws IOException, NoSuchAlgorithmException, KeyManagementException {
- if (mSsl) {
+ if (mSsl == SslMode.INSECURE) {
+ return url.openConnection();
+ } else {
// Install hostname verifiers and trust managers that don't do
// anything in order to get around the client not trusting
// the test server due to a lack of certificates.
@@ -226,13 +247,14 @@
connection.setHostnameVerifier(new CtsHostnameVerifier());
SSLContext context = SSLContext.getInstance("TLS");
- CtsTrustManager trustManager = new CtsTrustManager();
- context.init(null, new CtsTrustManager[] {trustManager}, null);
+ try {
+ context.init(ServerThread.getKeyManagers(), getTrustManagers(), null);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
connection.setSSLSocketFactory(context.getSocketFactory());
return connection;
- } else {
- return url.openConnection();
}
}
@@ -244,7 +266,7 @@
*/
private static class CtsTrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType) {
- // Trust the CtSTestServer...
+ // Trust the CtSTestServer's client...
}
public void checkServerTrusted(X509Certificate[] chain, String authType) {
@@ -257,6 +279,13 @@
}
/**
+ * @returns a trust manager array configured to permit any trust decision.
+ */
+ private static CtsTrustManager[] getTrustManagers() {
+ return new CtsTrustManager[] { new CtsTrustManager() };
+ }
+
+ /**
* {@link HostnameVerifier} that verifies everybody. This permits
* the client to trust the web server and call
* {@link CtsTestServer#shutdown()}.
@@ -767,7 +796,7 @@
private static class ServerThread extends Thread {
private CtsTestServer mServer;
private ServerSocket mSocket;
- private boolean mIsSsl;
+ private SslMode mSsl;
private boolean mIsCancelled;
private SSLContext mSslContext;
private ExecutorService mExecutorService = Executors.newFixedThreadPool(20);
@@ -802,13 +831,13 @@
"1gaEjsC/0wGmmBDg1dTDH+F1p9TInzr3EFuYD0YiQ7YlAHq3cPuyGoLXJ5dXYuSBfhDXJSeddUkl" +
"k1ufZyOOcskeInQge7jzaRfmKg3U94r+spMEvb0AzDQVOKvjjo1ivxMSgFRZaDb/4qw=";
- private String PASSWORD = "android";
+ private static final String PASSWORD = "android";
/**
* Loads a keystore from a base64-encoded String. Returns the KeyManager[]
* for the result.
*/
- private KeyManager[] getKeyManagers() throws Exception {
+ private static KeyManager[] getKeyManagers() throws Exception {
byte[] bytes = Base64.decode(SERVER_KEYS_BKS.getBytes());
InputStream inputStream = new ByteArrayInputStream(bytes);
@@ -824,19 +853,24 @@
}
- public ServerThread(CtsTestServer server, boolean ssl) throws Exception {
+ public ServerThread(CtsTestServer server, SslMode sslMode) throws Exception {
super("ServerThread");
mServer = server;
- mIsSsl = ssl;
+ mSsl = sslMode;
int retry = 3;
while (true) {
try {
- if (mIsSsl) {
- mSslContext = SSLContext.getInstance("TLS");
- mSslContext.init(getKeyManagers(), null, null);
- mSocket = mSslContext.getServerSocketFactory().createServerSocket(0);
- } else {
+ if (mSsl == SslMode.INSECURE) {
mSocket = new ServerSocket(0);
+ } else { // Use SSL
+ mSslContext = SSLContext.getInstance("TLS");
+ mSslContext.init(getKeyManagers(), getTrustManagers(), null);
+ mSocket = mSslContext.getServerSocketFactory().createServerSocket(0);
+ if (mSsl == SslMode.WANTS_CLIENT_AUTH) {
+ ((SSLServerSocket) mSocket).setWantClientAuth(true);
+ } else if (mSsl == SslMode.NEEDS_CLIENT_AUTH) {
+ ((SSLServerSocket) mSocket).setNeedClientAuth(true);
+ }
}
return;
} catch (IOException e) {
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
index 72e35b8..57bfbc3 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
@@ -142,7 +142,7 @@
protected void tearDown() throws Exception {
mOnUiThread.cleanUp();
if (mWebServer != null) {
- mWebServer.shutdown();
+ stopWebServer();
}
if (mIconDb != null) {
mIconDb.removeAllIcons();
@@ -1781,6 +1781,35 @@
assertEquals("Second page", mOnUiThread.getTitle());
}
+ public void testSecureServerRequestingClientCertDoesNotCancelRequest() throws Throwable {
+ mWebServer = new CtsTestServer(getActivity(), CtsTestServer.SslMode.WANTS_CLIENT_AUTH);
+ final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
+ final SslErrorWebViewClient webViewClient = new SslErrorWebViewClient();
+ mOnUiThread.setWebViewClient(webViewClient);
+ mOnUiThread.clearSslPreferences();
+ mOnUiThread.loadUrlAndWaitForCompletion(url);
+ // Page loaded OK...
+ assertTrue(webViewClient.wasOnReceivedSslErrorCalled());
+ assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
+ assertEquals(0, webViewClient.onReceivedErrorCode());
+ }
+
+ public void testSecureServerRequiringClientCertDoesCancelRequest() throws Throwable {
+ mWebServer = new CtsTestServer(getActivity(), CtsTestServer.SslMode.NEEDS_CLIENT_AUTH);
+ final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
+ final SslErrorWebViewClient webViewClient = new SslErrorWebViewClient();
+ mOnUiThread.setWebViewClient(webViewClient);
+ mOnUiThread.clearSslPreferences();
+ mOnUiThread.loadUrlAndWaitForCompletion(url);
+ // Page NOT loaded OK...
+ // In this case, we must NOT have received the onReceivedSslError callback as that is for
+ // recoverable (e.g. server auth) errors, whereas failing mandatory client auth is non-
+ // recoverable and should drop straight through to a load error.
+ assertFalse(webViewClient.wasOnReceivedSslErrorCalled());
+ assertFalse(TestHtmlConstants.HELLO_WORLD_TITLE.equals(mOnUiThread.getTitle()));
+ assertEquals(WebViewClient.ERROR_FAILED_SSL_HANDSHAKE, webViewClient.onReceivedErrorCode());
+ }
+
public void testRequestChildRectangleOnScreen() throws Throwable {
DisplayMetrics metrics = mOnUiThread.getDisplayMetrics();
final int dimension = 2 * Math.max(metrics.widthPixels, metrics.heightPixels);
@@ -2277,6 +2306,7 @@
final class SslErrorWebViewClient extends WaitForLoadedClient {
private boolean mWasOnReceivedSslErrorCalled;
private String mErrorUrl;
+ private int mErrorCode;
public SslErrorWebViewClient() {
super(mOnUiThread);
@@ -2287,6 +2317,11 @@
mErrorUrl = error.getUrl();
handler.proceed();
}
+ @Override
+ public void onReceivedError(WebView view, int errorCode, String description,
+ String failingUrl) {
+ mErrorCode = errorCode;
+ }
public void resetWasOnReceivedSslErrorCalled() {
mWasOnReceivedSslErrorCalled = false;
}
@@ -2296,6 +2331,9 @@
public String errorUrl() {
return mErrorUrl;
}
+ public int onReceivedErrorCode() {
+ return mErrorCode;
+ }
}
final class ScaleChangedWebViewClient extends WaitForLoadedClient {