aboutsummaryrefslogtreecommitdiff
path: root/security
diff options
context:
space:
mode:
authorTakeshi Hagikura <thagikura@google.com>2016-07-07 18:23:43 +0900
committerTakeshi Hagikura <thagikura@google.com>2016-07-07 18:26:48 +0900
commit821f126d7c5df09a2e97f42042f944e178ebf9bd (patch)
tree8015d238b16e10e5da5b97c31404b18000e460cd /security
parent4ebae4e3472e82612b27a5618790a94af2ec134f (diff)
downloadandroid-821f126d7c5df09a2e97f42042f944e178ebf9bd.tar.gz
Add another purchase button that uses a key which isn't invalidated even if a
new fingerprint is enrolled. This CL includes the changes in the previous http://ag/913148 and the change to fix the build by "make FingerprintDialog" (The previous http://ag/913148 broke the build so was reverted) Change-Id: Ia2dd793ce041b4720b7c7105041e942d82a35f86
Diffstat (limited to 'security')
-rw-r--r--security/FingerprintDialog/Application/src/main/Android.mk3
-rw-r--r--security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/FingerprintAuthenticationDialogFragment.java6
-rw-r--r--security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/MainActivity.java180
-rw-r--r--security/FingerprintDialog/Application/src/main/res/layout/activity_main.xml23
-rw-r--r--security/FingerprintDialog/Application/src/main/res/values/strings.xml4
-rw-r--r--security/FingerprintDialog/template-params.xml2
6 files changed, 155 insertions, 63 deletions
diff --git a/security/FingerprintDialog/Application/src/main/Android.mk b/security/FingerprintDialog/Application/src/main/Android.mk
index 1f694d87..1b020d98 100644
--- a/security/FingerprintDialog/Application/src/main/Android.mk
+++ b/security/FingerprintDialog/Application/src/main/Android.mk
@@ -14,7 +14,8 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-appcompat
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-appcompat \
+ android-support-annotations
LOCAL_MODULE_TAGS := samples
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := FingerprintDialog
diff --git a/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/FingerprintAuthenticationDialogFragment.java b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/FingerprintAuthenticationDialogFragment.java
index cb240dba..05375733 100644
--- a/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/FingerprintAuthenticationDialogFragment.java
+++ b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/FingerprintAuthenticationDialogFragment.java
@@ -181,12 +181,12 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment
if (mUseFingerprintFutureCheckBox.isChecked()) {
// Re-create the key so that fingerprints including new ones are validated.
- mActivity.createKey();
+ mActivity.createKey(MainActivity.DEFAULT_KEY_NAME, true);
mStage = Stage.FINGERPRINT;
}
}
mPassword.setText("");
- mActivity.onPurchased(false /* without Fingerprint */);
+ mActivity.onPurchased(false /* without Fingerprint */, null);
dismiss();
}
@@ -243,7 +243,7 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment
public void onAuthenticated() {
// Callback from FingerprintUiHelper. Let the activity know that authentication was
// successful.
- mActivity.onPurchased(true /* withFingerprint */);
+ mActivity.onPurchased(true /* withFingerprint */, mCryptoObject);
dismiss();
}
diff --git a/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/MainActivity.java b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/MainActivity.java
index 77c87886..400b2d69 100644
--- a/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/MainActivity.java
+++ b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/MainActivity.java
@@ -21,11 +21,13 @@ import android.app.KeyguardManager;
import android.content.Intent;
import android.content.SharedPreferences;
import android.hardware.fingerprint.FingerprintManager;
+import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
+import android.support.annotation.Nullable;
import android.util.Base64;
import android.util.Log;
import android.view.Menu;
@@ -61,12 +63,11 @@ public class MainActivity extends Activity {
private static final String DIALOG_FRAGMENT_TAG = "myFragment";
private static final String SECRET_MESSAGE = "Very secret message";
- /** Alias for our key in the Android Key Store */
- private static final String KEY_NAME = "my_key";
+ private static final String KEY_NAME_NOT_INVALIDATED = "key_not_invalidated";
+ static final String DEFAULT_KEY_NAME = "default_key";
private KeyStore mKeyStore;
private KeyGenerator mKeyGenerator;
- private Cipher mCipher;
private SharedPreferences mSharedPreferences;
@Override
@@ -85,8 +86,13 @@ public class MainActivity extends Activity {
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
throw new RuntimeException("Failed to get an instance of KeyGenerator", e);
}
+ Cipher defaultCipher;
+ Cipher cipherNotInvalidated;
try {
- mCipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ defaultCipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ + KeyProperties.BLOCK_MODE_CBC + "/"
+ + KeyProperties.ENCRYPTION_PADDING_PKCS7);
+ cipherNotInvalidated = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
@@ -97,6 +103,22 @@ public class MainActivity extends Activity {
KeyguardManager keyguardManager = getSystemService(KeyguardManager.class);
FingerprintManager fingerprintManager = getSystemService(FingerprintManager.class);
Button purchaseButton = (Button) findViewById(R.id.purchase_button);
+ Button purchaseButtonNotInvalidated = (Button) findViewById(
+ R.id.purchase_button_not_invalidated);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ purchaseButtonNotInvalidated.setEnabled(true);
+ purchaseButtonNotInvalidated.setOnClickListener(
+ new PurchaseButtonClickListener(cipherNotInvalidated,
+ KEY_NAME_NOT_INVALIDATED));
+ } else {
+ // Hide the purchase button which uses a non-invalidated key
+ // if the app doesn't work on Android N preview
+ purchaseButtonNotInvalidated.setVisibility(View.GONE);
+ findViewById(R.id.purchase_button_not_invalidated_description)
+ .setVisibility(View.GONE);
+ }
+
if (!keyguardManager.isKeyguardSecure()) {
// Show a message that the user hasn't set up a fingerprint or lock screen.
Toast.makeText(this,
@@ -104,6 +126,7 @@ public class MainActivity extends Activity {
+ "Go to 'Settings -> Security -> Fingerprint' to set up a fingerprint",
Toast.LENGTH_LONG).show();
purchaseButton.setEnabled(false);
+ purchaseButtonNotInvalidated.setEnabled(false);
return;
}
@@ -119,62 +142,27 @@ public class MainActivity extends Activity {
Toast.LENGTH_LONG).show();
return;
}
- createKey();
+ createKey(DEFAULT_KEY_NAME, true);
+ createKey(KEY_NAME_NOT_INVALIDATED, false);
purchaseButton.setEnabled(true);
- purchaseButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- findViewById(R.id.confirmation_message).setVisibility(View.GONE);
- findViewById(R.id.encrypted_message).setVisibility(View.GONE);
-
- // Set up the crypto object for later. The object will be authenticated by use
- // of the fingerprint.
- if (initCipher()) {
- // Show the fingerprint dialog. The user has the option to use the fingerprint
- // with crypto, or you can fall back to using a server-side verified password.
- FingerprintAuthenticationDialogFragment fragment
- = new FingerprintAuthenticationDialogFragment();
- fragment.setCryptoObject(new FingerprintManager.CryptoObject(mCipher));
- boolean useFingerprintPreference = mSharedPreferences
- .getBoolean(getString(R.string.use_fingerprint_to_authenticate_key),
- true);
- if (useFingerprintPreference) {
- fragment.setStage(
- FingerprintAuthenticationDialogFragment.Stage.FINGERPRINT);
- } else {
- fragment.setStage(
- FingerprintAuthenticationDialogFragment.Stage.PASSWORD);
- }
- fragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
- } else {
- // This happens if the lock screen has been disabled or or a fingerprint got
- // enrolled. Thus show the dialog to authenticate with their password first
- // and ask the user if they want to authenticate with fingerprints in the
- // future
- FingerprintAuthenticationDialogFragment fragment
- = new FingerprintAuthenticationDialogFragment();
- fragment.setCryptoObject(new FingerprintManager.CryptoObject(mCipher));
- fragment.setStage(
- FingerprintAuthenticationDialogFragment.Stage.NEW_FINGERPRINT_ENROLLED);
- fragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
- }
- }
- });
+ purchaseButton.setOnClickListener(
+ new PurchaseButtonClickListener(defaultCipher, DEFAULT_KEY_NAME));
}
/**
- * Initialize the {@link Cipher} instance with the created key in the {@link #createKey()}
- * method.
+ * Initialize the {@link Cipher} instance with the created key in the
+ * {@link #createKey(String, boolean)} method.
*
+ * @param keyName the key name to init the cipher
* @return {@code true} if initialization is successful, {@code false} if the lock screen has
* been disabled or reset after the key was generated, or if a fingerprint got enrolled after
* the key was generated.
*/
- private boolean initCipher() {
+ private boolean initCipher(Cipher cipher, String keyName) {
try {
mKeyStore.load(null);
- SecretKey key = (SecretKey) mKeyStore.getKey(KEY_NAME, null);
- mCipher.init(Cipher.ENCRYPT_MODE, key);
+ SecretKey key = (SecretKey) mKeyStore.getKey(keyName, null);
+ cipher.init(Cipher.ENCRYPT_MODE, key);
return true;
} catch (KeyPermanentlyInvalidatedException e) {
return false;
@@ -184,11 +172,19 @@ public class MainActivity extends Activity {
}
}
- public void onPurchased(boolean withFingerprint) {
+ /**
+ * Proceed the purchase operation
+ *
+ * @param withFingerprint {@code true} if the purchase was made by using a fingerprint
+ * @param cryptoObject the Crypto object
+ */
+ public void onPurchased(boolean withFingerprint,
+ @Nullable FingerprintManager.CryptoObject cryptoObject) {
if (withFingerprint) {
// If the user has authenticated with fingerprint, verify that using cryptography and
// then show the confirmation message.
- tryEncrypt();
+ assert cryptoObject != null;
+ tryEncrypt(cryptoObject.getCipher());
} else {
// Authentication happened with backup password. Just show the confirmation message.
showConfirmation(null);
@@ -209,9 +205,9 @@ public class MainActivity extends Activity {
* Tries to encrypt some data with the generated key in {@link #createKey} which is
* only works if the user has just authenticated via fingerprint.
*/
- private void tryEncrypt() {
+ private void tryEncrypt(Cipher cipher) {
try {
- byte[] encrypted = mCipher.doFinal(SECRET_MESSAGE.getBytes());
+ byte[] encrypted = cipher.doFinal(SECRET_MESSAGE.getBytes());
showConfirmation(encrypted);
} catch (BadPaddingException | IllegalBlockSizeException e) {
Toast.makeText(this, "Failed to encrypt the data with the generated key. "
@@ -223,8 +219,18 @@ public class MainActivity extends Activity {
/**
* Creates a symmetric key in the Android Key Store which can only be used after the user has
* authenticated with fingerprint.
+ *
+ * @param keyName the name of the key to be created
+ * @param invalidatedByBiometricEnrollment if {@code false} is passed, the created key will not
+ * be invalidated even if a new fingerprint is enrolled.
+ * The default value is {@code true}, so passing
+ * {@code true} doesn't change the behavior
+ * (the key will be invalidated if a new fingerprint is
+ * enrolled.). Note that this parameter is only valid if
+ * the app works on Android N developer preview.
+ *
*/
- public void createKey() {
+ public void createKey(String keyName, boolean invalidatedByBiometricEnrollment) {
// The enrolling flow for fingerprint. This is where you ask the user to set up fingerprint
// for your flow. Use of keys is necessary if you need to know if the set of
// enrolled fingerprints has changed.
@@ -232,15 +238,25 @@ public class MainActivity extends Activity {
mKeyStore.load(null);
// Set the alias of the entry in Android KeyStore where the key will appear
// and the constrains (purposes) in the constructor of the Builder
- mKeyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME,
+
+ KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(keyName,
KeyProperties.PURPOSE_ENCRYPT |
KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
// Require the user to authenticate with a fingerprint to authorize every use
// of the key
.setUserAuthenticationRequired(true)
- .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
- .build());
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);
+
+ // This is a workaround to avoid crashes on devices whose API level is < 24
+ // because KeyGenParameterSpec.Builder#setInvalidatedByBiometricEnrollment is only
+ // visible on API level +24.
+ // Ideally there should be a compat library for KeyGenParameterSpec.Builder but
+ // which isn't available yet.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ builder.setInvalidatedByBiometricEnrollment(invalidatedByBiometricEnrollment);
+ }
+ mKeyGenerator.init(builder.build());
mKeyGenerator.generateKey();
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
| CertificateException | IOException e) {
@@ -265,4 +281,54 @@ public class MainActivity extends Activity {
}
return super.onOptionsItemSelected(item);
}
+
+ private class PurchaseButtonClickListener implements View.OnClickListener {
+
+ Cipher mCipher;
+ String mKeyName;
+
+ PurchaseButtonClickListener(Cipher cipher, String keyName) {
+ mCipher = cipher;
+ mKeyName = keyName;
+ }
+
+ @Override
+ public void onClick(View view) {
+ findViewById(R.id.confirmation_message).setVisibility(View.GONE);
+ findViewById(R.id.encrypted_message).setVisibility(View.GONE);
+
+ // Set up the crypto object for later. The object will be authenticated by use
+ // of the fingerprint.
+ if (initCipher(mCipher, mKeyName)) {
+
+ // Show the fingerprint dialog. The user has the option to use the fingerprint with
+ // crypto, or you can fall back to using a server-side verified password.
+ FingerprintAuthenticationDialogFragment fragment
+ = new FingerprintAuthenticationDialogFragment();
+ fragment.setCryptoObject(new FingerprintManager.CryptoObject(mCipher));
+ boolean useFingerprintPreference = mSharedPreferences
+ .getBoolean(getString(R.string.use_fingerprint_to_authenticate_key),
+ true);
+ if (useFingerprintPreference) {
+ fragment.setStage(
+ FingerprintAuthenticationDialogFragment.Stage.FINGERPRINT);
+ } else {
+ fragment.setStage(
+ FingerprintAuthenticationDialogFragment.Stage.PASSWORD);
+ }
+ fragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
+ } else {
+ // This happens if the lock screen has been disabled or or a fingerprint got
+ // enrolled. Thus show the dialog to authenticate with their password first
+ // and ask the user if they want to authenticate with fingerprints in the
+ // future
+ FingerprintAuthenticationDialogFragment fragment
+ = new FingerprintAuthenticationDialogFragment();
+ fragment.setCryptoObject(new FingerprintManager.CryptoObject(mCipher));
+ fragment.setStage(
+ FingerprintAuthenticationDialogFragment.Stage.NEW_FINGERPRINT_ENROLLED);
+ fragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
+ }
+ }
+ }
}
diff --git a/security/FingerprintDialog/Application/src/main/res/layout/activity_main.xml b/security/FingerprintDialog/Application/src/main/res/layout/activity_main.xml
index 8f30557b..075899f8 100644
--- a/security/FingerprintDialog/Application/src/main/res/layout/activity_main.xml
+++ b/security/FingerprintDialog/Application/src/main/res/layout/activity_main.xml
@@ -77,8 +77,27 @@
android:layout_gravity="end"
android:textColor="?android:attr/textColorPrimaryInverse"
android:text="@string/purchase"
- android:id="@+id/purchase_button"
- android:layout_alignParentEnd="true"/>
+ android:id="@+id/purchase_button" />
+
+ <Button style="@android:style/Widget.Material.Button.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="4dp"
+ android:layout_gravity="end"
+ android:textColor="?android:attr/textColorPrimaryInverse"
+ android:text="@string/purchase_not_invalidated"
+ android:id="@+id/purchase_button_not_invalidated" />
+
+ <TextView
+ android:id="@+id/purchase_button_not_invalidated_description"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="4dp"
+ android:gravity="end"
+ android:textAlignment="gravity"
+ android:text="@string/purchase_button_not_invalidated_description"
+ />
<TextView
android:id="@+id/confirmation_message"
diff --git a/security/FingerprintDialog/Application/src/main/res/values/strings.xml b/security/FingerprintDialog/Application/src/main/res/values/strings.xml
index 77d1176e..86f200c9 100644
--- a/security/FingerprintDialog/Application/src/main/res/values/strings.xml
+++ b/security/FingerprintDialog/Application/src/main/res/values/strings.xml
@@ -31,6 +31,10 @@
<string name="fingerprint_hint">Touch sensor</string>
<string name="password_description">Enter your store password to continue</string>
<string name="purchase">Purchase</string>
+ <string name="purchase_not_invalidated">Purchase not invalidated</string>
+ <string name="purchase_button_not_invalidated_description">
+ You can proceed to purchase with this button \n even if a new fingerprint is enrolled
+ </string>
<string name="fingerprint_not_recognized">Fingerprint not recognized. Try again</string>
<string name="fingerprint_success">Fingerprint recognized</string>
<string name="item_title">White Mesh Pluto Backpack</string>
diff --git a/security/FingerprintDialog/template-params.xml b/security/FingerprintDialog/template-params.xml
index c13a82f6..78184922 100644
--- a/security/FingerprintDialog/template-params.xml
+++ b/security/FingerprintDialog/template-params.xml
@@ -25,6 +25,8 @@
<package>com.example.android.fingerprintdialog</package>
<minSdk>23</minSdk>
+ <targetSdkVersion>24</targetSdkVersion>
+ <compileSdkVersion>24</compileSdkVersion>
<strings>
<intro>