1 /*
2  * Copyright 2012 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.example.android.keychain;
18 
19 import android.app.Activity;
20 import android.content.Intent;
21 import android.content.SharedPreferences;
22 import android.content.SharedPreferences.Editor;
23 import android.net.Uri;
24 import android.os.Bundle;
25 import android.security.KeyChain;
26 import android.security.KeyChainAliasCallback;
27 import android.security.KeyChainException;
28 import android.util.Log;
29 import android.view.View;
30 import android.view.View.OnClickListener;
31 import android.widget.Button;
32 import android.widget.TextView;
33 
34 import java.io.BufferedInputStream;
35 import java.io.IOException;
36 import java.security.PrivateKey;
37 import java.security.cert.X509Certificate;
38 
39 public class KeyChainDemoActivity extends Activity implements
40         KeyChainAliasCallback {
41 
42     /**
43      * The file name of the PKCS12 file used
44      */
45     public static final String PKCS12_FILENAME = "keychain.p12";
46 
47     /**
48      * The pass phrase of the PKCS12 file
49      */
50     public static final String PKCS12_PASSWORD = "changeit";
51 
52     /**
53      * Intent extra name to indicate to stop server
54      */
55     public static final String EXTRA_STOP_SERVER = "stop_server";
56 
57     // Log tag for this class
58     private static final String TAG = "KeyChainApiActivity";
59 
60     // Alias for certificate
61     private static final String DEFAULT_ALIAS = "My Key Chain";
62 
63     // Name of the application preference
64     private static final String KEYCHAIN_PREF = "keychain";
65 
66     // Name of preference name that saves the alias
67     private static final String KEYCHAIN_PREF_ALIAS = "alias";
68 
69     // Request code used when starting the activity using the KeyChain install
70     // intent
71     private static final int INSTALL_KEYCHAIN_CODE = 1;
72 
73     // Test SSL URL
74     private static final String TEST_SSL_URL = "https://localhost:8080";
75 
76     // Button to start/stop the simple SSL web server
77     private Button serverButton;
78 
79     // Button to install the key chain
80     private Button keyChainButton;
81 
82     // Button to launch the browser for testing https://localhost:8080
83     private Button testSslButton;
84 
85     /** Called when the activity is first created. */
86     @Override
onCreate(Bundle savedInstanceState)87     public void onCreate(Bundle savedInstanceState) {
88         super.onCreate(savedInstanceState);
89 
90         // Set the view using the main.xml layout
91         setContentView(R.layout.main);
92 
93         // Check whether the key chain is installed or not. This takes time and
94         // should be done in another thread other than the main thread.
95         new Thread(new Runnable() {
96             @Override
97             public void run() {
98                 if (isKeyChainAccessible()) {
99                     // Key chain installed. Disable the install button and print
100                     // the key chain information
101                     disableKeyChainButton();
102                     printInfo();
103                 } else {
104                     Log.d(TAG, "Key Chain is not accessible");
105                 }
106             }
107         }).start();
108 
109         // Setup the key chain installation button
110         keyChainButton = (Button) findViewById(R.id.keychain_button);
111         keyChainButton.setOnClickListener(new OnClickListener() {
112             @Override
113             public void onClick(View v) {
114                 installPkcs12();
115             }
116         });
117 
118         // Setup the simple SSL web server start/stop button
119         serverButton = (Button) findViewById(R.id.server_button);
120         serverButton.setOnClickListener(new OnClickListener() {
121             @Override
122             public void onClick(View v) {
123                 if (serverButton.getText().equals(
124                         getResources().getString(R.string.server_start))) {
125                     serverButton.setText(R.string.server_stop);
126                     startServer();
127                 } else {
128                     serverButton.setText(R.string.server_start);
129                     stopServer();
130                 }
131             }
132         });
133 
134         // Setup the test SSL page button
135         testSslButton = (Button) findViewById(R.id.test_ssl_button);
136         testSslButton.setOnClickListener(new OnClickListener() {
137             @Override
138             public void onClick(View v) {
139                 Intent i = new Intent(Intent.ACTION_VIEW, Uri
140                         .parse(TEST_SSL_URL));
141                 startActivity(i);
142             }
143         });
144     }
145 
146     /**
147      * This will be called when the user click on the notification to stop the
148      * SSL server
149      */
150     @Override
onNewIntent(Intent intent)151     protected void onNewIntent(Intent intent) {
152         Log.d(TAG, "In onNewIntent()");
153         super.onNewIntent(intent);
154         boolean isStopServer = intent.getBooleanExtra(EXTRA_STOP_SERVER, false);
155         if (isStopServer) {
156             serverButton.setText(R.string.server_start);
157             stopServer();
158         }
159     }
160 
161     /**
162      * This implements the KeyChainAliasCallback
163      */
164     @Override
alias(String alias)165     public void alias(String alias) {
166         if (alias != null) {
167             setAlias(alias); // Set the alias in the application preference
168             disableKeyChainButton();
169             printInfo();
170         } else {
171             Log.d(TAG, "User hit Disallow");
172         }
173     }
174 
175     /**
176      * This method returns the alias of the key chain from the application
177      * preference
178      *
179      * @return The alias of the key chain
180      */
getAlias()181     private String getAlias() {
182         SharedPreferences pref = getSharedPreferences(KEYCHAIN_PREF,
183                 MODE_PRIVATE);
184         return pref.getString(KEYCHAIN_PREF_ALIAS, DEFAULT_ALIAS);
185     }
186 
187     /**
188      * This method sets the alias of the key chain to the application preference
189      */
setAlias(String alias)190     private void setAlias(String alias) {
191         SharedPreferences pref = getSharedPreferences(KEYCHAIN_PREF,
192                 MODE_PRIVATE);
193         Editor editor = pref.edit();
194         editor.putString(KEYCHAIN_PREF_ALIAS, alias);
195         editor.commit();
196     }
197 
198     /**
199      * This method prints the key chain information.
200      */
printInfo()201     private void printInfo() {
202         String alias = getAlias();
203         X509Certificate[] certs = getCertificateChain(alias);
204         final PrivateKey privateKey = getPrivateKey(alias);
205         final StringBuffer sb = new StringBuffer();
206         for (X509Certificate cert : certs) {
207             sb.append(cert.getIssuerDN());
208             sb.append("\n");
209         }
210         runOnUiThread(new Runnable() {
211             @Override
212             public void run() {
213                 TextView certTv = (TextView) findViewById(R.id.cert);
214                 TextView privateKeyTv = (TextView) findViewById(R.id.private_key);
215                 certTv.setText(sb.toString());
216                 privateKeyTv.setText(privateKey.getFormat() + ":" + privateKey);
217             }
218         });
219     }
220 
221     /**
222      * This method will launch an intent to install the key chain
223      */
installPkcs12()224     private void installPkcs12() {
225         try {
226             BufferedInputStream bis = new BufferedInputStream(getAssets().open(
227                     PKCS12_FILENAME));
228             byte[] keychain = new byte[bis.available()];
229             bis.read(keychain);
230 
231             Intent installIntent = KeyChain.createInstallIntent();
232             installIntent.putExtra(KeyChain.EXTRA_PKCS12, keychain);
233             installIntent.putExtra(KeyChain.EXTRA_NAME, DEFAULT_ALIAS);
234             startActivityForResult(installIntent, INSTALL_KEYCHAIN_CODE);
235         } catch (IOException e) {
236             e.printStackTrace();
237         }
238     }
239 
240     @Override
onActivityResult(int requestCode, int resultCode, Intent data)241     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
242         if (requestCode == INSTALL_KEYCHAIN_CODE) {
243             switch (resultCode) {
244                 case Activity.RESULT_OK:
245                     chooseCert();
246                     break;
247                 default:
248                     super.onActivityResult(requestCode, resultCode, data);
249             }
250         }
251     }
252 
chooseCert()253     private void chooseCert() {
254         KeyChain.choosePrivateKeyAlias(this, this, // Callback
255                 new String[] {}, // Any key types.
256                 null, // Any issuers.
257                 "localhost", // Any host
258                 -1, // Any port
259                 DEFAULT_ALIAS);
260     }
261 
getCertificateChain(String alias)262     private X509Certificate[] getCertificateChain(String alias) {
263         try {
264             return KeyChain.getCertificateChain(this, alias);
265         } catch (KeyChainException e) {
266             e.printStackTrace();
267         } catch (InterruptedException e) {
268             e.printStackTrace();
269         }
270         return null;
271     }
272 
getPrivateKey(String alias)273     private PrivateKey getPrivateKey(String alias) {
274         try {
275             return KeyChain.getPrivateKey(this, alias);
276         } catch (KeyChainException e) {
277             e.printStackTrace();
278         } catch (InterruptedException e) {
279             e.printStackTrace();
280         }
281         return null;
282     }
283 
284     /**
285      * This method checks if the key chain is installed
286      *
287      * @return true if the key chain is not installed or allowed
288      */
isKeyChainAccessible()289     private boolean isKeyChainAccessible() {
290         return getCertificateChain(getAlias()) != null
291                 && getPrivateKey(getAlias()) != null;
292     }
293 
294     /**
295      * This method starts the background service of the simple SSL web server
296      */
startServer()297     private void startServer() {
298         Intent secureWebServerIntent = new Intent(this,
299                 SecureWebServerService.class);
300         startService(secureWebServerIntent);
301     }
302 
303     /**
304      * This method stops the background service of the simple SSL web server
305      */
stopServer()306     private void stopServer() {
307         Intent secureWebServerIntent = new Intent(this,
308                 SecureWebServerService.class);
309         stopService(secureWebServerIntent);
310     }
311 
312     /**
313      * This is a convenient method to disable the key chain install button
314      */
disableKeyChainButton()315     private void disableKeyChainButton() {
316         runOnUiThread(new Runnable() {
317             @Override
318             public void run() {
319                 keyChainButton.setText(R.string.keychain_installed);
320                 keyChainButton.setEnabled(false);
321             }
322         });
323     }
324 
325 }
326