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.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
    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> {
    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         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
    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
    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
    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
    341         protected void onPostExecute(Boolean result) {
    342             updateKeyList();
    343             mGenerateButton.setEnabled(true);
    344         }
    345 
    346         @Override
    347         protected void onCancelled() {
    348             mGenerateButton.setEnabled(true);
    349         }
    350     }
    351 
    352     private class SignTask extends AsyncTask<String, Void, String> {
    353         @Override
    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
    402         protected void onPostExecute(String result) {
    403             mCipherText.setText(result);
    404             setKeyActionButtonsEnabled(true);
    405         }
    406 
    407         @Override
    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
    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
    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
    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
    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
    517         protected void onPostExecute(Void result) {
    518             updateKeyList();
    519         }
    520 
    521         @Override
    522         protected void onCancelled() {
    523             updateKeyList();
    524         }
    525     }
    526 }
    527