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.KeyPairGeneratorSpec; 27 import android.util.Base64; 28 import android.util.Log; 29 import android.view.View; 30 import android.view.View.OnClickListener; 31 import android.view.View.OnFocusChangeListener; 32 import android.view.ViewGroup; 33 import android.widget.AdapterView; 34 import android.widget.AdapterView.OnItemClickListener; 35 import android.widget.AdapterView.OnItemSelectedListener; 36 import android.widget.ArrayAdapter; 37 import android.widget.Button; 38 import android.widget.EditText; 39 import android.widget.ListAdapter; 40 import android.widget.ListView; 41 42 import java.io.IOException; 43 import java.math.BigInteger; 44 import java.security.InvalidAlgorithmParameterException; 45 import java.security.InvalidKeyException; 46 import java.security.KeyPair; 47 import java.security.KeyPairGenerator; 48 import java.security.KeyStore; 49 import java.security.KeyStore.PrivateKeyEntry; 50 import java.security.KeyStoreException; 51 import java.security.NoSuchAlgorithmException; 52 import java.security.NoSuchProviderException; 53 import java.security.Signature; 54 import java.security.SignatureException; 55 import java.security.UnrecoverableEntryException; 56 import java.security.cert.CertificateException; 57 import java.util.ArrayList; 58 import java.util.Calendar; 59 import java.util.Date; 60 import java.util.Enumeration; 61 import java.util.List; 62 63 import javax.security.auth.x500.X500Principal; 64 65 public class KeyStoreUsage extends Activity { 66 private static final String TAG = "AndroidKeyStoreUsage"; 67 68 /** 69 * An instance of {@link java.security.KeyStore} through which this app 70 * talks to the {@code AndroidKeyStore}. 71 */ 72 KeyStore mKeyStore; 73 74 /** 75 * Used by the {@code ListView} in our layout to list the keys available in 76 * our {@code KeyStore} by their alias names. 77 */ 78 AliasAdapter mAdapter; 79 80 /** 81 * Button in the UI that causes a new keypair to be generated in the 82 * {@code KeyStore}. 83 */ 84 Button mGenerateButton; 85 86 /** 87 * Button in the UI that causes data to be signed by a key we selected from 88 * the list available in the {@code KeyStore}. 89 */ 90 Button mSignButton; 91 92 /** 93 * Button in the UI that causes data to be signed by a key we selected from 94 * the list available in the {@code KeyStore}. 95 */ 96 Button mVerifyButton; 97 98 /** 99 * Button in the UI that causes a key entry to be deleted from the 100 * {@code KeyStore}. 101 */ 102 Button mDeleteButton; 103 104 /** 105 * Text field in the UI that holds plaintext. 106 */ 107 EditText mPlainText; 108 109 /** 110 * Text field in the UI that holds the signature. 111 */ 112 EditText mCipherText; 113 114 /** 115 * The alias of the selected entry in the KeyStore. 116 */ 117 private String mSelectedAlias; 118 119 @Override onCreate(Bundle savedInstanceState)120 protected void onCreate(Bundle savedInstanceState) { 121 super.onCreate(savedInstanceState); 122 123 setContentView(R.layout.keystore_usage); 124 125 /* 126 * Set up our {@code ListView} with an adapter that allows 127 * us to choose from the available entry aliases. 128 */ 129 ListView lv = (ListView) findViewById(R.id.entries_list); 130 mAdapter = new AliasAdapter(getApplicationContext()); 131 lv.setAdapter(mAdapter); 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> { AliasAdapter(Context context)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 */ setAliases(List<String> items)236 public void setAliases(List<String> items) { 237 clear(); 238 addAll(items); 239 notifyDataSetChanged(); 240 } 241 } 242 updateKeyList()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 */ setKeyActionButtonsEnabled(boolean enabled)252 private void setKeyActionButtonsEnabled(boolean enabled) { 253 mSignButton.setEnabled(enabled); 254 mVerifyButton.setEnabled(enabled); 255 mDeleteButton.setEnabled(enabled); 256 } 257 258 private class UpdateKeyListTask extends AsyncTask<Void, Void, Enumeration<String>> { 259 @Override doInBackground(Void... params)260 protected Enumeration<String> doInBackground(Void... params) { 261 try { 262 // BEGIN_INCLUDE(list) 263 /* 264 * Load the Android KeyStore instance using the the 265 * "AndroidKeyStore" provider to list out what entries are 266 * currently stored. 267 */ 268 KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); 269 ks.load(null); 270 Enumeration<String> aliases = ks.aliases(); 271 // END_INCLUDE(list) 272 return aliases; 273 } catch (KeyStoreException e) { 274 Log.w(TAG, "Could not list keys", e); 275 return null; 276 } catch (NoSuchAlgorithmException e) { 277 Log.w(TAG, "Could not list keys", e); 278 return null; 279 } catch (CertificateException e) { 280 Log.w(TAG, "Could not list keys", e); 281 return null; 282 } catch (IOException e) { 283 Log.w(TAG, "Could not list keys", e); 284 return null; 285 } 286 } 287 288 @Override onPostExecute(Enumeration<String> result)289 protected void onPostExecute(Enumeration<String> result) { 290 List<String> aliases = new ArrayList<String>(); 291 while (result.hasMoreElements()) { 292 aliases.add(result.nextElement()); 293 } 294 mAdapter.setAliases(aliases); 295 } 296 } 297 298 private class GenerateTask extends AsyncTask<String, Void, Boolean> { 299 @Override doInBackground(String... params)300 protected Boolean doInBackground(String... params) { 301 final String alias = params[0]; 302 try { 303 // BEGIN_INCLUDE(generate) 304 /* 305 * Generate a new entry in the KeyStore by using the 306 * KeyPairGenerator API. We have to specify the attributes for a 307 * self-signed X.509 certificate here so the KeyStore can attach 308 * the public key part to it. It can be replaced later with a 309 * certificate signed by a Certificate Authority (CA) if needed. 310 */ 311 Calendar cal = Calendar.getInstance(); 312 Date now = cal.getTime(); 313 cal.add(Calendar.YEAR, 1); 314 Date end = cal.getTime(); 315 316 KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore"); 317 kpg.initialize(new KeyPairGeneratorSpec.Builder(getApplicationContext()) 318 .setAlias(alias) 319 .setStartDate(now) 320 .setEndDate(end) 321 .setSerialNumber(BigInteger.valueOf(1)) 322 .setSubject(new X500Principal("CN=test1")) 323 .build()); 324 325 KeyPair kp = kpg.generateKeyPair(); 326 // END_INCLUDE(generate) 327 return true; 328 } catch (NoSuchAlgorithmException e) { 329 Log.w(TAG, "Could not generate key", e); 330 return false; 331 } catch (InvalidAlgorithmParameterException e) { 332 Log.w(TAG, "Could not generate key", e); 333 return false; 334 } catch (NoSuchProviderException e) { 335 Log.w(TAG, "Could not generate key", e); 336 return false; 337 } 338 } 339 340 @Override onPostExecute(Boolean result)341 protected void onPostExecute(Boolean result) { 342 updateKeyList(); 343 mGenerateButton.setEnabled(true); 344 } 345 346 @Override onCancelled()347 protected void onCancelled() { 348 mGenerateButton.setEnabled(true); 349 } 350 } 351 352 private class SignTask extends AsyncTask<String, Void, String> { 353 @Override doInBackground(String... params)354 protected String doInBackground(String... params) { 355 final String alias = params[0]; 356 final String dataString = params[1]; 357 try { 358 byte[] data = dataString.getBytes(); 359 // BEGIN_INCLUDE(sign) 360 /* 361 * Use a PrivateKey in the KeyStore to create a signature over 362 * some data. 363 */ 364 KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); 365 ks.load(null); 366 KeyStore.Entry entry = ks.getEntry(alias, null); 367 if (!(entry instanceof PrivateKeyEntry)) { 368 Log.w(TAG, "Not an instance of a PrivateKeyEntry"); 369 return null; 370 } 371 Signature s = Signature.getInstance("SHA256withRSA"); 372 s.initSign(((PrivateKeyEntry) entry).getPrivateKey()); 373 s.update(data); 374 byte[] signature = s.sign(); 375 // END_INCLUDE(sign) 376 return Base64.encodeToString(signature, Base64.DEFAULT); 377 } catch (NoSuchAlgorithmException e) { 378 Log.w(TAG, "Could not generate key", e); 379 return null; 380 } catch (KeyStoreException e) { 381 Log.w(TAG, "Could not generate key", e); 382 return null; 383 } catch (CertificateException e) { 384 Log.w(TAG, "Could not generate key", e); 385 return null; 386 } catch (IOException e) { 387 Log.w(TAG, "Could not generate key", e); 388 return null; 389 } catch (UnrecoverableEntryException e) { 390 Log.w(TAG, "Could not generate key", e); 391 return null; 392 } catch (InvalidKeyException e) { 393 Log.w(TAG, "Could not generate key", e); 394 return null; 395 } catch (SignatureException e) { 396 Log.w(TAG, "Could not generate key", e); 397 return null; 398 } 399 } 400 401 @Override onPostExecute(String result)402 protected void onPostExecute(String result) { 403 mCipherText.setText(result); 404 setKeyActionButtonsEnabled(true); 405 } 406 407 @Override onCancelled()408 protected void onCancelled() { 409 mCipherText.setText("error!"); 410 setKeyActionButtonsEnabled(true); 411 } 412 } 413 414 private class VerifyTask extends AsyncTask<String, Void, Boolean> { 415 @Override doInBackground(String... params)416 protected Boolean doInBackground(String... params) { 417 final String alias = params[0]; 418 final String dataString = params[1]; 419 final String signatureString = params[2]; 420 try { 421 byte[] data = dataString.getBytes(); 422 byte[] signature; 423 try { 424 signature = Base64.decode(signatureString, Base64.DEFAULT); 425 } catch (IllegalArgumentException e) { 426 signature = new byte[0]; 427 } 428 // BEGIN_INCLUDE(verify) 429 /* 430 * Verify a signature previously made by a PrivateKey in our 431 * KeyStore. This uses the X.509 certificate attached to our 432 * private key in the KeyStore to validate a previously 433 * generated signature. 434 */ 435 KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); 436 ks.load(null); 437 KeyStore.Entry entry = ks.getEntry(alias, null); 438 if (!(entry instanceof PrivateKeyEntry)) { 439 Log.w(TAG, "Not an instance of a PrivateKeyEntry"); 440 return false; 441 } 442 Signature s = Signature.getInstance("SHA256withRSA"); 443 s.initVerify(((PrivateKeyEntry) entry).getCertificate()); 444 s.update(data); 445 boolean valid = s.verify(signature); 446 // END_INCLUDE(verify) 447 return valid; 448 } catch (NoSuchAlgorithmException e) { 449 Log.w(TAG, "Could not generate key", e); 450 return false; 451 } catch (KeyStoreException e) { 452 Log.w(TAG, "Could not generate key", e); 453 return false; 454 } catch (CertificateException e) { 455 Log.w(TAG, "Could not generate key", e); 456 return false; 457 } catch (IOException e) { 458 Log.w(TAG, "Could not generate key", e); 459 return false; 460 } catch (UnrecoverableEntryException e) { 461 Log.w(TAG, "Could not generate key", e); 462 return false; 463 } catch (InvalidKeyException e) { 464 Log.w(TAG, "Could not generate key", e); 465 return false; 466 } catch (SignatureException e) { 467 Log.w(TAG, "Could not generate key", e); 468 return false; 469 } 470 } 471 472 @Override onPostExecute(Boolean result)473 protected void onPostExecute(Boolean result) { 474 if (result) { 475 mCipherText.setTextColor(getResources().getColor(R.color.solid_green)); 476 } else { 477 mCipherText.setTextColor(getResources().getColor(R.color.solid_red)); 478 } 479 setKeyActionButtonsEnabled(true); 480 } 481 482 @Override onCancelled()483 protected void onCancelled() { 484 mCipherText.setText("error!"); 485 setKeyActionButtonsEnabled(true); 486 mCipherText.setTextColor(getResources().getColor(android.R.color.primary_text_dark)); 487 } 488 } 489 490 private class DeleteTask extends AsyncTask<String, Void, Void> { 491 @Override doInBackground(String... params)492 protected Void doInBackground(String... params) { 493 final String alias = params[0]; 494 try { 495 // BEGIN_INCLUDE(delete) 496 /* 497 * Deletes a previously generated or stored entry in the 498 * KeyStore. 499 */ 500 KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); 501 ks.load(null); 502 ks.deleteEntry(alias); 503 // END_INCLUDE(delete) 504 } catch (NoSuchAlgorithmException e) { 505 Log.w(TAG, "Could not generate key", e); 506 } catch (KeyStoreException e) { 507 Log.w(TAG, "Could not generate key", e); 508 } catch (CertificateException e) { 509 Log.w(TAG, "Could not generate key", e); 510 } catch (IOException e) { 511 Log.w(TAG, "Could not generate key", e); 512 } 513 return null; 514 } 515 516 @Override onPostExecute(Void result)517 protected void onPostExecute(Void result) { 518 updateKeyList(); 519 } 520 521 @Override onCancelled()522 protected void onCancelled() { 523 updateKeyList(); 524 } 525 } 526 } 527