/* * 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 { /* * 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(); 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 { /* * 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(); 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(); /* * 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(); 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]; } /* * 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); 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 { /* * Deletes a previously generated or stored entry in the * KeyStore. */ KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); ks.load(null); ks.deleteEntry(alias); } 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(); } } }