1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.keychain.tests; 18 19 import android.app.Activity; 20 import android.content.Intent; 21 import android.os.AsyncTask; 22 import android.os.Bundle; 23 import android.os.StrictMode; 24 import android.security.KeyChain; 25 import android.security.KeyChainAliasCallback; 26 import android.security.KeyChainException; 27 import android.text.method.ScrollingMovementMethod; 28 import android.util.Log; 29 import android.widget.TextView; 30 import java.net.Socket; 31 import java.net.URL; 32 import java.security.KeyStore; 33 import java.security.Principal; 34 import java.security.PrivateKey; 35 import java.security.cert.CertificateException; 36 import java.security.cert.X509Certificate; 37 import javax.net.ssl.HttpsURLConnection; 38 import javax.net.ssl.KeyManager; 39 import javax.net.ssl.KeyManagerFactory; 40 import javax.net.ssl.SSLContext; 41 import javax.net.ssl.SSLSocketFactory; 42 import javax.net.ssl.TrustManager; 43 import javax.net.ssl.X509ExtendedKeyManager; 44 import javax.net.ssl.X509TrustManager; 45 import libcore.java.security.TestKeyStore; 46 import libcore.javax.net.ssl.TestSSLContext; 47 import com.google.mockwebserver.MockResponse; 48 import com.google.mockwebserver.MockWebServer; 49 50 /** 51 * Simple activity based test that exercises the KeyChain API 52 */ 53 public class KeyChainTestActivity extends Activity { 54 55 private static final String TAG = "KeyChainTestActivity"; 56 57 private static final int REQUEST_CA_INSTALL = 1; 58 59 private TextView mTextView; 60 61 private TestKeyStore mTestKeyStore; 62 63 private final Object mAliasLock = new Object(); 64 private String mAlias; 65 66 private final Object mGrantedLock = new Object(); 67 private boolean mGranted; 68 log(final String message)69 private void log(final String message) { 70 Log.d(TAG, message); 71 runOnUiThread(new Runnable() { 72 @Override public void run() { 73 mTextView.append(message + "\n"); 74 } 75 }); 76 } 77 onCreate(Bundle savedInstanceState)78 @Override public void onCreate(Bundle savedInstanceState) { 79 super.onCreate(savedInstanceState); 80 81 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() 82 .detectDiskReads() 83 .detectDiskWrites() 84 .detectAll() 85 .penaltyLog() 86 .penaltyDeath() 87 .build()); 88 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() 89 .detectLeakedSqlLiteObjects() 90 .detectLeakedClosableObjects() 91 .penaltyLog() 92 .penaltyDeath() 93 .build()); 94 95 mTextView = new TextView(this); 96 mTextView.setMovementMethod(new ScrollingMovementMethod()); 97 setContentView(mTextView); 98 99 log("Starting test..."); 100 testKeyChainImproperUse(); 101 102 new SetupTestKeyStore().execute(); 103 } 104 testKeyChainImproperUse()105 private void testKeyChainImproperUse() { 106 try { 107 KeyChain.getPrivateKey(null, null); 108 throw new AssertionError(); 109 } catch (InterruptedException e) { 110 throw new AssertionError(e); 111 } catch (KeyChainException e) { 112 throw new AssertionError(e); 113 } catch (NullPointerException expected) { 114 log("KeyChain failed as expected with null argument."); 115 } 116 117 try { 118 KeyChain.getPrivateKey(this, null); 119 throw new AssertionError(); 120 } catch (InterruptedException e) { 121 throw new AssertionError(e); 122 } catch (KeyChainException e) { 123 throw new AssertionError(e); 124 } catch (NullPointerException expected) { 125 log("KeyChain failed as expected with null argument."); 126 } 127 128 try { 129 KeyChain.getPrivateKey(null, ""); 130 throw new AssertionError(); 131 } catch (InterruptedException e) { 132 throw new AssertionError(e); 133 } catch (KeyChainException e) { 134 throw new AssertionError(e); 135 } catch (NullPointerException expected) { 136 log("KeyChain failed as expected with null argument."); 137 } 138 139 try { 140 KeyChain.getPrivateKey(this, ""); 141 throw new AssertionError(); 142 } catch (InterruptedException e) { 143 throw new AssertionError(e); 144 } catch (KeyChainException e) { 145 throw new AssertionError(e); 146 } catch (IllegalStateException expected) { 147 log("KeyChain failed as expected on main thread."); 148 } 149 } 150 151 private class SetupTestKeyStore extends AsyncTask<Void, Void, Void> { doInBackground(Void... params)152 @Override protected Void doInBackground(Void... params) { 153 mTestKeyStore = TestKeyStore.getServer(); 154 return null; 155 } onPostExecute(Void result)156 @Override protected void onPostExecute(Void result) { 157 testCaInstall(); 158 } 159 } 160 testCaInstall()161 private void testCaInstall() { 162 try { 163 log("Requesting install of server's CA..."); 164 X509Certificate ca = mTestKeyStore.getRootCertificate("RSA"); 165 Intent intent = KeyChain.createInstallIntent(); 166 intent.putExtra(KeyChain.EXTRA_NAME, TAG); 167 intent.putExtra(KeyChain.EXTRA_CERTIFICATE, ca.getEncoded()); 168 startActivityForResult(intent, REQUEST_CA_INSTALL); 169 } catch (Exception e) { 170 throw new AssertionError(e); 171 } 172 173 } 174 175 private class TestHttpsRequest extends AsyncTask<Void, Void, Void> { doInBackground(Void... params)176 @Override protected Void doInBackground(Void... params) { 177 try { 178 log("Starting web server..."); 179 URL url = startWebServer(); 180 log("Making https request to " + url); 181 makeHttpsRequest(url); 182 log("Tests succeeded."); 183 184 return null; 185 } catch (Exception e) { 186 throw new AssertionError(e); 187 } 188 } startWebServer()189 private URL startWebServer() throws Exception { 190 KeyStore serverKeyStore = mTestKeyStore.keyStore; 191 char[] serverKeyStorePassword = mTestKeyStore.storePassword; 192 String kmfAlgoritm = KeyManagerFactory.getDefaultAlgorithm(); 193 KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfAlgoritm); 194 kmf.init(serverKeyStore, serverKeyStorePassword); 195 SSLContext serverContext = SSLContext.getInstance("SSL"); 196 serverContext.init(kmf.getKeyManagers(), 197 new TrustManager[] { new TrustAllTrustManager() }, 198 null); 199 SSLSocketFactory sf = serverContext.getSocketFactory(); 200 SSLSocketFactory needClientAuth = TestSSLContext.clientAuth(sf, false, true); 201 MockWebServer server = new MockWebServer(); 202 server.useHttps(needClientAuth, false); 203 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 204 server.play(); 205 return server.getUrl("/"); 206 } makeHttpsRequest(URL url)207 private void makeHttpsRequest(URL url) throws Exception { 208 SSLContext clientContext = SSLContext.getInstance("SSL"); 209 clientContext.init(new KeyManager[] { new KeyChainKeyManager() }, null, null); 210 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); 211 connection.setSSLSocketFactory(clientContext.getSocketFactory()); 212 if (connection.getResponseCode() != 200) { 213 throw new AssertionError(); 214 } 215 } 216 } 217 218 private class KeyChainKeyManager extends X509ExtendedKeyManager { chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket)219 @Override public String chooseClientAlias(String[] keyTypes, 220 Principal[] issuers, 221 Socket socket) { 222 log("KeyChainKeyManager chooseClientAlias..."); 223 224 KeyChain.choosePrivateKeyAlias(KeyChainTestActivity.this, new AliasResponse(), 225 keyTypes, issuers, 226 socket.getInetAddress().getHostName(), socket.getPort(), 227 "My Test Certificate"); 228 String alias; 229 synchronized (mAliasLock) { 230 while (mAlias == null) { 231 try { 232 mAliasLock.wait(); 233 } catch (InterruptedException ignored) { 234 } 235 } 236 alias = mAlias; 237 } 238 return alias; 239 } chooseServerAlias(String keyType, Principal[] issuers, Socket socket)240 @Override public String chooseServerAlias(String keyType, 241 Principal[] issuers, 242 Socket socket) { 243 // not a client SSLSocket callback 244 throw new UnsupportedOperationException(); 245 } getCertificateChain(String alias)246 @Override public X509Certificate[] getCertificateChain(String alias) { 247 try { 248 log("KeyChainKeyManager getCertificateChain..."); 249 X509Certificate[] certificateChain 250 = KeyChain.getCertificateChain(KeyChainTestActivity.this, alias); 251 if (certificateChain == null) { 252 log("Null certificate chain!"); 253 return null; 254 } 255 for (int i = 0; i < certificateChain.length; i++) { 256 log("certificate[" + i + "]=" + certificateChain[i]); 257 } 258 return certificateChain; 259 } catch (InterruptedException e) { 260 Thread.currentThread().interrupt(); 261 return null; 262 } catch (KeyChainException e) { 263 throw new RuntimeException(e); 264 } 265 } getClientAliases(String keyType, Principal[] issuers)266 @Override public String[] getClientAliases(String keyType, Principal[] issuers) { 267 // not a client SSLSocket callback 268 throw new UnsupportedOperationException(); 269 } getServerAliases(String keyType, Principal[] issuers)270 @Override public String[] getServerAliases(String keyType, Principal[] issuers) { 271 // not a client SSLSocket callback 272 throw new UnsupportedOperationException(); 273 } getPrivateKey(String alias)274 @Override public PrivateKey getPrivateKey(String alias) { 275 try { 276 log("KeyChainKeyManager getPrivateKey..."); 277 PrivateKey privateKey = KeyChain.getPrivateKey(KeyChainTestActivity.this, 278 alias); 279 log("privateKey=" + privateKey); 280 return privateKey; 281 } catch (InterruptedException e) { 282 Thread.currentThread().interrupt(); 283 return null; 284 } catch (KeyChainException e) { 285 throw new RuntimeException(e); 286 } 287 } 288 } 289 290 private class AliasResponse implements KeyChainAliasCallback { alias(String alias)291 @Override public void alias(String alias) { 292 if (alias == null) { 293 log("AliasResponse empty!"); 294 log("Do you need to install some client certs with:"); 295 log(" adb shell am startservice -n " 296 + "com.android.keychain.tests/.KeyChainServiceTest"); 297 return; 298 } 299 log("Alias choosen '" + alias + "'"); 300 synchronized (mAliasLock) { 301 mAlias = alias; 302 mAliasLock.notifyAll(); 303 } 304 } 305 } 306 307 private static class TrustAllTrustManager implements X509TrustManager { checkClientTrusted(X509Certificate[] chain, String authType)308 @Override public void checkClientTrusted(X509Certificate[] chain, String authType) 309 throws CertificateException { 310 } checkServerTrusted(X509Certificate[] chain, String authType)311 @Override public void checkServerTrusted(X509Certificate[] chain, String authType) 312 throws CertificateException { 313 } getAcceptedIssuers()314 @Override public X509Certificate[] getAcceptedIssuers() { 315 return null; 316 } 317 } 318 onActivityResult(int requestCode, int resultCode, Intent data)319 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { 320 switch (requestCode) { 321 case REQUEST_CA_INSTALL: { 322 log("onActivityResult REQUEST_CA_INSTALL..."); 323 if (resultCode != RESULT_OK) { 324 log("REQUEST_CA_INSTALL failed!"); 325 return; 326 } 327 new TestHttpsRequest().execute(); 328 break; 329 } 330 default: 331 throw new IllegalStateException("requestCode == " + requestCode); 332 } 333 } 334 } 335