Home | History | Annotate | Download | only in security
      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