| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| |
| package com.example.android.fingerprintdialog; |
| |
| import android.Manifest; |
| import android.app.Activity; |
| import android.app.KeyguardManager; |
| import android.content.pm.PackageManager; |
| import android.hardware.fingerprint.FingerprintManager; |
| import android.os.Bundle; |
| import android.security.keystore.KeyGenParameterSpec; |
| import android.security.keystore.KeyPermanentlyInvalidatedException; |
| import android.security.keystore.KeyProperties; |
| import android.util.Base64; |
| import android.util.Log; |
| import android.view.View; |
| import android.widget.Button; |
| import android.widget.TextView; |
| import android.widget.Toast; |
| |
| import java.io.IOException; |
| import java.security.InvalidAlgorithmParameterException; |
| import java.security.InvalidKeyException; |
| import java.security.KeyStore; |
| import java.security.KeyStoreException; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.UnrecoverableKeyException; |
| import java.security.cert.CertificateException; |
| |
| import javax.crypto.BadPaddingException; |
| import javax.crypto.Cipher; |
| import javax.crypto.IllegalBlockSizeException; |
| import javax.crypto.KeyGenerator; |
| import javax.crypto.SecretKey; |
| import javax.inject.Inject; |
| |
| /** |
| * Main entry point for the sample, showing a backpack and "Purchase" button. |
| */ |
| public class MainActivity extends Activity { |
| |
| private static final String TAG = MainActivity.class.getSimpleName(); |
| |
| 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"; |
| |
| @Inject KeyguardManager mKeyguardManager; |
| @Inject FingerprintAuthenticationDialogFragment mFragment; |
| @Inject KeyStore mKeyStore; |
| @Inject KeyGenerator mKeyGenerator; |
| @Inject Cipher mCipher; |
| |
| @Override |
| protected void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| ((InjectedApplication) getApplication()).inject(this); |
| |
| requestPermissions(new String[]{Manifest.permission.USE_FINGERPRINT}, 0); |
| } |
| |
| @Override |
| public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] state) { |
| if (requestCode == 0 && state[0] == PackageManager.PERMISSION_GRANTED) { |
| setContentView(R.layout.activity_main); |
| Button purchaseButton = (Button) findViewById(R.id.purchase_button); |
| if (!mKeyguardManager.isKeyguardSecure()) { |
| // Show a message that the user hasn't set up a fingerprint or lock screen. |
| Toast.makeText(this, |
| "Secure lock screen hasn't set up.\n" |
| + "Go to 'Settings -> Security -> Fingerprint' to set up a fingerprint", |
| Toast.LENGTH_LONG).show(); |
| purchaseButton.setEnabled(false); |
| return; |
| } |
| createKey(); |
| purchaseButton.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| |
| // 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. |
| mFragment.setCryptoObject(new FingerprintManager.CryptoObject(mCipher)); |
| mFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG); |
| } |
| }); |
| |
| // Set up the crypto object for later. The object will be authenticated by use |
| // of the fingerprint. |
| initCipher(); |
| } |
| } |
| |
| private void initCipher() { |
| try { |
| mKeyStore.load(null); |
| SecretKey key = (SecretKey) mKeyStore.getKey(KEY_NAME, null); |
| mCipher.init(Cipher.ENCRYPT_MODE, key); |
| } catch (KeyPermanentlyInvalidatedException e) { |
| // This happens 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. |
| Toast.makeText(this, "Keys are invalidated after created. Retry the purchase\n" |
| + e.getMessage(), |
| Toast.LENGTH_LONG).show(); |
| } catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException |
| | NoSuchAlgorithmException | InvalidKeyException e) { |
| throw new RuntimeException("Failed to init Cipher", e); |
| } |
| } |
| |
| public void onPurchased(boolean withFingerprint) { |
| findViewById(R.id.purchase_button).setVisibility(View.GONE); |
| if (withFingerprint) { |
| // If the user has authenticated with fingerprint, verify that using cryptography and |
| // then show the confirmation message. |
| tryEncrypt(); |
| } else { |
| // Authentication happened with backup password. Just show the confirmation message. |
| showConfirmation(null); |
| } |
| } |
| |
| // Show confirmation, if fingerprint was used show crypto information. |
| private void showConfirmation(byte[] encrypted) { |
| findViewById(R.id.confirmation_message).setVisibility(View.VISIBLE); |
| if (encrypted != null) { |
| TextView v = (TextView) findViewById(R.id.encrypted_message); |
| v.setVisibility(View.VISIBLE); |
| v.setText(Base64.encodeToString(encrypted, 0 /* flags */)); |
| } |
| } |
| |
| /** |
| * 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() { |
| try { |
| byte[] encrypted = mCipher.doFinal(SECRET_MESSAGE.getBytes()); |
| showConfirmation(encrypted); |
| } catch (BadPaddingException | IllegalBlockSizeException e) { |
| Toast.makeText(this, "Failed to encrypt the data with the generated key. " |
| + "Retry the purchase", Toast.LENGTH_LONG).show(); |
| Log.e(TAG, "Failed to encrypt the data with the generated key." + e.getMessage()); |
| } |
| } |
| |
| /** |
| * Creates a symmetric key in the Android Key Store which can only be used after the user has |
| * authenticated with fingerprint. |
| */ |
| private void createKey() { |
| // 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. |
| try { |
| 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, |
| 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()); |
| mKeyGenerator.generateKey(); |
| } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException |
| | CertificateException | IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| } |