• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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