Home | History | Annotate | Download | only in tests
      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 
     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 
     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 
    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> {
    152         @Override protected Void doInBackground(Void... params) {
    153             mTestKeyStore = TestKeyStore.getServer();
    154             return null;
    155         }
    156         @Override protected void onPostExecute(Void result) {
    157             testCaInstall();
    158         }
    159     }
    160 
    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> {
    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         }
    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         }
    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 {
    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         }
    240         @Override public String chooseServerAlias(String keyType,
    241                                                   Principal[] issuers,
    242                                                   Socket socket) {
    243             // not a client SSLSocket callback
    244             throw new UnsupportedOperationException();
    245         }
    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         }
    266         @Override public String[] getClientAliases(String keyType, Principal[] issuers) {
    267             // not a client SSLSocket callback
    268             throw new UnsupportedOperationException();
    269         }
    270         @Override public String[] getServerAliases(String keyType, Principal[] issuers) {
    271             // not a client SSLSocket callback
    272             throw new UnsupportedOperationException();
    273         }
    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 {
    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 {
    308         @Override public void checkClientTrusted(X509Certificate[] chain, String authType)
    309                 throws CertificateException {
    310         }
    311         @Override public void checkServerTrusted(X509Certificate[] chain, String authType)
    312                 throws CertificateException {
    313         }
    314         @Override public X509Certificate[] getAcceptedIssuers() {
    315             return null;
    316         }
    317     }
    318 
    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