1 /* 2 * Copyright 2013 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.apis.security; 18 19 import com.example.android.apis.R; 20 21 import android.app.Activity; 22 import android.content.Context; 23 import android.database.DataSetObserver; 24 import android.os.AsyncTask; 25 import android.os.Bundle; 26 import android.security.keystore.KeyGenParameterSpec; 27 import android.security.keystore.KeyProperties; 28 import android.util.Base64; 29 import android.util.Log; 30 import android.view.View; 31 import android.view.View.OnClickListener; 32 import android.view.View.OnFocusChangeListener; 33 import android.view.ViewGroup; 34 import android.widget.AdapterView; 35 import android.widget.AdapterView.OnItemClickListener; 36 import android.widget.AdapterView.OnItemSelectedListener; 37 import android.widget.ArrayAdapter; 38 import android.widget.Button; 39 import android.widget.EditText; 40 import android.widget.ListAdapter; 41 import android.widget.ListView; 42 43 import java.io.IOException; 44 import java.math.BigInteger; 45 import java.security.InvalidAlgorithmParameterException; 46 import java.security.InvalidKeyException; 47 import java.security.KeyPair; 48 import java.security.KeyPairGenerator; 49 import java.security.KeyStore; 50 import java.security.KeyStore.PrivateKeyEntry; 51 import java.security.KeyStoreException; 52 import java.security.NoSuchAlgorithmException; 53 import java.security.NoSuchProviderException; 54 import java.security.Signature; 55 import java.security.SignatureException; 56 import java.security.UnrecoverableEntryException; 57 import java.security.cert.CertificateException; 58 import java.util.ArrayList; 59 import java.util.Enumeration; 60 import java.util.List; 61 62 import javax.security.auth.x500.X500Principal; 63 64 public class KeyStoreUsage extends Activity { 65 private static final String TAG = "AndroidKeyStoreUsage"; 66 67 /** 68 * An instance of {@link java.security.KeyStore} through which this app 69 * talks to the {@code AndroidKeyStore}. 70 */ 71 KeyStore mKeyStore; 72 73 /** 74 * Used by the {@code ListView} in our layout to list the keys available in 75 * our {@code KeyStore} by their alias names. 76 */ 77 AliasAdapter mAdapter; 78 79 /** 80 * Button in the UI that causes a new keypair to be generated in the 81 * {@code KeyStore}. 82 */ 83 Button mGenerateButton; 84 85 /** 86 * Button in the UI that causes data to be signed by a key we selected from 87 * the list available in the {@code KeyStore}. 88 */ 89 Button mSignButton; 90 91 /** 92 * Button in the UI that causes data to be signed by a key we selected from 93 * the list available in the {@code KeyStore}. 94 */ 95 Button mVerifyButton; 96 97 /** 98 * Button in the UI that causes a key entry to be deleted from the 99 * {@code KeyStore}. 100 */ 101 Button mDeleteButton; 102 103 /** 104 * Text field in the UI that holds plaintext. 105 */ 106 EditText mPlainText; 107 108 /** 109 * Text field in the UI that holds the signature. 110 */ 111 EditText mCipherText; 112 113 /** 114 * The alias of the selected entry in the KeyStore. 115 */ 116 private String mSelectedAlias; 117 118 @Override 119 protected void onCreate(Bundle savedInstanceState) { 120 super.onCreate(savedInstanceState); 121 122 setContentView(R.layout.keystore_usage); 123 124 /* 125 * Set up our {@code ListView} with an adapter that allows 126 * us to choose from the available entry aliases. 127 */ 128 ListView lv = (ListView) findViewById(R.id.entries_list); 129 mAdapter = new AliasAdapter(getApplicationContext()); 130 lv.setAdapter(mAdapter); 131 lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 132 lv.setOnItemClickListener(new OnItemClickListener() { 133 @Override 134 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 135 mSelectedAlias = mAdapter.getItem(position); 136 setKeyActionButtonsEnabled(true); 137 } 138 }); 139 140 // This is alias the user wants for a generated key. 141 final EditText aliasInput = (EditText) findViewById(R.id.entry_name); 142 mGenerateButton = (Button) findViewById(R.id.generate_button); 143 mGenerateButton.setOnClickListener(new OnClickListener() { 144 @Override 145 public void onClick(View v) { 146 /* 147 * When the user presses the "Generate" button, we'll 148 * check the alias isn't blank here. 149 */ 150 final String alias = aliasInput.getText().toString(); 151 if (alias == null || alias.length() == 0) { 152 aliasInput.setError(getResources().getText(R.string.keystore_no_alias_error)); 153 } else { 154 /* 155 * It's not blank, so disable the generate button while 156 * the generation of the key is happening. It will be 157 * enabled by the {@code AsyncTask} later after its 158 * work is done. 159 */ 160 aliasInput.setError(null); 161 mGenerateButton.setEnabled(false); 162 new GenerateTask().execute(alias); 163 } 164 } 165 }); 166 167 mSignButton = (Button) findViewById(R.id.sign_button); 168 mSignButton.setOnClickListener(new OnClickListener() { 169 @Override 170 public void onClick(View v) { 171 final String alias = mSelectedAlias; 172 final String data = mPlainText.getText().toString(); 173 if (alias != null) { 174 setKeyActionButtonsEnabled(false); 175 new SignTask().execute(alias, data); 176 } 177 } 178 }); 179 180 mVerifyButton = (Button) findViewById(R.id.verify_button); 181 mVerifyButton.setOnClickListener(new OnClickListener() { 182 @Override 183 public void onClick(View v) { 184 final String alias = mSelectedAlias; 185 final String data = mPlainText.getText().toString(); 186 final String signature = mCipherText.getText().toString(); 187 if (alias != null) { 188 setKeyActionButtonsEnabled(false); 189 new VerifyTask().execute(alias, data, signature); 190 } 191 } 192 }); 193 194 mDeleteButton = (Button) findViewById(R.id.delete_button); 195 mDeleteButton.setOnClickListener(new OnClickListener() { 196 @Override 197 public void onClick(View v) { 198 final String alias = mSelectedAlias; 199 if (alias != null) { 200 setKeyActionButtonsEnabled(false); 201 new DeleteTask().execute(alias); 202 } 203 } 204 }); 205 206 mPlainText = (EditText) findViewById(R.id.plaintext); 207 mPlainText.setOnFocusChangeListener(new OnFocusChangeListener() { 208 @Override 209 public void onFocusChange(View v, boolean hasFocus) { 210 mPlainText.setTextColor(getResources().getColor(android.R.color.primary_text_dark)); 211 } 212 }); 213 214 mCipherText = (EditText) findViewById(R.id.ciphertext); 215 mCipherText.setOnFocusChangeListener(new OnFocusChangeListener() { 216 @Override 217 public void onFocusChange(View v, boolean hasFocus) { 218 mCipherText 219 .setTextColor(getResources().getColor(android.R.color.primary_text_dark)); 220 } 221 }); 222 223 updateKeyList(); 224 } 225 226 private class AliasAdapter extends ArrayAdapter<String> { 227 public AliasAdapter(Context context) { 228 // We want users to choose a key, so use the appropriate layout. 229 super(context, android.R.layout.simple_list_item_single_choice); 230 } 231 232 /** 233 * This clears out all previous aliases and replaces it with the 234 * current entries. 235 */ 236 public void setAliases(List<String> items) { 237 clear(); 238 addAll(items); 239 notifyDataSetChanged(); 240 } 241 } 242 243 private void updateKeyList() { 244 setKeyActionButtonsEnabled(false); 245 new UpdateKeyListTask().execute(); 246 } 247 248 /** 249 * Sets all the buttons related to actions that act on an existing key to 250 * enabled or disabled. 251 */ 252 private void setKeyActionButtonsEnabled(boolean enabled) { 253 mPlainText.setEnabled(enabled); 254 mCipherText.setEnabled(enabled); 255 mSignButton.setEnabled(enabled); 256 mVerifyButton.setEnabled(enabled); 257 mDeleteButton.setEnabled(enabled); 258 } 259 260 private class UpdateKeyListTask extends AsyncTask<Void, Void, Enumeration<String>> { 261 @Override 262 protected Enumeration<String> doInBackground(Void... params) { 263 try { 264 // BEGIN_INCLUDE(list) 265 /* 266 * Load the Android KeyStore instance using the the 267 * "AndroidKeyStore" provider to list out what entries are 268 * currently stored. 269 */ 270 KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); 271 ks.load(null); 272 Enumeration<String> aliases = ks.aliases(); 273 // END_INCLUDE(list) 274 return aliases; 275 } catch (KeyStoreException e) { 276 Log.w(TAG, "Could not list keys", e); 277 return null; 278 } catch (NoSuchAlgorithmException e) { 279 Log.w(TAG, "Could not list keys", e); 280 return null; 281 } catch (CertificateException e) { 282 Log.w(TAG, "Could not list keys", e); 283 return null; 284 } catch (IOException e) { 285 Log.w(TAG, "Could not list keys", e); 286 return null; 287 } 288 } 289 290 @Override 291 protected void onPostExecute(Enumeration<String> result) { 292 List<String> aliases = new ArrayList<String>(); 293 while (result.hasMoreElements()) { 294 aliases.add(result.nextElement()); 295 } 296 mAdapter.setAliases(aliases); 297 } 298 } 299 300 private class GenerateTask extends AsyncTask<String, Void, Boolean> { 301 @Override 302 protected Boolean doInBackground(String... params) { 303 final String alias = params[0]; 304 try { 305 // BEGIN_INCLUDE(generate) 306 /* 307 * Generate a new EC key pair entry in the Android Keystore by 308 * using the KeyPairGenerator API. The private key can only be 309 * used for signing or verification and only with SHA-256 or 310 * SHA-512 as the message digest. 311 */ 312 KeyPairGenerator kpg = KeyPairGenerator.getInstance( 313 KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore"); 314 kpg.initialize(new KeyGenParameterSpec.Builder( 315 alias, 316 KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY) 317 .setDigests(KeyProperties.DIGEST_SHA256, 318 KeyProperties.DIGEST_SHA512) 319 .build()); 320 321 KeyPair kp = kpg.generateKeyPair(); 322 // END_INCLUDE(generate) 323 return true; 324 } catch (NoSuchAlgorithmException e) { 325 Log.w(TAG, "Could not generate key", e); 326 return false; 327 } catch (InvalidAlgorithmParameterException e) { 328 Log.w(TAG, "Could not generate key", e); 329 return false; 330 } catch (NoSuchProviderException e) { 331 Log.w(TAG, "Could not generate key", e); 332 return false; 333 } 334 } 335 336 @Override 337 protected void onPostExecute(Boolean result) { 338 updateKeyList(); 339 mGenerateButton.setEnabled(true); 340 } 341 342 @Override 343 protected void onCancelled() { 344 mGenerateButton.setEnabled(true); 345 } 346 } 347 348 private class SignTask extends AsyncTask<String, Void, String> { 349 @Override 350 protected String doInBackground(String... params) { 351 final String alias = params[0]; 352 final String dataString = params[1]; 353 try { 354 byte[] data = dataString.getBytes(); 355 // BEGIN_INCLUDE(sign) 356 /* 357 * Use a PrivateKey in the KeyStore to create a signature over 358 * some data. 359 */ 360 KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); 361 ks.load(null); 362 KeyStore.Entry entry = ks.getEntry(alias, null); 363 if (!(entry instanceof PrivateKeyEntry)) { 364 Log.w(TAG, "Not an instance of a PrivateKeyEntry"); 365 return null; 366 } 367 Signature s = Signature.getInstance("SHA256withECDSA"); 368 s.initSign(((PrivateKeyEntry) entry).getPrivateKey()); 369 s.update(data); 370 byte[] signature = s.sign(); 371 // END_INCLUDE(sign) 372 return Base64.encodeToString(signature, Base64.DEFAULT); 373 } catch (NoSuchAlgorithmException e) { 374 Log.w(TAG, "Could not generate key", e); 375 return null; 376 } catch (KeyStoreException e) { 377 Log.w(TAG, "Could not generate key", e); 378 return null; 379 } catch (CertificateException e) { 380 Log.w(TAG, "Could not generate key", e); 381 return null; 382 } catch (IOException e) { 383 Log.w(TAG, "Could not generate key", e); 384 return null; 385 } catch (UnrecoverableEntryException e) { 386 Log.w(TAG, "Could not generate key", e); 387 return null; 388 } catch (InvalidKeyException e) { 389 Log.w(TAG, "Could not generate key", e); 390 return null; 391 } catch (SignatureException e) { 392 Log.w(TAG, "Could not generate key", e); 393 return null; 394 } 395 } 396 397 @Override 398 protected void onPostExecute(String result) { 399 mCipherText.setText(result); 400 setKeyActionButtonsEnabled(true); 401 } 402 403 @Override 404 protected void onCancelled() { 405 mCipherText.setText("error!"); 406 setKeyActionButtonsEnabled(true); 407 } 408 } 409 410 private class VerifyTask extends AsyncTask<String, Void, Boolean> { 411 @Override 412 protected Boolean doInBackground(String... params) { 413 final String alias = params[0]; 414 final String dataString = params[1]; 415 final String signatureString = params[2]; 416 try { 417 byte[] data = dataString.getBytes(); 418 byte[] signature; 419 try { 420 signature = Base64.decode(signatureString, Base64.DEFAULT); 421 } catch (IllegalArgumentException e) { 422 signature = new byte[0]; 423 } 424 // BEGIN_INCLUDE(verify) 425 /* 426 * Verify a signature previously made by a PrivateKey in our 427 * KeyStore. This uses the X.509 certificate attached to our 428 * private key in the KeyStore to validate a previously 429 * generated signature. 430 */ 431 KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); 432 ks.load(null); 433 KeyStore.Entry entry = ks.getEntry(alias, null); 434 if (!(entry instanceof PrivateKeyEntry)) { 435 Log.w(TAG, "Not an instance of a PrivateKeyEntry"); 436 return false; 437 } 438 Signature s = Signature.getInstance("SHA256withECDSA"); 439 s.initVerify(((PrivateKeyEntry) entry).getCertificate()); 440 s.update(data); 441 boolean valid = s.verify(signature); 442 // END_INCLUDE(verify) 443 return valid; 444 } catch (NoSuchAlgorithmException e) { 445 Log.w(TAG, "Could not generate key", e); 446 return false; 447 } catch (KeyStoreException e) { 448 Log.w(TAG, "Could not generate key", e); 449 return false; 450 } catch (CertificateException e) { 451 Log.w(TAG, "Could not generate key", e); 452 return false; 453 } catch (IOException e) { 454 Log.w(TAG, "Could not generate key", e); 455 return false; 456 } catch (UnrecoverableEntryException e) { 457 Log.w(TAG, "Could not generate key", e); 458 return false; 459 } catch (InvalidKeyException e) { 460 Log.w(TAG, "Could not generate key", e); 461 return false; 462 } catch (SignatureException e) { 463 Log.w(TAG, "Could not generate key", e); 464 return false; 465 } 466 } 467 468 @Override 469 protected void onPostExecute(Boolean result) { 470 if (result) { 471 mCipherText.setTextColor(getResources().getColor(R.color.solid_green)); 472 } else { 473 mCipherText.setTextColor(getResources().getColor(R.color.solid_red)); 474 } 475 setKeyActionButtonsEnabled(true); 476 } 477 478 @Override 479 protected void onCancelled() { 480 mCipherText.setText("error!"); 481 setKeyActionButtonsEnabled(true); 482 mCipherText.setTextColor(getResources().getColor(android.R.color.primary_text_dark)); 483 } 484 } 485 486 private class DeleteTask extends AsyncTask<String, Void, Void> { 487 @Override 488 protected Void doInBackground(String... params) { 489 final String alias = params[0]; 490 try { 491 // BEGIN_INCLUDE(delete) 492 /* 493 * Deletes a previously generated or stored entry in the 494 * KeyStore. 495 */ 496 KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); 497 ks.load(null); 498 ks.deleteEntry(alias); 499 // END_INCLUDE(delete) 500 } catch (NoSuchAlgorithmException e) { 501 Log.w(TAG, "Could not generate key", e); 502 } catch (KeyStoreException e) { 503 Log.w(TAG, "Could not generate key", e); 504 } catch (CertificateException e) { 505 Log.w(TAG, "Could not generate key", e); 506 } catch (IOException e) { 507 Log.w(TAG, "Could not generate key", e); 508 } 509 return null; 510 } 511 512 @Override 513 protected void onPostExecute(Void result) { 514 updateKeyList(); 515 } 516 517 @Override 518 protected void onCancelled() { 519 updateKeyList(); 520 } 521 } 522 } 523