Home | History | Annotate | Download | only in basicandroidkeystore
      1 /*
      2 * Copyright (C) 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.basicandroidkeystore;
     18 
     19 import com.example.android.common.logger.Log;
     20 
     21 import android.content.Context;
     22 import android.os.Build;
     23 import android.os.Bundle;
     24 import android.security.KeyPairGeneratorSpec;
     25 import android.security.keystore.KeyGenParameterSpec;
     26 import android.security.keystore.KeyProperties;
     27 import android.support.v4.app.Fragment;
     28 import android.util.Base64;
     29 import android.view.MenuItem;
     30 
     31 import java.io.IOException;
     32 import java.math.BigInteger;
     33 import java.security.InvalidAlgorithmParameterException;
     34 import java.security.InvalidKeyException;
     35 import java.security.KeyPair;
     36 import java.security.KeyPairGenerator;
     37 import java.security.KeyStore;
     38 import java.security.KeyStoreException;
     39 import java.security.NoSuchAlgorithmException;
     40 import java.security.NoSuchProviderException;
     41 import java.security.Signature;
     42 import java.security.SignatureException;
     43 import java.security.UnrecoverableEntryException;
     44 import java.security.cert.CertificateException;
     45 import java.security.spec.AlgorithmParameterSpec;
     46 import java.util.Calendar;
     47 import java.util.GregorianCalendar;
     48 
     49 import javax.security.auth.x500.X500Principal;
     50 
     51 public class BasicAndroidKeyStoreFragment extends Fragment {
     52 
     53     public static final String TAG = "KeyStoreFragment";
     54 
     55     // BEGIN_INCLUDE(values)
     56 
     57     public static final String SAMPLE_ALIAS = "myKey";
     58 
     59     // Some sample data to sign, and later verify using the generated signature.
     60     public static final String SAMPLE_INPUT="Hello, Android!";
     61 
     62     // Just a handy place to store the signature in between signing and verifying.
     63     public String mSignatureStr = null;
     64 
     65     // You can store multiple key pairs in the Key Store.  The string used to refer to the Key you
     66     // want to store, or later pull, is referred to as an "alias" in this case, because calling it
     67     // a key, when you use it to retrieve a key, would just be irritating.
     68     private String mAlias = null;
     69 
     70     // END_INCLUDE(values)
     71 
     72     @Override
     73     public void onCreate(Bundle savedInstanceState) {
     74         super.onCreate(savedInstanceState);
     75         setHasOptionsMenu(true);
     76         setAlias(SAMPLE_ALIAS);
     77     }
     78 
     79     @Override
     80     public void onActivityCreated(Bundle savedInstanceState) {
     81         super.onActivityCreated(savedInstanceState);
     82     }
     83 
     84     @Override
     85     public boolean onOptionsItemSelected(MenuItem item) {
     86         switch (item.getItemId()) {
     87             case R.id.btn_create_keys:
     88                 try {
     89                     createKeys(getActivity());
     90                     Log.d(TAG, "Keys created");
     91                     return true;
     92                 } catch (NoSuchAlgorithmException e) {
     93                     Log.w(TAG, "RSA not supported", e);
     94                 } catch (InvalidAlgorithmParameterException e) {
     95                     Log.w(TAG, "No such provider: AndroidKeyStore");
     96                 } catch (NoSuchProviderException e) {
     97                     Log.w(TAG, "Invalid Algorithm Parameter Exception", e);
     98                 }
     99                 return true;
    100             case R.id.btn_sign_data:
    101                 try {
    102                     mSignatureStr = signData(SAMPLE_INPUT);
    103                 } catch (KeyStoreException e) {
    104                     Log.w(TAG, "KeyStore not Initialized", e);
    105                 } catch (UnrecoverableEntryException e) {
    106                     Log.w(TAG, "KeyPair not recovered", e);
    107                 } catch (NoSuchAlgorithmException e) {
    108                     Log.w(TAG, "RSA not supported", e);
    109                 } catch (InvalidKeyException e) {
    110                     Log.w(TAG, "Invalid Key", e);
    111                 } catch (SignatureException e) {
    112                     Log.w(TAG, "Invalid Signature", e);
    113                 } catch (IOException e) {
    114                     Log.w(TAG, "IO Exception", e);
    115                 } catch (CertificateException e) {
    116                     Log.w(TAG, "Error occurred while loading certificates", e);
    117                 }
    118                 Log.d(TAG, "Signature: " + mSignatureStr);
    119                 return true;
    120 
    121             case R.id.btn_verify_data:
    122                 boolean verified = false;
    123                 try {
    124                     if (mSignatureStr != null) {
    125                         verified = verifyData(SAMPLE_INPUT, mSignatureStr);
    126                     }
    127                 } catch (KeyStoreException e) {
    128                     Log.w(TAG, "KeyStore not Initialized", e);
    129                 } catch (CertificateException e) {
    130                     Log.w(TAG, "Error occurred while loading certificates", e);
    131                 } catch (NoSuchAlgorithmException e) {
    132                     Log.w(TAG, "RSA not supported", e);
    133                 } catch (IOException e) {
    134                     Log.w(TAG, "IO Exception", e);
    135                 } catch (UnrecoverableEntryException e) {
    136                     Log.w(TAG, "KeyPair not recovered", e);
    137                 } catch (InvalidKeyException e) {
    138                     Log.w(TAG, "Invalid Key", e);
    139                 } catch (SignatureException e) {
    140                     Log.w(TAG, "Invalid Signature", e);
    141                 }
    142                 if (verified) {
    143                     Log.d(TAG, "Data Signature Verified");
    144                 } else {
    145                     Log.d(TAG, "Data not verified.");
    146                 }
    147                 return true;
    148         }
    149         return false;
    150     }
    151 
    152     /**
    153      * Creates a public and private key and stores it using the Android Key Store, so that only
    154      * this application will be able to access the keys.
    155      */
    156     public void createKeys(Context context) throws NoSuchProviderException,
    157             NoSuchAlgorithmException, InvalidAlgorithmParameterException {
    158         // BEGIN_INCLUDE(create_valid_dates)
    159         // Create a start and end time, for the validity range of the key pair that's about to be
    160         // generated.
    161         Calendar start = new GregorianCalendar();
    162         Calendar end = new GregorianCalendar();
    163         end.add(Calendar.YEAR, 1);
    164         //END_INCLUDE(create_valid_dates)
    165 
    166         // BEGIN_INCLUDE(create_keypair)
    167         // Initialize a KeyPair generator using the the intended algorithm (in this example, RSA
    168         // and the KeyStore.  This example uses the AndroidKeyStore.
    169         KeyPairGenerator kpGenerator = KeyPairGenerator
    170                 .getInstance(SecurityConstants.TYPE_RSA,
    171                         SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
    172         // END_INCLUDE(create_keypair)
    173 
    174         // BEGIN_INCLUDE(create_spec)
    175         // The KeyPairGeneratorSpec object is how parameters for your key pair are passed
    176         // to the KeyPairGenerator.
    177         AlgorithmParameterSpec spec;
    178 
    179         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
    180             // Below Android M, use the KeyPairGeneratorSpec.Builder.
    181 
    182             spec = new KeyPairGeneratorSpec.Builder(context)
    183                     // You'll use the alias later to retrieve the key.  It's a key for the key!
    184                     .setAlias(mAlias)
    185                     // The subject used for the self-signed certificate of the generated pair
    186                     .setSubject(new X500Principal("CN=" + mAlias))
    187                     // The serial number used for the self-signed certificate of the
    188                     // generated pair.
    189                     .setSerialNumber(BigInteger.valueOf(1337))
    190                     // Date range of validity for the generated pair.
    191                     .setStartDate(start.getTime())
    192                     .setEndDate(end.getTime())
    193                     .build();
    194 
    195 
    196         } else {
    197             // On Android M or above, use the KeyGenparameterSpec.Builder and specify permitted
    198             // properties  and restrictions of the key.
    199             spec = new KeyGenParameterSpec.Builder(mAlias, KeyProperties.PURPOSE_SIGN)
    200                     .setCertificateSubject(new X500Principal("CN=" + mAlias))
    201                     .setDigests(KeyProperties.DIGEST_SHA256)
    202                     .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
    203                     .setCertificateSerialNumber(BigInteger.valueOf(1337))
    204                     .setCertificateNotBefore(start.getTime())
    205                     .setCertificateNotAfter(end.getTime())
    206                     .build();
    207         }
    208 
    209         kpGenerator.initialize(spec);
    210 
    211         KeyPair kp = kpGenerator.generateKeyPair();
    212         // END_INCLUDE(create_spec)
    213         Log.d(TAG, "Public Key is: " + kp.getPublic().toString());
    214     }
    215 
    216     /**
    217      * Signs the data using the key pair stored in the Android Key Store.  This signature can be
    218      * used with the data later to verify it was signed by this application.
    219      * @return A string encoding of the data signature generated
    220      */
    221     public String signData(String inputStr) throws KeyStoreException,
    222             UnrecoverableEntryException, NoSuchAlgorithmException, InvalidKeyException,
    223             SignatureException, IOException, CertificateException {
    224         byte[] data = inputStr.getBytes();
    225 
    226         // BEGIN_INCLUDE(sign_load_keystore)
    227         KeyStore ks = KeyStore.getInstance(SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
    228 
    229         // Weird artifact of Java API.  If you don't have an InputStream to load, you still need
    230         // to call "load", or it'll crash.
    231         ks.load(null);
    232 
    233         // Load the key pair from the Android Key Store
    234         KeyStore.Entry entry = ks.getEntry(mAlias, null);
    235 
    236         /* If the entry is null, keys were never stored under this alias.
    237          * Debug steps in this situation would be:
    238          * -Check the list of aliases by iterating over Keystore.aliases(), be sure the alias
    239          *   exists.
    240          * -If that's empty, verify they were both stored and pulled from the same keystore
    241          *   "AndroidKeyStore"
    242          */
    243         if (entry == null) {
    244             Log.w(TAG, "No key found under alias: " + mAlias);
    245             Log.w(TAG, "Exiting signData()...");
    246             return null;
    247         }
    248 
    249         /* If entry is not a KeyStore.PrivateKeyEntry, it might have gotten stored in a previous
    250          * iteration of your application that was using some other mechanism, or been overwritten
    251          * by something else using the same keystore with the same alias.
    252          * You can determine the type using entry.getClass() and debug from there.
    253          */
    254         if (!(entry instanceof KeyStore.PrivateKeyEntry)) {
    255             Log.w(TAG, "Not an instance of a PrivateKeyEntry");
    256             Log.w(TAG, "Exiting signData()...");
    257             return null;
    258         }
    259         // END_INCLUDE(sign_data)
    260 
    261         // BEGIN_INCLUDE(sign_create_signature)
    262         // This class doesn't actually represent the signature,
    263         // just the engine for creating/verifying signatures, using
    264         // the specified algorithm.
    265         Signature s = Signature.getInstance(SecurityConstants.SIGNATURE_SHA256withRSA);
    266 
    267         // Initialize Signature using specified private key
    268         s.initSign(((KeyStore.PrivateKeyEntry) entry).getPrivateKey());
    269 
    270         // Sign the data, store the result as a Base64 encoded String.
    271         s.update(data);
    272         byte[] signature = s.sign();
    273         String result = Base64.encodeToString(signature, Base64.DEFAULT);
    274         // END_INCLUDE(sign_data)
    275 
    276         return result;
    277     }
    278 
    279     /**
    280      * Given some data and a signature, uses the key pair stored in the Android Key Store to verify
    281      * that the data was signed by this application, using that key pair.
    282      * @param input The data to be verified.
    283      * @param signatureStr The signature provided for the data.
    284      * @return A boolean value telling you whether the signature is valid or not.
    285      */
    286     public boolean verifyData(String input, String signatureStr) throws KeyStoreException,
    287             CertificateException, NoSuchAlgorithmException, IOException,
    288             UnrecoverableEntryException, InvalidKeyException, SignatureException {
    289         byte[] data = input.getBytes();
    290         byte[] signature;
    291         // BEGIN_INCLUDE(decode_signature)
    292 
    293         // Make sure the signature string exists.  If not, bail out, nothing to do.
    294 
    295         if (signatureStr == null) {
    296             Log.w(TAG, "Invalid signature.");
    297             Log.w(TAG, "Exiting verifyData()...");
    298             return false;
    299         }
    300 
    301         try {
    302             // The signature is going to be examined as a byte array,
    303             // not as a base64 encoded string.
    304             signature = Base64.decode(signatureStr, Base64.DEFAULT);
    305         } catch (IllegalArgumentException e) {
    306             // signatureStr wasn't null, but might not have been encoded properly.
    307             // It's not a valid Base64 string.
    308             return false;
    309         }
    310         // END_INCLUDE(decode_signature)
    311 
    312         KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
    313 
    314         // Weird artifact of Java API.  If you don't have an InputStream to load, you still need
    315         // to call "load", or it'll crash.
    316         ks.load(null);
    317 
    318         // Load the key pair from the Android Key Store
    319         KeyStore.Entry entry = ks.getEntry(mAlias, null);
    320 
    321         if (entry == null) {
    322             Log.w(TAG, "No key found under alias: " + mAlias);
    323             Log.w(TAG, "Exiting verifyData()...");
    324             return false;
    325         }
    326 
    327         if (!(entry instanceof KeyStore.PrivateKeyEntry)) {
    328             Log.w(TAG, "Not an instance of a PrivateKeyEntry");
    329             return false;
    330         }
    331 
    332         // This class doesn't actually represent the signature,
    333         // just the engine for creating/verifying signatures, using
    334         // the specified algorithm.
    335         Signature s = Signature.getInstance(SecurityConstants.SIGNATURE_SHA256withRSA);
    336 
    337         // BEGIN_INCLUDE(verify_data)
    338         // Verify the data.
    339         s.initVerify(((KeyStore.PrivateKeyEntry) entry).getCertificate());
    340         s.update(data);
    341         return s.verify(signature);
    342         // END_INCLUDE(verify_data)
    343     }
    344 
    345     public void setAlias(String alias) {
    346         mAlias = alias;
    347     }
    348 }
    349