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 
AuthenticationBoundKeyTestActivity()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
onCreate(Bundle savedInstanceState)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 
LockscreenCountDownTester()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
onFinish()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
onTick(long millisUntilFinished)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
setupTests(ArrayTestListAdapter adapter)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                         if (tryEncryptWithFingerprintKey(initFingerprintEncryptionCipher())) {
177                             showToast("Test failed. Key accessible without auth.");
178                             setTestResult(mFingerprintBoundKeyTest, TestResult.TEST_RESULT_FAILED);
179                         } else {
180                             mFingerprintCipher = initFingerprintEncryptionCipher();
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 
checkPreconditions()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 
getKeyName(int testType)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      */
createKey(int testType)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 
loadSecretKey(int testType)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 
tryEncryptWithLockscreenKey()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 
initFingerprintEncryptionCipher()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 
tryEncryptWithFingerprintKey(Cipher cipher)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
handleActivityResult(int requestCode, int resultCode, Intent data)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 
showToast(String message)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
onAuthenticationError(int errMsgId, CharSequence errString)340             public void onAuthenticationError(int errMsgId, CharSequence errString) {
341                 if (!mSelfCancelled) {
342                     showToast(errString.toString());
343                 }
344             }
345 
346             @Override
onAuthenticationHelp(int helpMsgId, CharSequence helpString)347             public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
348                 showToast(helpString.toString());
349             }
350 
351             @Override
onAuthenticationFailed()352             public void onAuthenticationFailed() {
353                 showToast(getString(R.string.sec_fp_auth_failed));
354             }
355 
356             @Override
onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result)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
onDismiss(DialogInterface dialog)372         public void onDismiss(DialogInterface dialog) {
373             mCancellationSignal.cancel();
374             mSelfCancelled = true;
375         }
376 
setActivity(AuthenticationBoundKeyTestActivity activity)377         private void setActivity(AuthenticationBoundKeyTestActivity activity) {
378             mActivity = activity;
379         }
380 
showToast(String message)381         private void showToast(String message) {
382             Toast.makeText(getContext(), message, Toast.LENGTH_LONG)
383                 .show();
384         }
385 
386 
387         @Override
onCreateDialog(Bundle savedInstanceState)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