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
onCreate(Bundle savedInstanceState)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> {
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         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
doInBackground(Void... params)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
onPostExecute(Enumeration<String> result)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
doInBackground(String... params)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
onPostExecute(Boolean result)337         protected void onPostExecute(Boolean result) {
338             updateKeyList();
339             mGenerateButton.setEnabled(true);
340         }
341 
342         @Override
onCancelled()343         protected void onCancelled() {
344             mGenerateButton.setEnabled(true);
345         }
346     }
347 
348     private class SignTask extends AsyncTask<String, Void, String> {
349         @Override
doInBackground(String... params)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
onPostExecute(String result)398         protected void onPostExecute(String result) {
399             mCipherText.setText(result);
400             setKeyActionButtonsEnabled(true);
401         }
402 
403         @Override
onCancelled()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
doInBackground(String... params)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
onPostExecute(Boolean result)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
onCancelled()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
doInBackground(String... params)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
onPostExecute(Void result)513         protected void onPostExecute(Void result) {
514             updateKeyList();
515         }
516 
517         @Override
onCancelled()518         protected void onCancelled() {
519             updateKeyList();
520         }
521     }
522 }
523