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