diff options
Diffstat (limited to 'samples/ApiDemos/src/com/example/android/apis/security')
| -rw-r--r-- | samples/ApiDemos/src/com/example/android/apis/security/KeyStoreUsage.java | 526 |
1 files changed, 526 insertions, 0 deletions
diff --git a/samples/ApiDemos/src/com/example/android/apis/security/KeyStoreUsage.java b/samples/ApiDemos/src/com/example/android/apis/security/KeyStoreUsage.java new file mode 100644 index 000000000..901806ac5 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/security/KeyStoreUsage.java @@ -0,0 +1,526 @@ +/* + * Copyright 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.example.android.apis.security; + +import com.example.android.apis.R; + +import android.app.Activity; +import android.content.Context; +import android.database.DataSetObserver; +import android.os.AsyncTask; +import android.os.Bundle; +import android.security.KeyPairGeneratorSpec; +import android.util.Base64; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnFocusChangeListener; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ListAdapter; +import android.widget.ListView; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.KeyStore.PrivateKeyEntry; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Signature; +import java.security.SignatureException; +import java.security.UnrecoverableEntryException; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.Enumeration; +import java.util.List; + +import javax.security.auth.x500.X500Principal; + +public class KeyStoreUsage extends Activity { + private static final String TAG = "AndroidKeyStoreUsage"; + + /** + * An instance of {@link java.security.KeyStore} through which this app + * talks to the {@code AndroidKeyStore}. + */ + KeyStore mKeyStore; + + /** + * Used by the {@code ListView} in our layout to list the keys available in + * our {@code KeyStore} by their alias names. + */ + AliasAdapter mAdapter; + + /** + * Button in the UI that causes a new keypair to be generated in the + * {@code KeyStore}. + */ + Button mGenerateButton; + + /** + * Button in the UI that causes data to be signed by a key we selected from + * the list available in the {@code KeyStore}. + */ + Button mSignButton; + + /** + * Button in the UI that causes data to be signed by a key we selected from + * the list available in the {@code KeyStore}. + */ + Button mVerifyButton; + + /** + * Button in the UI that causes a key entry to be deleted from the + * {@code KeyStore}. + */ + Button mDeleteButton; + + /** + * Text field in the UI that holds plaintext. + */ + EditText mPlainText; + + /** + * Text field in the UI that holds the signature. + */ + EditText mCipherText; + + /** + * The alias of the selected entry in the KeyStore. + */ + private String mSelectedAlias; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.keystore_usage); + + /* + * Set up our {@code ListView} with an adapter that allows + * us to choose from the available entry aliases. + */ + ListView lv = (ListView) findViewById(R.id.entries_list); + mAdapter = new AliasAdapter(getApplicationContext()); + lv.setAdapter(mAdapter); + lv.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + mSelectedAlias = mAdapter.getItem(position); + setKeyActionButtonsEnabled(true); + } + }); + + // This is alias the user wants for a generated key. + final EditText aliasInput = (EditText) findViewById(R.id.entry_name); + mGenerateButton = (Button) findViewById(R.id.generate_button); + mGenerateButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + /* + * When the user presses the "Generate" button, we'll + * check the alias isn't blank here. + */ + final String alias = aliasInput.getText().toString(); + if (alias == null || alias.length() == 0) { + aliasInput.setError(getResources().getText(R.string.keystore_no_alias_error)); + } else { + /* + * It's not blank, so disable the generate button while + * the generation of the key is happening. It will be + * enabled by the {@code AsyncTask} later after its + * work is done. + */ + aliasInput.setError(null); + mGenerateButton.setEnabled(false); + new GenerateTask().execute(alias); + } + } + }); + + mSignButton = (Button) findViewById(R.id.sign_button); + mSignButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + final String alias = mSelectedAlias; + final String data = mPlainText.getText().toString(); + if (alias != null) { + setKeyActionButtonsEnabled(false); + new SignTask().execute(alias, data); + } + } + }); + + mVerifyButton = (Button) findViewById(R.id.verify_button); + mVerifyButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + final String alias = mSelectedAlias; + final String data = mPlainText.getText().toString(); + final String signature = mCipherText.getText().toString(); + if (alias != null) { + setKeyActionButtonsEnabled(false); + new VerifyTask().execute(alias, data, signature); + } + } + }); + + mDeleteButton = (Button) findViewById(R.id.delete_button); + mDeleteButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + final String alias = mSelectedAlias; + if (alias != null) { + setKeyActionButtonsEnabled(false); + new DeleteTask().execute(alias); + } + } + }); + + mPlainText = (EditText) findViewById(R.id.plaintext); + mPlainText.setOnFocusChangeListener(new OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + mPlainText.setTextColor(getResources().getColor(android.R.color.primary_text_dark)); + } + }); + + mCipherText = (EditText) findViewById(R.id.ciphertext); + mCipherText.setOnFocusChangeListener(new OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + mCipherText + .setTextColor(getResources().getColor(android.R.color.primary_text_dark)); + } + }); + + updateKeyList(); + } + + private class AliasAdapter extends ArrayAdapter<String> { + public AliasAdapter(Context context) { + // We want users to choose a key, so use the appropriate layout. + super(context, android.R.layout.simple_list_item_single_choice); + } + + /** + * This clears out all previous aliases and replaces it with the + * current entries. + */ + public void setAliases(List<String> items) { + clear(); + addAll(items); + notifyDataSetChanged(); + } + } + + private void updateKeyList() { + setKeyActionButtonsEnabled(false); + new UpdateKeyListTask().execute(); + } + + /** + * Sets all the buttons related to actions that act on an existing key to + * enabled or disabled. + */ + private void setKeyActionButtonsEnabled(boolean enabled) { + mSignButton.setEnabled(enabled); + mVerifyButton.setEnabled(enabled); + mDeleteButton.setEnabled(enabled); + } + + private class UpdateKeyListTask extends AsyncTask<Void, Void, Enumeration<String>> { + @Override + protected Enumeration<String> doInBackground(Void... params) { + try { +// BEGIN_INCLUDE(list) + /* + * Load the Android KeyStore instance using the the + * "AndroidKeyStore" provider to list out what entries are + * currently stored. + */ + KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); + ks.load(null); + Enumeration<String> aliases = ks.aliases(); +// END_INCLUDE(list) + return aliases; + } catch (KeyStoreException e) { + Log.w(TAG, "Could not list keys", e); + return null; + } catch (NoSuchAlgorithmException e) { + Log.w(TAG, "Could not list keys", e); + return null; + } catch (CertificateException e) { + Log.w(TAG, "Could not list keys", e); + return null; + } catch (IOException e) { + Log.w(TAG, "Could not list keys", e); + return null; + } + } + + @Override + protected void onPostExecute(Enumeration<String> result) { + List<String> aliases = new ArrayList<String>(); + while (result.hasMoreElements()) { + aliases.add(result.nextElement()); + } + mAdapter.setAliases(aliases); + } + } + + private class GenerateTask extends AsyncTask<String, Void, Boolean> { + @Override + protected Boolean doInBackground(String... params) { + final String alias = params[0]; + try { +// BEGIN_INCLUDE(generate) + /* + * Generate a new entry in the KeyStore by using the + * KeyPairGenerator API. We have to specify the attributes for a + * self-signed X.509 certificate here so the KeyStore can attach + * the public key part to it. It can be replaced later with a + * certificate signed by a Certificate Authority (CA) if needed. + */ + Calendar cal = Calendar.getInstance(); + Date now = cal.getTime(); + cal.add(Calendar.YEAR, 1); + Date end = cal.getTime(); + + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore"); + kpg.initialize(new KeyPairGeneratorSpec.Builder(getApplicationContext()) + .setAlias(alias) + .setStartDate(now) + .setEndDate(end) + .setSerialNumber(BigInteger.valueOf(1)) + .setSubject(new X500Principal("CN=test1")) + .build()); + + KeyPair kp = kpg.generateKeyPair(); +// END_INCLUDE(generate) + return true; + } catch (NoSuchAlgorithmException e) { + Log.w(TAG, "Could not generate key", e); + return false; + } catch (InvalidAlgorithmParameterException e) { + Log.w(TAG, "Could not generate key", e); + return false; + } catch (NoSuchProviderException e) { + Log.w(TAG, "Could not generate key", e); + return false; + } + } + + @Override + protected void onPostExecute(Boolean result) { + updateKeyList(); + mGenerateButton.setEnabled(true); + } + + @Override + protected void onCancelled() { + mGenerateButton.setEnabled(true); + } + } + + private class SignTask extends AsyncTask<String, Void, String> { + @Override + protected String doInBackground(String... params) { + final String alias = params[0]; + final String dataString = params[1]; + try { + byte[] data = dataString.getBytes(); +// BEGIN_INCLUDE(sign) + /* + * Use a PrivateKey in the KeyStore to create a signature over + * some data. + */ + KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); + ks.load(null); + KeyStore.Entry entry = ks.getEntry(alias, null); + if (!(entry instanceof PrivateKeyEntry)) { + Log.w(TAG, "Not an instance of a PrivateKeyEntry"); + return null; + } + Signature s = Signature.getInstance("SHA256withRSA"); + s.initSign(((PrivateKeyEntry) entry).getPrivateKey()); + s.update(data); + byte[] signature = s.sign(); +// END_INCLUDE(sign) + return Base64.encodeToString(signature, Base64.DEFAULT); + } catch (NoSuchAlgorithmException e) { + Log.w(TAG, "Could not generate key", e); + return null; + } catch (KeyStoreException e) { + Log.w(TAG, "Could not generate key", e); + return null; + } catch (CertificateException e) { + Log.w(TAG, "Could not generate key", e); + return null; + } catch (IOException e) { + Log.w(TAG, "Could not generate key", e); + return null; + } catch (UnrecoverableEntryException e) { + Log.w(TAG, "Could not generate key", e); + return null; + } catch (InvalidKeyException e) { + Log.w(TAG, "Could not generate key", e); + return null; + } catch (SignatureException e) { + Log.w(TAG, "Could not generate key", e); + return null; + } + } + + @Override + protected void onPostExecute(String result) { + mCipherText.setText(result); + setKeyActionButtonsEnabled(true); + } + + @Override + protected void onCancelled() { + mCipherText.setText("error!"); + setKeyActionButtonsEnabled(true); + } + } + + private class VerifyTask extends AsyncTask<String, Void, Boolean> { + @Override + protected Boolean doInBackground(String... params) { + final String alias = params[0]; + final String dataString = params[1]; + final String signatureString = params[2]; + try { + byte[] data = dataString.getBytes(); + byte[] signature; + try { + signature = Base64.decode(signatureString, Base64.DEFAULT); + } catch (IllegalArgumentException e) { + signature = new byte[0]; + } +// BEGIN_INCLUDE(verify) + /* + * Verify a signature previously made by a PrivateKey in our + * KeyStore. This uses the X.509 certificate attached to our + * private key in the KeyStore to validate a previously + * generated signature. + */ + KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); + ks.load(null); + KeyStore.Entry entry = ks.getEntry(alias, null); + if (!(entry instanceof PrivateKeyEntry)) { + Log.w(TAG, "Not an instance of a PrivateKeyEntry"); + return false; + } + Signature s = Signature.getInstance("SHA256withRSA"); + s.initVerify(((PrivateKeyEntry) entry).getCertificate()); + s.update(data); + boolean valid = s.verify(signature); +// END_INCLUDE(verify) + return valid; + } catch (NoSuchAlgorithmException e) { + Log.w(TAG, "Could not generate key", e); + return false; + } catch (KeyStoreException e) { + Log.w(TAG, "Could not generate key", e); + return false; + } catch (CertificateException e) { + Log.w(TAG, "Could not generate key", e); + return false; + } catch (IOException e) { + Log.w(TAG, "Could not generate key", e); + return false; + } catch (UnrecoverableEntryException e) { + Log.w(TAG, "Could not generate key", e); + return false; + } catch (InvalidKeyException e) { + Log.w(TAG, "Could not generate key", e); + return false; + } catch (SignatureException e) { + Log.w(TAG, "Could not generate key", e); + return false; + } + } + + @Override + protected void onPostExecute(Boolean result) { + if (result) { + mCipherText.setTextColor(getResources().getColor(R.color.solid_green)); + } else { + mCipherText.setTextColor(getResources().getColor(R.color.solid_red)); + } + setKeyActionButtonsEnabled(true); + } + + @Override + protected void onCancelled() { + mCipherText.setText("error!"); + setKeyActionButtonsEnabled(true); + mCipherText.setTextColor(getResources().getColor(android.R.color.primary_text_dark)); + } + } + + private class DeleteTask extends AsyncTask<String, Void, Void> { + @Override + protected Void doInBackground(String... params) { + final String alias = params[0]; + try { +// BEGIN_INCLUDE(delete) + /* + * Deletes a previously generated or stored entry in the + * KeyStore. + */ + KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); + ks.load(null); + ks.deleteEntry(alias); +// END_INCLUDE(delete) + } catch (NoSuchAlgorithmException e) { + Log.w(TAG, "Could not generate key", e); + } catch (KeyStoreException e) { + Log.w(TAG, "Could not generate key", e); + } catch (CertificateException e) { + Log.w(TAG, "Could not generate key", e); + } catch (IOException e) { + Log.w(TAG, "Could not generate key", e); + } + return null; + } + + @Override + protected void onPostExecute(Void result) { + updateKeyList(); + } + + @Override + protected void onCancelled() { + updateKeyList(); + } + } +} |
