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