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