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