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