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