/* * 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.Intent; import android.content.SharedPreferences; 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.view.Menu; import android.view.MenuItem; 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.KeyPairGenerator; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Signature; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.security.spec.ECGenParameterSpec; 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 */ public static final String KEY_NAME = "my_key"; private static final int FINGERPRINT_PERMISSION_REQUEST_CODE = 0; @Inject KeyguardManager mKeyguardManager; @Inject FingerprintManager mFingerprintManager; @Inject FingerprintAuthenticationDialogFragment mFragment; @Inject KeyStore mKeyStore; @Inject KeyPairGenerator mKeyPairGenerator; @Inject Signature mSignature; @Inject SharedPreferences mSharedPreferences; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ((InjectedApplication) getApplication()).inject(this); requestPermissions(new String[]{Manifest.permission.USE_FINGERPRINT}, FINGERPRINT_PERMISSION_REQUEST_CODE); } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] state) { if (requestCode == FINGERPRINT_PERMISSION_REQUEST_CODE && 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; } if (!mFingerprintManager.hasEnrolledFingerprints()) { purchaseButton.setEnabled(false); // This happens when no fingerprints are registered. Toast.makeText(this, "Go to 'Settings -> Security -> Fingerprint' and register at least one fingerprint", Toast.LENGTH_LONG).show(); return; } createKeyPair(); 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 (initSignature()) { // 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(mSignature)); boolean useFingerprintPreference = mSharedPreferences .getBoolean(getString(R.string.use_fingerprint_to_authenticate_key), true); if (useFingerprintPreference) { mFragment.setStage( FingerprintAuthenticationDialogFragment.Stage.FINGERPRINT); } else { mFragment.setStage( FingerprintAuthenticationDialogFragment.Stage.PASSWORD); } mFragment.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 mFragment.setStage( FingerprintAuthenticationDialogFragment.Stage.NEW_FINGERPRINT_ENROLLED); mFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG); } } }); } } /** * Initialize the {@link Signature} instance with the created key in the * {@link #createKeyPair()} method. * * @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 initSignature() { try { mKeyStore.load(null); PrivateKey key = (PrivateKey) mKeyStore.getKey(KEY_NAME, null); mSignature.initSign(key); return true; } catch (KeyPermanentlyInvalidatedException e) { return false; } catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException | NoSuchAlgorithmException | InvalidKeyException e) { throw new RuntimeException("Failed to init Cipher", e); } } public void onPurchased(byte[] signature) { showConfirmation(signature); } public void onPurchaseFailed() { Toast.makeText(this, R.string.purchase_fail, Toast.LENGTH_SHORT).show(); } // 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 */)); } } /** * Generates an asymmetric key pair in the Android Keystore. Every use of the private key must * be authorized by the user authenticating with fingerprint. Public key use is unrestricted. */ public void createKeyPair() { // 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 { // Set the alias of the entry in Android KeyStore where the key will appear // and the constrains (purposes) in the constructor of the Builder mKeyPairGenerator.initialize( new KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_SIGN) .setDigests(KeyProperties.DIGEST_SHA256) .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1")) // Require the user to authenticate with a fingerprint to authorize // every use of the private key .setUserAuthenticationRequired(true) .build()); mKeyPairGenerator.generateKeyPair(); } catch (InvalidAlgorithmParameterException e) { throw new RuntimeException(e); } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.action_settings) { Intent intent = new Intent(this, SettingsActivity.class); startActivity(intent); return true; } return super.onOptionsItemSelected(item); } }