Home | History | Annotate | Download | only in managedprovisioning
      1 package com.android.cts.verifier.managedprovisioning;
      2 
      3 import android.Manifest;
      4 import android.app.Activity;
      5 import android.app.AlertDialog;
      6 import android.app.Dialog;
      7 import android.app.DialogFragment;
      8 import android.app.KeyguardManager;
      9 import android.content.Context;
     10 import android.content.DialogInterface;
     11 import android.content.Intent;
     12 import android.hardware.fingerprint.FingerprintManager;
     13 import android.os.Bundle;
     14 import android.os.CancellationSignal;
     15 import android.os.CountDownTimer;
     16 import android.provider.Settings;
     17 import android.security.keystore.KeyGenParameterSpec;
     18 import android.security.keystore.KeyPermanentlyInvalidatedException;
     19 import android.security.keystore.KeyProperties;
     20 import android.security.keystore.UserNotAuthenticatedException;
     21 import android.view.View;
     22 import android.view.View.OnClickListener;
     23 import android.widget.Toast;
     24 
     25 import com.android.cts.verifier.ArrayTestListAdapter;
     26 import com.android.cts.verifier.DialogTestListActivity;
     27 import com.android.cts.verifier.R;
     28 import com.android.cts.verifier.TestResult;
     29 
     30 import java.io.IOException;
     31 import java.security.InvalidAlgorithmParameterException;
     32 import java.security.InvalidKeyException;
     33 import java.security.KeyStore;
     34 import java.security.KeyStoreException;
     35 import java.security.NoSuchAlgorithmException;
     36 import java.security.NoSuchProviderException;
     37 import java.security.UnrecoverableKeyException;
     38 import java.security.cert.CertificateException;
     39 
     40 import javax.crypto.BadPaddingException;
     41 import javax.crypto.Cipher;
     42 import javax.crypto.IllegalBlockSizeException;
     43 import javax.crypto.KeyGenerator;
     44 import javax.crypto.NoSuchPaddingException;
     45 import javax.crypto.SecretKey;
     46 
     47 /**
     48  * Test device credential-bound keys in work profile.
     49  * Currently there are two types, one is keys bound to lockscreen passwords which can be configured
     50  * to remain available within a certain timeout after the latest successful user authentication.
     51  * The other is keys bound to fingerprint authentication which require explicit fingerprint
     52  * authentication before they can be accessed.
     53  */
     54 public class AuthenticationBoundKeyTestActivity extends DialogTestListActivity {
     55 
     56     public static final String ACTION_AUTH_BOUND_KEY_TEST =
     57             "com.android.cts.verifier.managedprovisioning.action.AUTH_BOUND_KEY_TEST";
     58 
     59     private static final int AUTHENTICATION_DURATION_SECONDS = 5;
     60     private static final String LOCKSCREEN_KEY_NAME = "mp_lockscreen_key";
     61     private static final String FINGERPRINT_KEY_NAME = "mp_fingerprint_key";
     62     private static final byte[] SECRET_BYTE_ARRAY = new byte[] {1, 2, 3, 4, 5, 6};
     63     private static final int CONFIRM_CREDENTIALS_REQUEST_CODE = 1;
     64     private static final int FINGERPRINT_PERMISSION_REQUEST_CODE = 0;
     65 
     66     private static final int LOCKSCREEN = 1;
     67     private static final int FINGERPRINT = 2;
     68 
     69     private static final String KEYSTORE_NAME = "AndroidKeyStore";
     70     private static final String CIPHER_TRANSFORMATION =  KeyProperties.KEY_ALGORITHM_AES + "/"
     71             + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7;
     72 
     73 
     74     private KeyguardManager mKeyguardManager;
     75     private FingerprintManager mFingerprintManager;
     76     private boolean mFingerprintSupported;
     77 
     78     private DialogTestListItem mLockScreenBoundKeyTest;
     79     private DialogTestListItem mFingerprintBoundKeyTest;
     80 
     81     private Cipher mFingerprintCipher;
     82 
     83     public AuthenticationBoundKeyTestActivity() {
     84         super(R.layout.provisioning_byod,
     85                 R.string.provisioning_byod_auth_bound_key,
     86                 R.string.provisioning_byod_auth_bound_key_info,
     87                 R.string.provisioning_byod_auth_bound_key_instruction);
     88     }
     89 
     90 
     91     @Override
     92     protected void onCreate(Bundle savedInstanceState) {
     93         mKeyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
     94         mFingerprintManager = (FingerprintManager) getSystemService(FINGERPRINT_SERVICE);
     95         mFingerprintSupported = mFingerprintManager != null
     96                 && mFingerprintManager.isHardwareDetected();
     97         // Need to have valid mFingerprintSupported value before calling super.onCreate() because
     98         // mFingerprintSupported is used in setupTests() which gets called by super.onCreate().
     99         super.onCreate(savedInstanceState);
    100 
    101         mPrepareTestButton.setText(R.string.provisioning_byod_auth_bound_key_set_up);
    102         mPrepareTestButton.setOnClickListener(new OnClickListener() {
    103             @Override
    104             public void onClick(View arg0) {
    105                 startActivity(new Intent(Settings.ACTION_SECURITY_SETTINGS));
    106             }
    107         });
    108         if (mFingerprintSupported) {
    109             requestPermissions(new String[] {Manifest.permission.USE_FINGERPRINT},
    110                     FINGERPRINT_PERMISSION_REQUEST_CODE);
    111         }
    112     }
    113 
    114     private class LockscreenCountDownTester extends CountDownTimer {
    115 
    116         private Toast mToast;
    117 
    118         public LockscreenCountDownTester() {
    119             // Wait for AUTHENTICATION_DURATION_SECONDS so the key is evicted before the real test.
    120             super(AUTHENTICATION_DURATION_SECONDS * 1000, 1000);
    121             mToast = Toast.makeText(AuthenticationBoundKeyTestActivity.this, "", Toast.LENGTH_SHORT);
    122         }
    123 
    124         @Override
    125         public void onFinish() {
    126             mToast.cancel();
    127             if (tryEncryptWithLockscreenKey()) {
    128                 showToast("Test failed. Key accessible without auth.");
    129                 setTestResult(mLockScreenBoundKeyTest, TestResult.TEST_RESULT_FAILED);
    130             } else {
    131                 // Start the Confirm Credentials screen.
    132                 Intent intent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null);
    133                 if (intent != null) {
    134                     startActivityForResult(intent, CONFIRM_CREDENTIALS_REQUEST_CODE);
    135                 } else {
    136                     showToast("Test failed. No lockscreen password exists.");
    137                     setTestResult(mLockScreenBoundKeyTest, TestResult.TEST_RESULT_FAILED);
    138                 }
    139             }
    140         }
    141 
    142         @Override
    143         public void onTick(long millisUntilFinished) {
    144             mToast.setText(String.format("Lockscreen challenge start in %d seconds..",
    145                     millisUntilFinished / 1000));
    146             mToast.show();
    147         }
    148     }
    149 
    150 
    151     @Override
    152     protected void setupTests(ArrayTestListAdapter adapter) {
    153         mLockScreenBoundKeyTest = new DialogTestListItem(this,
    154                 R.string.provisioning_byod_lockscreen_bound_key,
    155                 "BYOD_LockScreenBoundKeyTest") {
    156 
    157             @Override
    158             public void performTest(DialogTestListActivity activity) {
    159                 if (checkPreconditions()) {
    160                     createKey(LOCKSCREEN);
    161                     new LockscreenCountDownTester().start();
    162                 }
    163             }
    164         };
    165         adapter.add(mLockScreenBoundKeyTest);
    166         if (mFingerprintSupported) {
    167             AuthenticationBoundKeyTestActivity that = this;
    168             mFingerprintBoundKeyTest = new DialogTestListItem(this,
    169                     R.string.provisioning_byod_fingerprint_bound_key,
    170                     "BYOD_FingerprintBoundKeyTest") {
    171 
    172                 @Override
    173                 public void performTest(DialogTestListActivity activity) {
    174                     if (checkPreconditions()) {
    175                         createKey(FINGERPRINT);
    176                         mFingerprintCipher = initFingerprintEncryptionCipher();
    177                         if (tryEncryptWithFingerprintKey(mFingerprintCipher)) {
    178                             showToast("Test failed. Key accessible without auth.");
    179                             setTestResult(mFingerprintBoundKeyTest, TestResult.TEST_RESULT_FAILED);
    180                         } else {
    181                             FingerprintAuthDialogFragment fadf =
    182                                     new FingerprintAuthDialogFragment();
    183                             fadf.setActivity(that);
    184                             fadf.show(getFragmentManager(),"fingerprint_dialog");
    185                         }
    186                     }
    187                 }
    188             };
    189             adapter.add(mFingerprintBoundKeyTest);
    190         }
    191     }
    192 
    193     private boolean checkPreconditions() {
    194         if (!mKeyguardManager.isKeyguardSecure()) {
    195             showToast("Please set a lockscreen password.");
    196             return false;
    197         } else if (mFingerprintSupported && !mFingerprintManager.hasEnrolledFingerprints()) {
    198             showToast("Please enroll a fingerprint.");
    199             return false;
    200         } else {
    201             return true;
    202         }
    203     }
    204 
    205     private String getKeyName(int testType) {
    206         return testType == LOCKSCREEN ? LOCKSCREEN_KEY_NAME : FINGERPRINT_KEY_NAME;
    207     }
    208     /**
    209      * Creates a symmetric key in the Android Key Store which can only be used after the user has
    210      * authenticated with device credentials.
    211      */
    212     private void createKey(int testType) {
    213         try {
    214             KeyStore keyStore = KeyStore.getInstance(KEYSTORE_NAME);
    215             keyStore.load(null);
    216             // Set the alias of the entry in Android KeyStore where the key will appear
    217             // and the constrains (purposes) in the constructor of the Builder
    218             KeyGenParameterSpec.Builder builder;
    219             builder = new KeyGenParameterSpec.Builder(getKeyName(testType),
    220                     KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
    221                     .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
    222                     .setUserAuthenticationRequired(true)
    223                     .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);
    224             if (testType == LOCKSCREEN) {
    225                 // Require that the user unlocked the lockscreen in the last 5 seconds
    226                 builder.setUserAuthenticationValidityDurationSeconds(
    227                         AUTHENTICATION_DURATION_SECONDS);
    228             }
    229             KeyGenerator keyGenerator = KeyGenerator.getInstance(
    230                     KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_NAME);
    231             keyGenerator.init(builder.build());
    232             keyGenerator.generateKey();
    233         } catch (NoSuchAlgorithmException | NoSuchProviderException
    234                 | InvalidAlgorithmParameterException | KeyStoreException
    235                 | CertificateException | IOException e) {
    236             throw new RuntimeException("Failed to create a symmetric key", e);
    237         }
    238     }
    239 
    240     private SecretKey loadSecretKey(int testType) {
    241         try {
    242             KeyStore keyStore = KeyStore.getInstance(KEYSTORE_NAME);
    243             keyStore.load(null);
    244             return (SecretKey) keyStore.getKey(getKeyName(testType), null);
    245         } catch (UnrecoverableKeyException  | CertificateException |KeyStoreException | IOException
    246                 | NoSuchAlgorithmException e) {
    247             throw new RuntimeException("Failed to load a symmetric key", e);
    248         }
    249     }
    250 
    251     private boolean tryEncryptWithLockscreenKey() {
    252         try {
    253             // Try encrypting something, it will only work if the user authenticated within
    254             // the last AUTHENTICATION_DURATION_SECONDS seconds.
    255             Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
    256             cipher.init(Cipher.ENCRYPT_MODE, loadSecretKey(LOCKSCREEN));
    257             cipher.doFinal(SECRET_BYTE_ARRAY);
    258             return true;
    259         } catch (UserNotAuthenticatedException e) {
    260             // User is not authenticated, let's authenticate with device credentials.
    261             return false;
    262         } catch (KeyPermanentlyInvalidatedException e) {
    263             // This happens if the lock screen has been disabled or reset after the key was
    264             // generated.
    265             createKey(LOCKSCREEN);
    266             showToast("Set up lockscreen after test ran. Retry the test.");
    267             return false;
    268         } catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException
    269                 | NoSuchPaddingException | NoSuchAlgorithmException e) {
    270             throw new RuntimeException("Encrypt with lockscreen-bound key failed", e);
    271         }
    272     }
    273 
    274     private Cipher initFingerprintEncryptionCipher() {
    275         try {
    276             Cipher cipher =  Cipher.getInstance(CIPHER_TRANSFORMATION);
    277             cipher.init(Cipher.ENCRYPT_MODE, loadSecretKey(FINGERPRINT));
    278             return cipher;
    279         } catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
    280             return null;
    281         } catch (KeyPermanentlyInvalidatedException e) {
    282             // This happens if the lock screen has been disabled or reset after the key was
    283             // generated after the key was generated.
    284             createKey(FINGERPRINT);
    285             showToast("Set up lockscreen after test ran. Retry the test.");
    286             return null;
    287         } catch (InvalidKeyException e) {
    288             throw new RuntimeException("Init cipher with fingerprint-bound key failed", e);
    289         }
    290     }
    291 
    292     private boolean tryEncryptWithFingerprintKey(Cipher cipher) {
    293 
    294         try {
    295             cipher.doFinal(SECRET_BYTE_ARRAY);
    296             return true;
    297         } catch (IllegalBlockSizeException e) {
    298             // Cannot encrypt, key is unavailable
    299             return false;
    300         } catch (BadPaddingException e) {
    301             throw new RuntimeException(e);
    302         }
    303     }
    304 
    305     @Override
    306     protected void handleActivityResult(int requestCode, int resultCode, Intent data) {
    307         switch (requestCode) {
    308             case CONFIRM_CREDENTIALS_REQUEST_CODE:
    309                 if (resultCode == RESULT_OK) {
    310                     if (tryEncryptWithLockscreenKey()) {
    311                         setTestResult(mLockScreenBoundKeyTest, TestResult.TEST_RESULT_PASSED);
    312                     } else {
    313                         showToast("Test failed. Key not accessible after auth");
    314                         setTestResult(mLockScreenBoundKeyTest, TestResult.TEST_RESULT_FAILED);
    315                     }
    316                 } else {
    317                     showToast("Lockscreen challenge canceled.");
    318                     setTestResult(mLockScreenBoundKeyTest, TestResult.TEST_RESULT_FAILED);
    319                 }
    320                 break;
    321             default:
    322                 super.handleActivityResult(requestCode, resultCode, data);
    323         }
    324     }
    325 
    326     private void showToast(String message) {
    327         Toast.makeText(this, message, Toast.LENGTH_LONG).show();
    328     }
    329 
    330     static public class FingerprintAuthDialogFragment extends DialogFragment {
    331 
    332         private AuthenticationBoundKeyTestActivity mActivity;
    333         private CancellationSignal mCancellationSignal;
    334         private FingerprintManager mFingerprintManager;
    335         private FingerprintManagerCallback mFingerprintManagerCallback;
    336         private boolean mSelfCancelled;
    337 
    338         class FingerprintManagerCallback extends FingerprintManager.AuthenticationCallback {
    339             @Override
    340             public void onAuthenticationError(int errMsgId, CharSequence errString) {
    341                 if (!mSelfCancelled) {
    342                     showToast(errString.toString());
    343                 }
    344             }
    345 
    346             @Override
    347             public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
    348                 showToast(helpString.toString());
    349             }
    350 
    351             @Override
    352             public void onAuthenticationFailed() {
    353                 showToast(getString(R.string.sec_fp_auth_failed));
    354             }
    355 
    356             @Override
    357             public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
    358                 if (mActivity.tryEncryptWithFingerprintKey(mActivity.mFingerprintCipher)) {
    359                     showToast("Test passed.");
    360                     mActivity.setTestResult(mActivity.mFingerprintBoundKeyTest,
    361                             TestResult.TEST_RESULT_PASSED);
    362                 } else {
    363                     showToast("Test failed. Key not accessible after auth");
    364                     mActivity.setTestResult(mActivity.mFingerprintBoundKeyTest,
    365                             TestResult.TEST_RESULT_FAILED);
    366                 }
    367                 FingerprintAuthDialogFragment.this.dismiss();
    368             }
    369         }
    370 
    371         @Override
    372         public void onDismiss(DialogInterface dialog) {
    373             mCancellationSignal.cancel();
    374             mSelfCancelled = true;
    375         }
    376 
    377         private void setActivity(AuthenticationBoundKeyTestActivity activity) {
    378             mActivity = activity;
    379         }
    380 
    381         private void showToast(String message) {
    382             Toast.makeText(getContext(), message, Toast.LENGTH_LONG)
    383                 .show();
    384         }
    385 
    386 
    387         @Override
    388         public Dialog onCreateDialog(Bundle savedInstanceState) {
    389             mCancellationSignal = new CancellationSignal();
    390             mSelfCancelled = false;
    391             mFingerprintManager =
    392                     (FingerprintManager) getContext().getSystemService(Context.FINGERPRINT_SERVICE);
    393             mFingerprintManagerCallback = new FingerprintManagerCallback();
    394             mFingerprintManager.authenticate(
    395                     new FingerprintManager.CryptoObject(mActivity.mFingerprintCipher),
    396                     mCancellationSignal, 0, mFingerprintManagerCallback, null);
    397             AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    398             builder.setMessage(R.string.sec_fp_dialog_message);
    399             return builder.create();
    400         }
    401 
    402     }
    403 
    404 }
    405