blob: 2422ccfe8dbac72fc8278f3d86c4f2dc34a97efa [file] [log] [blame]
/*
* Copyright (C) 2011 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 com.android.keychain.tests;
import android.app.Activity;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.StrictMode;
import android.security.KeyChain;
import android.security.KeyChainAliasCallback;
import android.security.KeyChainException;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.widget.TextView;
import java.net.Socket;
import java.net.URL;
import java.security.KeyStore;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509TrustManager;
import libcore.java.security.TestKeyStore;
import libcore.javax.net.ssl.TestSSLContext;
import com.google.mockwebserver.MockResponse;
import com.google.mockwebserver.MockWebServer;
/**
* Simple activity based test that exercises the KeyChain API
*/
public class KeyChainTestActivity extends Activity {
private static final String TAG = "KeyChainTestActivity";
private static final int REQUEST_CA_INSTALL = 1;
private TextView mTextView;
private TestKeyStore mTestKeyStore;
private final Object mAliasLock = new Object();
private String mAlias;
private final Object mGrantedLock = new Object();
private boolean mGranted;
private void log(final String message) {
Log.d(TAG, message);
runOnUiThread(new Runnable() {
@Override public void run() {
mTextView.append(message + "\n");
}
});
}
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectAll()
.penaltyLog()
.penaltyDeath()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath()
.build());
mTextView = new TextView(this);
mTextView.setMovementMethod(new ScrollingMovementMethod());
setContentView(mTextView);
log("Starting test...");
testKeyChainImproperUse();
new SetupTestKeyStore().execute();
}
private void testKeyChainImproperUse() {
try {
KeyChain.getPrivateKey(null, null);
throw new AssertionError();
} catch (InterruptedException e) {
throw new AssertionError(e);
} catch (KeyChainException e) {
throw new AssertionError(e);
} catch (NullPointerException expected) {
log("KeyChain failed as expected with null argument.");
}
try {
KeyChain.getPrivateKey(this, null);
throw new AssertionError();
} catch (InterruptedException e) {
throw new AssertionError(e);
} catch (KeyChainException e) {
throw new AssertionError(e);
} catch (NullPointerException expected) {
log("KeyChain failed as expected with null argument.");
}
try {
KeyChain.getPrivateKey(null, "");
throw new AssertionError();
} catch (InterruptedException e) {
throw new AssertionError(e);
} catch (KeyChainException e) {
throw new AssertionError(e);
} catch (NullPointerException expected) {
log("KeyChain failed as expected with null argument.");
}
try {
KeyChain.getPrivateKey(this, "");
throw new AssertionError();
} catch (InterruptedException e) {
throw new AssertionError(e);
} catch (KeyChainException e) {
throw new AssertionError(e);
} catch (IllegalStateException expected) {
log("KeyChain failed as expected on main thread.");
}
}
private class SetupTestKeyStore extends AsyncTask<Void, Void, Void> {
@Override protected Void doInBackground(Void... params) {
mTestKeyStore = TestKeyStore.getServer();
return null;
}
@Override protected void onPostExecute(Void result) {
testCaInstall();
}
}
private void testCaInstall() {
try {
log("Requesting install of server's CA...");
X509Certificate ca = mTestKeyStore.getRootCertificate("RSA");
Intent intent = KeyChain.createInstallIntent();
intent.putExtra(KeyChain.EXTRA_NAME, TAG);
intent.putExtra(KeyChain.EXTRA_CERTIFICATE, ca.getEncoded());
startActivityForResult(intent, REQUEST_CA_INSTALL);
} catch (Exception e) {
throw new AssertionError(e);
}
}
private class TestHttpsRequest extends AsyncTask<Void, Void, Void> {
@Override protected Void doInBackground(Void... params) {
try {
log("Starting web server...");
URL url = startWebServer();
log("Making https request to " + url);
makeHttpsRequest(url);
log("Tests succeeded.");
return null;
} catch (Exception e) {
throw new AssertionError(e);
}
}
private URL startWebServer() throws Exception {
KeyStore serverKeyStore = mTestKeyStore.keyStore;
char[] serverKeyStorePassword = mTestKeyStore.storePassword;
String kmfAlgoritm = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfAlgoritm);
kmf.init(serverKeyStore, serverKeyStorePassword);
SSLContext serverContext = SSLContext.getInstance("SSL");
serverContext.init(kmf.getKeyManagers(),
new TrustManager[] { new TrustAllTrustManager() },
null);
SSLSocketFactory sf = serverContext.getSocketFactory();
SSLSocketFactory needClientAuth = TestSSLContext.clientAuth(sf, false, true);
MockWebServer server = new MockWebServer();
server.useHttps(needClientAuth, false);
server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
server.play();
return server.getUrl("/");
}
private void makeHttpsRequest(URL url) throws Exception {
SSLContext clientContext = SSLContext.getInstance("SSL");
clientContext.init(new KeyManager[] { new KeyChainKeyManager() }, null, null);
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setSSLSocketFactory(clientContext.getSocketFactory());
if (connection.getResponseCode() != 200) {
throw new AssertionError();
}
}
}
private class KeyChainKeyManager extends X509ExtendedKeyManager {
@Override public String chooseClientAlias(String[] keyTypes,
Principal[] issuers,
Socket socket) {
log("KeyChainKeyManager chooseClientAlias...");
KeyChain.choosePrivateKeyAlias(KeyChainTestActivity.this, new AliasResponse(),
keyTypes, issuers,
socket.getInetAddress().getHostName(), socket.getPort(),
"My Test Certificate");
String alias;
synchronized (mAliasLock) {
while (mAlias == null) {
try {
mAliasLock.wait();
} catch (InterruptedException ignored) {
}
}
alias = mAlias;
}
return alias;
}
@Override public String chooseServerAlias(String keyType,
Principal[] issuers,
Socket socket) {
// not a client SSLSocket callback
throw new UnsupportedOperationException();
}
@Override public X509Certificate[] getCertificateChain(String alias) {
try {
log("KeyChainKeyManager getCertificateChain...");
X509Certificate[] certificateChain
= KeyChain.getCertificateChain(KeyChainTestActivity.this, alias);
if (certificateChain == null) {
log("Null certificate chain!");
return null;
}
for (int i = 0; i < certificateChain.length; i++) {
log("certificate[" + i + "]=" + certificateChain[i]);
}
return certificateChain;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
} catch (KeyChainException e) {
throw new RuntimeException(e);
}
}
@Override public String[] getClientAliases(String keyType, Principal[] issuers) {
// not a client SSLSocket callback
throw new UnsupportedOperationException();
}
@Override public String[] getServerAliases(String keyType, Principal[] issuers) {
// not a client SSLSocket callback
throw new UnsupportedOperationException();
}
@Override public PrivateKey getPrivateKey(String alias) {
try {
log("KeyChainKeyManager getPrivateKey...");
PrivateKey privateKey = KeyChain.getPrivateKey(KeyChainTestActivity.this,
alias);
log("privateKey=" + privateKey);
return privateKey;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
} catch (KeyChainException e) {
throw new RuntimeException(e);
}
}
}
private class AliasResponse implements KeyChainAliasCallback {
@Override public void alias(String alias) {
if (alias == null) {
log("AliasResponse empty!");
log("Do you need to install some client certs with:");
log(" adb shell am startservice -n "
+ "com.android.keychain.tests/.KeyChainServiceTest");
return;
}
log("Alias choosen '" + alias + "'");
synchronized (mAliasLock) {
mAlias = alias;
mAliasLock.notifyAll();
}
}
}
private static class TrustAllTrustManager implements X509TrustManager {
@Override public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
@Override public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
@Override public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CA_INSTALL: {
log("onActivityResult REQUEST_CA_INSTALL...");
if (resultCode != RESULT_OK) {
log("REQUEST_CA_INSTALL failed!");
return;
}
new TestHttpsRequest().execute();
break;
}
default:
throw new IllegalStateException("requestCode == " + requestCode);
}
}
}