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             // Set the alias of the entry in Android KeyStore where the key will appear
212             // and the constrains (purposes) in the constructor of the Builder
213             KeyGenParameterSpec.Builder builder;
214             builder = new KeyGenParameterSpec.Builder(getKeyName(testType),
215                     KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
216                     .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
217                     .setUserAuthenticationRequired(true)
218                     .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);
219             if (testType == LOCKSCREEN) {
220                 // Require that the user unlocked the lockscreen in the last 5 seconds
221                 builder.setUserAuthenticationValidityDurationSeconds(
222                         AUTHENTICATION_DURATION_SECONDS);
223             }
224             KeyGenerator keyGenerator = KeyGenerator.getInstance(
225                     KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_NAME);
226             keyGenerator.init(builder.build());
227             keyGenerator.generateKey();
228         } catch (NoSuchAlgorithmException | NoSuchProviderException
229                 | InvalidAlgorithmParameterException e) {
230             throw new RuntimeException("Failed to create a symmetric key", e);
231         }
232     }
233 
loadSecretKey(int testType)234     private SecretKey loadSecretKey(int testType) {
235         try {
236             KeyStore keyStore = KeyStore.getInstance(KEYSTORE_NAME);
237             keyStore.load(null);
238             return (SecretKey) keyStore.getKey(getKeyName(testType), null);
239         } catch (UnrecoverableKeyException  | CertificateException |KeyStoreException | IOException
240                 | NoSuchAlgorithmException e) {
241             throw new RuntimeException("Failed to load a symmetric key", e);
242         }
243     }
244 
tryEncryptWithLockscreenKey()245     private boolean tryEncryptWithLockscreenKey() {
246         try {
247             // Try encrypting something, it will only work if the user authenticated within
248             // the last AUTHENTICATION_DURATION_SECONDS seconds.
249             Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
250             cipher.init(Cipher.ENCRYPT_MODE, loadSecretKey(LOCKSCREEN));
251             cipher.doFinal(SECRET_BYTE_ARRAY);
252             return true;
253         } catch (UserNotAuthenticatedException e) {
254             // User is not authenticated, let's authenticate with device credentials.
255             return false;
256         } catch (KeyPermanentlyInvalidatedException e) {
257             // This happens if the lock screen has been disabled or reset after the key was
258             // generated.
259             createKey(LOCKSCREEN);
260             showToast("Set up lockscreen after test ran. Retry the test.");
261             return false;
262         } catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException
263                 | NoSuchPaddingException | NoSuchAlgorithmException e) {
264             throw new RuntimeException("Encrypt with lockscreen-bound key failed", e);
265         }
266     }
267 
initFingerprintEncryptionCipher()268     private Cipher initFingerprintEncryptionCipher() {
269         try {
270             Cipher cipher =  Cipher.getInstance(CIPHER_TRANSFORMATION);
271             cipher.init(Cipher.ENCRYPT_MODE, loadSecretKey(FINGERPRINT));
272             return cipher;
273         } catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
274             return null;
275         } catch (KeyPermanentlyInvalidatedException e) {
276             // This happens if the lock screen has been disabled or reset after the key was
277             // generated after the key was generated.
278             createKey(FINGERPRINT);
279             showToast("Set up lockscreen after test ran. Retry the test.");
280             return null;
281         } catch (InvalidKeyException e) {
282             throw new RuntimeException("Init cipher with fingerprint-bound key failed", e);
283         }
284     }
285 
tryEncryptWithFingerprintKey(Cipher cipher)286     private boolean tryEncryptWithFingerprintKey(Cipher cipher) {
287 
288         try {
289             cipher.doFinal(SECRET_BYTE_ARRAY);
290             return true;
291         } catch (IllegalBlockSizeException e) {
292             // Cannot encrypt, key is unavailable
293             return false;
294         } catch (BadPaddingException e) {
295             throw new RuntimeException(e);
296         }
297     }
298 
299     @Override
handleActivityResult(int requestCode, int resultCode, Intent data)300     protected void handleActivityResult(int requestCode, int resultCode, Intent data) {
301         switch (requestCode) {
302             case CONFIRM_CREDENTIALS_REQUEST_CODE:
303                 if (resultCode == RESULT_OK) {
304                     if (tryEncryptWithLockscreenKey()) {
305                         setTestResult(mLockScreenBoundKeyTest, TestResult.TEST_RESULT_PASSED);
306                     } else {
307                         showToast("Test failed. Key not accessible after auth");
308                         setTestResult(mLockScreenBoundKeyTest, TestResult.TEST_RESULT_FAILED);
309                     }
310                 } else {
311                     showToast("Lockscreen challenge canceled.");
312                     setTestResult(mLockScreenBoundKeyTest, TestResult.TEST_RESULT_FAILED);
313                 }
314                 break;
315             default:
316                 super.handleActivityResult(requestCode, resultCode, data);
317         }
318     }
319 
showToast(String message)320     private void showToast(String message) {
321         Toast.makeText(this, message, Toast.LENGTH_LONG).show();
322     }
323 
324     public class FingerprintAuthDialogFragment extends DialogFragment {
325 
326         private CancellationSignal mCancellationSignal;
327         private FingerprintManagerCallback mFingerprintManagerCallback;
328         private boolean mSelfCancelled;
329 
330         class FingerprintManagerCallback extends FingerprintManager.AuthenticationCallback {
331             @Override
onAuthenticationError(int errMsgId, CharSequence errString)332             public void onAuthenticationError(int errMsgId, CharSequence errString) {
333                 if (!mSelfCancelled) {
334                     showToast(errString.toString());
335                 }
336             }
337 
338             @Override
onAuthenticationHelp(int helpMsgId, CharSequence helpString)339             public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
340                 showToast(helpString.toString());
341             }
342 
343             @Override
onAuthenticationFailed()344             public void onAuthenticationFailed() {
345                 showToast(getString(R.string.sec_fp_auth_failed));
346             }
347 
348             @Override
onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result)349             public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
350                 if (tryEncryptWithFingerprintKey(mFingerprintCipher)) {
351                     showToast("Test passed.");
352                     setTestResult(mFingerprintBoundKeyTest, TestResult.TEST_RESULT_PASSED);
353                 } else {
354                     showToast("Test failed. Key not accessible after auth");
355                     setTestResult(mFingerprintBoundKeyTest, TestResult.TEST_RESULT_FAILED);
356                 }
357                 FingerprintAuthDialogFragment.this.dismiss();
358             }
359         }
360 
361         @Override
onDismiss(DialogInterface dialog)362         public void onDismiss(DialogInterface dialog) {
363             mCancellationSignal.cancel();
364             mSelfCancelled = true;
365         }
366 
367         @Override
onCreateDialog(Bundle savedInstanceState)368         public Dialog onCreateDialog(Bundle savedInstanceState) {
369             mCancellationSignal = new CancellationSignal();
370             mSelfCancelled = false;
371             mFingerprintManagerCallback = new FingerprintManagerCallback();
372             mFingerprintManager.authenticate(new FingerprintManager.CryptoObject(mFingerprintCipher),
373                     mCancellationSignal, 0, mFingerprintManagerCallback, null);
374             AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
375             builder.setMessage(R.string.sec_fp_dialog_message);
376             return builder.create();
377         }
378 
379     }
380 
381 }
382