Home | History | Annotate | Download | only in security
      1 /*
      2  * Copyright (C) 2015 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.android.cts.verifier.security;
     18 
     19 import com.android.cts.verifier.PassFailButtons;
     20 import com.android.cts.verifier.R;
     21 
     22 import android.Manifest;
     23 import android.app.AlertDialog;
     24 import android.app.Dialog;
     25 import android.app.DialogFragment;
     26 import android.app.KeyguardManager;
     27 import android.content.Context;
     28 import android.content.DialogInterface;
     29 import android.content.Intent;
     30 import android.content.pm.PackageManager;
     31 import android.hardware.fingerprint.FingerprintManager;
     32 import android.os.Bundle;
     33 import android.os.CancellationSignal;
     34 import android.util.Log;
     35 import android.security.keystore.KeyGenParameterSpec;
     36 import android.security.keystore.KeyPermanentlyInvalidatedException;
     37 import android.security.keystore.KeyProperties;
     38 import android.security.keystore.UserNotAuthenticatedException;
     39 import android.view.View;
     40 import android.view.View.OnClickListener;
     41 import android.widget.Button;
     42 import android.widget.Toast;
     43 
     44 import java.io.IOException;
     45 import java.security.InvalidAlgorithmParameterException;
     46 import java.security.InvalidKeyException;
     47 import java.security.KeyStore;
     48 import java.security.KeyStoreException;
     49 import java.security.NoSuchAlgorithmException;
     50 import java.security.NoSuchProviderException;
     51 import java.security.UnrecoverableKeyException;
     52 import java.security.cert.CertificateException;
     53 
     54 import javax.crypto.BadPaddingException;
     55 import javax.crypto.Cipher;
     56 import javax.crypto.IllegalBlockSizeException;
     57 import javax.crypto.KeyGenerator;
     58 import javax.crypto.NoSuchPaddingException;
     59 import javax.crypto.SecretKey;
     60 
     61 public class FingerprintBoundKeysTest extends PassFailButtons.Activity {
     62     private static final boolean DEBUG = false;
     63     private static final String TAG = "FingerprintBoundKeysTest";
     64 
     65     /** Alias for our key in the Android Key Store. */
     66     private static final String KEY_NAME = "my_key";
     67     private static final byte[] SECRET_BYTE_ARRAY = new byte[] {1, 2, 3, 4, 5, 6};
     68     private static final int AUTHENTICATION_DURATION_SECONDS = 5;
     69     private static final int CONFIRM_CREDENTIALS_REQUEST_CODE = 1;
     70     private static final int FINGERPRINT_PERMISSION_REQUEST_CODE = 0;
     71 
     72     private FingerprintManager mFingerprintManager;
     73     private KeyguardManager mKeyguardManager;
     74     private FingerprintAuthDialogFragment mFingerprintDialog;
     75     private Cipher mCipher;
     76 
     77     @Override
     78     protected void onCreate(Bundle savedInstanceState) {
     79         super.onCreate(savedInstanceState);
     80         setContentView(R.layout.sec_screen_lock_keys_main);
     81         setPassFailButtonClickListeners();
     82         setInfoResources(R.string.sec_fingerprint_bound_key_test, R.string.sec_fingerprint_bound_key_test_info, -1);
     83         getPassButton().setEnabled(false);
     84         requestPermissions(new String[]{Manifest.permission.USE_FINGERPRINT},
     85                 FINGERPRINT_PERMISSION_REQUEST_CODE);
     86     }
     87 
     88     @Override
     89     public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] state) {
     90         if (requestCode == FINGERPRINT_PERMISSION_REQUEST_CODE && state[0] == PackageManager.PERMISSION_GRANTED) {
     91             mFingerprintManager = (FingerprintManager) getSystemService(Context.FINGERPRINT_SERVICE);
     92             mKeyguardManager = (KeyguardManager) getSystemService(KeyguardManager.class);
     93             Button startTestButton = (Button) findViewById(R.id.sec_start_test_button);
     94 
     95             if (!mKeyguardManager.isKeyguardSecure()) {
     96                 // Show a message that the user hasn't set up a lock screen.
     97                 showToast( "Secure lock screen hasn't been set up.\n"
     98                                 + "Go to 'Settings -> Security -> Screen lock' to set up a lock screen");
     99                 startTestButton.setEnabled(false);
    100                 return;
    101             } else if (!mFingerprintManager.hasEnrolledFingerprints()) {
    102                 showToast("No fingerprints enrolled.\n"
    103                                 + "Go to 'Settings -> Security -> Fingerprint' to set up a fingerprint");
    104                 startTestButton.setEnabled(false);
    105                 return;
    106             }
    107 
    108             startTestButton.setOnClickListener(new OnClickListener() {
    109                 @Override
    110                 public void onClick(View v) {
    111                     createKey();
    112                     prepareEncrypt();
    113                     if (tryEncrypt()) {
    114                         showToast("Test failed. Key accessible without auth.");
    115                     } else {
    116                         showAuthenticationScreen();
    117                     }
    118                 }
    119             });
    120         }
    121     }
    122 
    123     /**
    124      * Creates a symmetric key in the Android Key Store which can only be used after the user has
    125      * authenticated with device credentials within the last X seconds.
    126      */
    127     private void createKey() {
    128         // Generate a key to decrypt payment credentials, tokens, etc.
    129         // This will most likely be a registration step for the user when they are setting up your app.
    130         try {
    131             KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
    132             keyStore.load(null);
    133             KeyGenerator keyGenerator = KeyGenerator.getInstance(
    134                     KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
    135 
    136             // Set the alias of the entry in Android KeyStore where the key will appear
    137             // and the constrains (purposes) in the constructor of the Builder
    138             keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME,
    139                     KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
    140                     .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
    141                     .setUserAuthenticationRequired(true)
    142                     .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
    143                     .build());
    144             keyGenerator.generateKey();
    145             if (DEBUG) {
    146                 Log.i(TAG, "createKey: [1]: done");
    147             }
    148         } catch (NoSuchAlgorithmException | NoSuchProviderException
    149                 | InvalidAlgorithmParameterException | KeyStoreException
    150                 | CertificateException | IOException e) {
    151             if (DEBUG) {
    152                 Log.i(TAG, "createKey: [2]: failed");
    153             }
    154             throw new RuntimeException("Failed to create a symmetric key", e);
    155         }
    156     }
    157 
    158     /**
    159      * create and init cipher; has to be done before we do auth
    160      */
    161     private boolean prepareEncrypt() {
    162         return encryptInternal(false);
    163     }
    164 
    165     /**
    166      * Tries to encrypt some data with the generated key in {@link #createKey} which is
    167      * only works if the user has just authenticated via device credentials.
    168      * has to be run after successful auth, in order to succeed
    169      */
    170     protected boolean tryEncrypt() {
    171         return encryptInternal(true);
    172     }
    173 
    174     protected Cipher getCipher() {
    175         return mCipher;
    176     }
    177 
    178     private boolean encryptInternal(boolean doEncrypt) {
    179         try {
    180             KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
    181             keyStore.load(null);
    182             SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_NAME, null);
    183             if (DEBUG) {
    184                 Log.i(TAG, "encryptInternal: [1]: key retrieved");
    185             }
    186             if (!doEncrypt) {
    187                 if (mCipher == null) {
    188                     mCipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
    189                             + KeyProperties.BLOCK_MODE_CBC + "/"
    190                             + KeyProperties.ENCRYPTION_PADDING_PKCS7);
    191                 }
    192                 mCipher.init(Cipher.ENCRYPT_MODE, secretKey);
    193                 if (DEBUG) {
    194                     Log.i(TAG, "encryptInternal: [2]: cipher initialized");
    195                 }
    196             } else {
    197                 mCipher.doFinal(SECRET_BYTE_ARRAY);
    198                 if (DEBUG) {
    199                     Log.i(TAG, "encryptInternal: [3]: encryption performed");
    200                 }
    201             }
    202             return true;
    203         } catch (BadPaddingException | IllegalBlockSizeException e) {
    204             // this happens in "no-error" scenarios routinely;
    205             // All we want it to see the event in the log;
    206             // Extra exception info is not valuable
    207             if (DEBUG) {
    208                 Log.i(TAG, "encryptInternal: [4]: Encryption failed");
    209             }
    210             return false;
    211         } catch (KeyPermanentlyInvalidatedException e) {
    212             // Extra exception info is not of big value, but let's have it,
    213             // since this is an unlikely sutuation and potential error condition
    214             Log.w(TAG, "encryptInternal: [5]: Key invalidated", e);
    215             createKey();
    216             showToast("The key has been invalidated, please try again.\n");
    217             return false;
    218         } catch (NoSuchPaddingException | KeyStoreException | CertificateException | UnrecoverableKeyException | IOException
    219                 | NoSuchAlgorithmException | InvalidKeyException e) {
    220             throw new RuntimeException("Failed to init Cipher", e);
    221         }
    222     }
    223 
    224     protected void showAuthenticationScreen() {
    225         mFingerprintDialog = new FingerprintAuthDialogFragment();
    226         mFingerprintDialog.setActivity(this);
    227         mFingerprintDialog.show(getFragmentManager(), "fingerprint_dialog");
    228     }
    229 
    230     protected void showToast(String message) {
    231         Toast.makeText(this, message, Toast.LENGTH_LONG).show();
    232     }
    233 
    234     public static class FingerprintAuthDialogFragment extends DialogFragment {
    235 
    236         private FingerprintBoundKeysTest mActivity;
    237         private CancellationSignal mCancellationSignal;
    238         private FingerprintManager mFingerprintManager;
    239         private FingerprintManagerCallback mFingerprintManagerCallback;
    240         private boolean mSelfCancelled;
    241 
    242         class FingerprintManagerCallback extends FingerprintManager.AuthenticationCallback {
    243             @Override
    244             public void onAuthenticationError(int errMsgId, CharSequence errString) {
    245                 if (DEBUG) {
    246                     Log.i(TAG,"onAuthenticationError: id=" + errMsgId + "; str=" + errString);
    247                 }
    248                 if (!mSelfCancelled) {
    249                     showToast(errString.toString());
    250                 }
    251             }
    252 
    253             @Override
    254             public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
    255                 showToast(helpString.toString());
    256             }
    257 
    258             @Override
    259             public void onAuthenticationFailed() {
    260                 if (DEBUG) {
    261                     Log.i(TAG,"onAuthenticationFailed");
    262                 }
    263                 showToast(getString(R.string.sec_fp_auth_failed));
    264             }
    265 
    266             @Override
    267             public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
    268                 if (DEBUG) {
    269                     Log.i(TAG,"onAuthenticationSucceeded");
    270                 }
    271                 if (mActivity.tryEncrypt()) {
    272                     showToast("Test passed.");
    273                     mActivity.getPassButton().setEnabled(true);
    274                     FingerprintAuthDialogFragment.this.dismiss();
    275                 } else {
    276                     showToast("Test failed. Key not accessible after auth");
    277                 }
    278             }
    279         }
    280 
    281         @Override
    282         public void onDismiss(DialogInterface dialog) {
    283             mCancellationSignal.cancel();
    284             mSelfCancelled = true;
    285         }
    286 
    287         private void setActivity(FingerprintBoundKeysTest activity) {
    288             mActivity = activity;
    289         }
    290 
    291         private void showToast(String message) {
    292             Toast.makeText(getContext(), message, Toast.LENGTH_LONG)
    293                 .show();
    294         }
    295 
    296         @Override
    297         public Dialog onCreateDialog(Bundle savedInstanceState) {
    298             mCancellationSignal = new CancellationSignal();
    299             mSelfCancelled = false;
    300             mFingerprintManager =
    301                     (FingerprintManager) getContext().getSystemService(Context.FINGERPRINT_SERVICE);
    302             mFingerprintManagerCallback = new FingerprintManagerCallback();
    303             mFingerprintManager.authenticate(
    304                     new FingerprintManager.CryptoObject(mActivity.mCipher),
    305                     mCancellationSignal, 0, mFingerprintManagerCallback, null);
    306             AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    307             builder.setMessage(R.string.sec_fp_dialog_message);
    308             return builder.create();
    309         }
    310 
    311     }
    312 }
    313 
    314 
    315