• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
onCreate(Bundle savedInstanceState)73     public void onCreate(Bundle savedInstanceState) {
74         super.onCreate(savedInstanceState);
75         setHasOptionsMenu(true);
76         setAlias(SAMPLE_ALIAS);
77     }
78 
79     @Override
onActivityCreated(Bundle savedInstanceState)80     public void onActivityCreated(Bundle savedInstanceState) {
81         super.onActivityCreated(savedInstanceState);
82     }
83 
84     @Override
onOptionsItemSelected(MenuItem item)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      */
createKeys(Context context)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      */
signData(String inputStr)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      */
verifyData(String input, String signatureStr)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 
setAlias(String alias)345     public void setAlias(String alias) {
346         mAlias = alias;
347     }
348 }
349