1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.cts.verifier.security; 18 19 import com.android.cts.verifier.PassFailButtons; 20 import com.android.cts.verifier.R; 21 22 import android.Manifest; 23 import android.app.AlertDialog; 24 import android.app.Dialog; 25 import android.app.DialogFragment; 26 import android.app.KeyguardManager; 27 import android.content.Context; 28 import android.content.DialogInterface; 29 import android.content.Intent; 30 import android.content.pm.PackageManager; 31 import android.hardware.fingerprint.FingerprintManager; 32 import android.os.Bundle; 33 import android.os.CancellationSignal; 34 import android.util.Log; 35 import android.security.keystore.KeyGenParameterSpec; 36 import android.security.keystore.KeyPermanentlyInvalidatedException; 37 import android.security.keystore.KeyProperties; 38 import android.security.keystore.UserNotAuthenticatedException; 39 import android.view.View; 40 import android.view.View.OnClickListener; 41 import android.widget.Button; 42 import android.widget.Toast; 43 44 import java.io.IOException; 45 import java.security.InvalidAlgorithmParameterException; 46 import java.security.InvalidKeyException; 47 import java.security.KeyStore; 48 import java.security.KeyStoreException; 49 import java.security.NoSuchAlgorithmException; 50 import java.security.NoSuchProviderException; 51 import java.security.UnrecoverableKeyException; 52 import java.security.cert.CertificateException; 53 54 import javax.crypto.BadPaddingException; 55 import javax.crypto.Cipher; 56 import javax.crypto.IllegalBlockSizeException; 57 import javax.crypto.KeyGenerator; 58 import javax.crypto.NoSuchPaddingException; 59 import javax.crypto.SecretKey; 60 61 public class FingerprintBoundKeysTest extends PassFailButtons.Activity { 62 private static final boolean DEBUG = false; 63 private static final String TAG = "FingerprintBoundKeysTest"; 64 65 /** Alias for our key in the Android Key Store. */ 66 private static final String KEY_NAME = "my_key"; 67 private static final byte[] SECRET_BYTE_ARRAY = new byte[] {1, 2, 3, 4, 5, 6}; 68 private static final int AUTHENTICATION_DURATION_SECONDS = 5; 69 private static final int CONFIRM_CREDENTIALS_REQUEST_CODE = 1; 70 private static final int FINGERPRINT_PERMISSION_REQUEST_CODE = 0; 71 72 private FingerprintManager mFingerprintManager; 73 private KeyguardManager mKeyguardManager; 74 private FingerprintAuthDialogFragment mFingerprintDialog; 75 private Cipher mCipher; 76 77 @Override 78 protected void onCreate(Bundle savedInstanceState) { 79 super.onCreate(savedInstanceState); 80 setContentView(R.layout.sec_screen_lock_keys_main); 81 setPassFailButtonClickListeners(); 82 setInfoResources(R.string.sec_fingerprint_bound_key_test, R.string.sec_fingerprint_bound_key_test_info, -1); 83 getPassButton().setEnabled(false); 84 requestPermissions(new String[]{Manifest.permission.USE_FINGERPRINT}, 85 FINGERPRINT_PERMISSION_REQUEST_CODE); 86 } 87 88 @Override 89 public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] state) { 90 if (requestCode == FINGERPRINT_PERMISSION_REQUEST_CODE && state[0] == PackageManager.PERMISSION_GRANTED) { 91 mFingerprintManager = (FingerprintManager) getSystemService(Context.FINGERPRINT_SERVICE); 92 mKeyguardManager = (KeyguardManager) getSystemService(KeyguardManager.class); 93 Button startTestButton = (Button) findViewById(R.id.sec_start_test_button); 94 95 if (!mKeyguardManager.isKeyguardSecure()) { 96 // Show a message that the user hasn't set up a lock screen. 97 showToast( "Secure lock screen hasn't been set up.\n" 98 + "Go to 'Settings -> Security -> Screen lock' to set up a lock screen"); 99 startTestButton.setEnabled(false); 100 return; 101 } else if (!mFingerprintManager.hasEnrolledFingerprints()) { 102 showToast("No fingerprints enrolled.\n" 103 + "Go to 'Settings -> Security -> Fingerprint' to set up a fingerprint"); 104 startTestButton.setEnabled(false); 105 return; 106 } 107 108 startTestButton.setOnClickListener(new OnClickListener() { 109 @Override 110 public void onClick(View v) { 111 createKey(); 112 prepareEncrypt(); 113 if (tryEncrypt()) { 114 showToast("Test failed. Key accessible without auth."); 115 } else { 116 showAuthenticationScreen(); 117 } 118 } 119 }); 120 } 121 } 122 123 /** 124 * Creates a symmetric key in the Android Key Store which can only be used after the user has 125 * authenticated with device credentials within the last X seconds. 126 */ 127 private void createKey() { 128 // Generate a key to decrypt payment credentials, tokens, etc. 129 // This will most likely be a registration step for the user when they are setting up your app. 130 try { 131 KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); 132 keyStore.load(null); 133 KeyGenerator keyGenerator = KeyGenerator.getInstance( 134 KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); 135 136 // Set the alias of the entry in Android KeyStore where the key will appear 137 // and the constrains (purposes) in the constructor of the Builder 138 keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME, 139 KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) 140 .setBlockModes(KeyProperties.BLOCK_MODE_CBC) 141 .setUserAuthenticationRequired(true) 142 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) 143 .build()); 144 keyGenerator.generateKey(); 145 if (DEBUG) { 146 Log.i(TAG, "createKey: [1]: done"); 147 } 148 } catch (NoSuchAlgorithmException | NoSuchProviderException 149 | InvalidAlgorithmParameterException | KeyStoreException 150 | CertificateException | IOException e) { 151 if (DEBUG) { 152 Log.i(TAG, "createKey: [2]: failed"); 153 } 154 throw new RuntimeException("Failed to create a symmetric key", e); 155 } 156 } 157 158 /** 159 * create and init cipher; has to be done before we do auth 160 */ 161 private boolean prepareEncrypt() { 162 return encryptInternal(false); 163 } 164 165 /** 166 * Tries to encrypt some data with the generated key in {@link #createKey} which is 167 * only works if the user has just authenticated via device credentials. 168 * has to be run after successful auth, in order to succeed 169 */ 170 protected boolean tryEncrypt() { 171 return encryptInternal(true); 172 } 173 174 protected Cipher getCipher() { 175 return mCipher; 176 } 177 178 private boolean encryptInternal(boolean doEncrypt) { 179 try { 180 KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); 181 keyStore.load(null); 182 SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_NAME, null); 183 if (DEBUG) { 184 Log.i(TAG, "encryptInternal: [1]: key retrieved"); 185 } 186 if (!doEncrypt) { 187 if (mCipher == null) { 188 mCipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" 189 + KeyProperties.BLOCK_MODE_CBC + "/" 190 + KeyProperties.ENCRYPTION_PADDING_PKCS7); 191 } 192 mCipher.init(Cipher.ENCRYPT_MODE, secretKey); 193 if (DEBUG) { 194 Log.i(TAG, "encryptInternal: [2]: cipher initialized"); 195 } 196 } else { 197 mCipher.doFinal(SECRET_BYTE_ARRAY); 198 if (DEBUG) { 199 Log.i(TAG, "encryptInternal: [3]: encryption performed"); 200 } 201 } 202 return true; 203 } catch (BadPaddingException | IllegalBlockSizeException e) { 204 // this happens in "no-error" scenarios routinely; 205 // All we want it to see the event in the log; 206 // Extra exception info is not valuable 207 if (DEBUG) { 208 Log.i(TAG, "encryptInternal: [4]: Encryption failed"); 209 } 210 return false; 211 } catch (KeyPermanentlyInvalidatedException e) { 212 // Extra exception info is not of big value, but let's have it, 213 // since this is an unlikely sutuation and potential error condition 214 Log.w(TAG, "encryptInternal: [5]: Key invalidated", e); 215 createKey(); 216 showToast("The key has been invalidated, please try again.\n"); 217 return false; 218 } catch (NoSuchPaddingException | KeyStoreException | CertificateException | UnrecoverableKeyException | IOException 219 | NoSuchAlgorithmException | InvalidKeyException e) { 220 throw new RuntimeException("Failed to init Cipher", e); 221 } 222 } 223 224 protected void showAuthenticationScreen() { 225 mFingerprintDialog = new FingerprintAuthDialogFragment(); 226 mFingerprintDialog.setActivity(this); 227 mFingerprintDialog.show(getFragmentManager(), "fingerprint_dialog"); 228 } 229 230 protected void showToast(String message) { 231 Toast.makeText(this, message, Toast.LENGTH_LONG).show(); 232 } 233 234 public static class FingerprintAuthDialogFragment extends DialogFragment { 235 236 private FingerprintBoundKeysTest mActivity; 237 private CancellationSignal mCancellationSignal; 238 private FingerprintManager mFingerprintManager; 239 private FingerprintManagerCallback mFingerprintManagerCallback; 240 private boolean mSelfCancelled; 241 242 class FingerprintManagerCallback extends FingerprintManager.AuthenticationCallback { 243 @Override 244 public void onAuthenticationError(int errMsgId, CharSequence errString) { 245 if (DEBUG) { 246 Log.i(TAG,"onAuthenticationError: id=" + errMsgId + "; str=" + errString); 247 } 248 if (!mSelfCancelled) { 249 showToast(errString.toString()); 250 } 251 } 252 253 @Override 254 public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { 255 showToast(helpString.toString()); 256 } 257 258 @Override 259 public void onAuthenticationFailed() { 260 if (DEBUG) { 261 Log.i(TAG,"onAuthenticationFailed"); 262 } 263 showToast(getString(R.string.sec_fp_auth_failed)); 264 } 265 266 @Override 267 public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { 268 if (DEBUG) { 269 Log.i(TAG,"onAuthenticationSucceeded"); 270 } 271 if (mActivity.tryEncrypt()) { 272 showToast("Test passed."); 273 mActivity.getPassButton().setEnabled(true); 274 FingerprintAuthDialogFragment.this.dismiss(); 275 } else { 276 showToast("Test failed. Key not accessible after auth"); 277 } 278 } 279 } 280 281 @Override 282 public void onDismiss(DialogInterface dialog) { 283 mCancellationSignal.cancel(); 284 mSelfCancelled = true; 285 } 286 287 private void setActivity(FingerprintBoundKeysTest activity) { 288 mActivity = activity; 289 } 290 291 private void showToast(String message) { 292 Toast.makeText(getContext(), message, Toast.LENGTH_LONG) 293 .show(); 294 } 295 296 @Override 297 public Dialog onCreateDialog(Bundle savedInstanceState) { 298 mCancellationSignal = new CancellationSignal(); 299 mSelfCancelled = false; 300 mFingerprintManager = 301 (FingerprintManager) getContext().getSystemService(Context.FINGERPRINT_SERVICE); 302 mFingerprintManagerCallback = new FingerprintManagerCallback(); 303 mFingerprintManager.authenticate( 304 new FingerprintManager.CryptoObject(mActivity.mCipher), 305 mCancellationSignal, 0, mFingerprintManagerCallback, null); 306 AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 307 builder.setMessage(R.string.sec_fp_dialog_message); 308 return builder.create(); 309 } 310 311 } 312 } 313 314 315