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 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 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 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 */ 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 */ 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 */ 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 */ 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 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 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 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 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 */ 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 */ 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 */ 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 */ 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