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