1*90c8c64dSAndroid Build Coastguard Worker /* 2*90c8c64dSAndroid Build Coastguard Worker * Copyright (C) 2015 The Android Open Source Project 3*90c8c64dSAndroid Build Coastguard Worker * 4*90c8c64dSAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License"); 5*90c8c64dSAndroid Build Coastguard Worker * you may not use this file except in compliance with the License. 6*90c8c64dSAndroid Build Coastguard Worker * You may obtain a copy of the License at 7*90c8c64dSAndroid Build Coastguard Worker * 8*90c8c64dSAndroid Build Coastguard Worker * http://www.apache.org/licenses/LICENSE-2.0 9*90c8c64dSAndroid Build Coastguard Worker * 10*90c8c64dSAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software 11*90c8c64dSAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS, 12*90c8c64dSAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*90c8c64dSAndroid Build Coastguard Worker * See the License for the specific language governing permissions and 14*90c8c64dSAndroid Build Coastguard Worker * limitations under the License 15*90c8c64dSAndroid Build Coastguard Worker */ 16*90c8c64dSAndroid Build Coastguard Worker 17*90c8c64dSAndroid Build Coastguard Worker package com.example.android.asymmetricfingerprintdialog; 18*90c8c64dSAndroid Build Coastguard Worker 19*90c8c64dSAndroid Build Coastguard Worker import android.app.Activity; 20*90c8c64dSAndroid Build Coastguard Worker import android.app.KeyguardManager; 21*90c8c64dSAndroid Build Coastguard Worker import android.content.Intent; 22*90c8c64dSAndroid Build Coastguard Worker import android.content.SharedPreferences; 23*90c8c64dSAndroid Build Coastguard Worker import android.hardware.fingerprint.FingerprintManager; 24*90c8c64dSAndroid Build Coastguard Worker import android.os.Bundle; 25*90c8c64dSAndroid Build Coastguard Worker import android.security.keystore.KeyGenParameterSpec; 26*90c8c64dSAndroid Build Coastguard Worker import android.security.keystore.KeyPermanentlyInvalidatedException; 27*90c8c64dSAndroid Build Coastguard Worker import android.security.keystore.KeyProperties; 28*90c8c64dSAndroid Build Coastguard Worker import android.util.Base64; 29*90c8c64dSAndroid Build Coastguard Worker import android.view.Menu; 30*90c8c64dSAndroid Build Coastguard Worker import android.view.MenuItem; 31*90c8c64dSAndroid Build Coastguard Worker import android.view.View; 32*90c8c64dSAndroid Build Coastguard Worker import android.widget.Button; 33*90c8c64dSAndroid Build Coastguard Worker import android.widget.TextView; 34*90c8c64dSAndroid Build Coastguard Worker import android.widget.Toast; 35*90c8c64dSAndroid Build Coastguard Worker 36*90c8c64dSAndroid Build Coastguard Worker import java.io.IOException; 37*90c8c64dSAndroid Build Coastguard Worker import java.security.InvalidAlgorithmParameterException; 38*90c8c64dSAndroid Build Coastguard Worker import java.security.InvalidKeyException; 39*90c8c64dSAndroid Build Coastguard Worker import java.security.KeyPairGenerator; 40*90c8c64dSAndroid Build Coastguard Worker import java.security.KeyStore; 41*90c8c64dSAndroid Build Coastguard Worker import java.security.KeyStoreException; 42*90c8c64dSAndroid Build Coastguard Worker import java.security.NoSuchAlgorithmException; 43*90c8c64dSAndroid Build Coastguard Worker import java.security.PrivateKey; 44*90c8c64dSAndroid Build Coastguard Worker import java.security.Signature; 45*90c8c64dSAndroid Build Coastguard Worker import java.security.UnrecoverableKeyException; 46*90c8c64dSAndroid Build Coastguard Worker import java.security.cert.CertificateException; 47*90c8c64dSAndroid Build Coastguard Worker import java.security.spec.ECGenParameterSpec; 48*90c8c64dSAndroid Build Coastguard Worker 49*90c8c64dSAndroid Build Coastguard Worker import javax.inject.Inject; 50*90c8c64dSAndroid Build Coastguard Worker 51*90c8c64dSAndroid Build Coastguard Worker /** 52*90c8c64dSAndroid Build Coastguard Worker * Main entry point for the sample, showing a backpack and "Purchase" button. 53*90c8c64dSAndroid Build Coastguard Worker */ 54*90c8c64dSAndroid Build Coastguard Worker public class MainActivity extends Activity { 55*90c8c64dSAndroid Build Coastguard Worker 56*90c8c64dSAndroid Build Coastguard Worker private static final String DIALOG_FRAGMENT_TAG = "myFragment"; 57*90c8c64dSAndroid Build Coastguard Worker /** Alias for our key in the Android Key Store */ 58*90c8c64dSAndroid Build Coastguard Worker public static final String KEY_NAME = "my_key"; 59*90c8c64dSAndroid Build Coastguard Worker 60*90c8c64dSAndroid Build Coastguard Worker @Inject KeyguardManager mKeyguardManager; 61*90c8c64dSAndroid Build Coastguard Worker @Inject FingerprintManager mFingerprintManager; 62*90c8c64dSAndroid Build Coastguard Worker @Inject FingerprintAuthenticationDialogFragment mFragment; 63*90c8c64dSAndroid Build Coastguard Worker @Inject KeyStore mKeyStore; 64*90c8c64dSAndroid Build Coastguard Worker @Inject KeyPairGenerator mKeyPairGenerator; 65*90c8c64dSAndroid Build Coastguard Worker @Inject Signature mSignature; 66*90c8c64dSAndroid Build Coastguard Worker @Inject SharedPreferences mSharedPreferences; 67*90c8c64dSAndroid Build Coastguard Worker 68*90c8c64dSAndroid Build Coastguard Worker @Override onCreate(Bundle savedInstanceState)69*90c8c64dSAndroid Build Coastguard Worker protected void onCreate(Bundle savedInstanceState) { 70*90c8c64dSAndroid Build Coastguard Worker super.onCreate(savedInstanceState); 71*90c8c64dSAndroid Build Coastguard Worker ((InjectedApplication) getApplication()).inject(this); 72*90c8c64dSAndroid Build Coastguard Worker 73*90c8c64dSAndroid Build Coastguard Worker setContentView(R.layout.activity_main); 74*90c8c64dSAndroid Build Coastguard Worker Button purchaseButton = (Button) findViewById(R.id.purchase_button); 75*90c8c64dSAndroid Build Coastguard Worker if (!mKeyguardManager.isKeyguardSecure()) { 76*90c8c64dSAndroid Build Coastguard Worker // Show a message that the user hasn't set up a fingerprint or lock screen. 77*90c8c64dSAndroid Build Coastguard Worker Toast.makeText(this, 78*90c8c64dSAndroid Build Coastguard Worker "Secure lock screen hasn't set up.\n" 79*90c8c64dSAndroid Build Coastguard Worker + "Go to 'Settings -> Security -> Fingerprint' to set up a fingerprint", 80*90c8c64dSAndroid Build Coastguard Worker Toast.LENGTH_LONG).show(); 81*90c8c64dSAndroid Build Coastguard Worker purchaseButton.setEnabled(false); 82*90c8c64dSAndroid Build Coastguard Worker return; 83*90c8c64dSAndroid Build Coastguard Worker } 84*90c8c64dSAndroid Build Coastguard Worker //noinspection ResourceType 85*90c8c64dSAndroid Build Coastguard Worker if (!mFingerprintManager.hasEnrolledFingerprints()) { 86*90c8c64dSAndroid Build Coastguard Worker purchaseButton.setEnabled(false); 87*90c8c64dSAndroid Build Coastguard Worker // This happens when no fingerprints are registered. 88*90c8c64dSAndroid Build Coastguard Worker Toast.makeText(this, 89*90c8c64dSAndroid Build Coastguard Worker "Go to 'Settings -> Security -> Fingerprint' and register at least one fingerprint", 90*90c8c64dSAndroid Build Coastguard Worker Toast.LENGTH_LONG).show(); 91*90c8c64dSAndroid Build Coastguard Worker return; 92*90c8c64dSAndroid Build Coastguard Worker } 93*90c8c64dSAndroid Build Coastguard Worker createKeyPair(); 94*90c8c64dSAndroid Build Coastguard Worker purchaseButton.setEnabled(true); 95*90c8c64dSAndroid Build Coastguard Worker purchaseButton.setOnClickListener(new View.OnClickListener() { 96*90c8c64dSAndroid Build Coastguard Worker @Override 97*90c8c64dSAndroid Build Coastguard Worker public void onClick(View v) { 98*90c8c64dSAndroid Build Coastguard Worker findViewById(R.id.confirmation_message).setVisibility(View.GONE); 99*90c8c64dSAndroid Build Coastguard Worker findViewById(R.id.encrypted_message).setVisibility(View.GONE); 100*90c8c64dSAndroid Build Coastguard Worker 101*90c8c64dSAndroid Build Coastguard Worker // Set up the crypto object for later. The object will be authenticated by use 102*90c8c64dSAndroid Build Coastguard Worker // of the fingerprint. 103*90c8c64dSAndroid Build Coastguard Worker if (initSignature()) { 104*90c8c64dSAndroid Build Coastguard Worker 105*90c8c64dSAndroid Build Coastguard Worker // Show the fingerprint dialog. The user has the option to use the fingerprint with 106*90c8c64dSAndroid Build Coastguard Worker // crypto, or you can fall back to using a server-side verified password. 107*90c8c64dSAndroid Build Coastguard Worker mFragment.setCryptoObject(new FingerprintManager.CryptoObject(mSignature)); 108*90c8c64dSAndroid Build Coastguard Worker boolean useFingerprintPreference = mSharedPreferences 109*90c8c64dSAndroid Build Coastguard Worker .getBoolean(getString(R.string.use_fingerprint_to_authenticate_key), 110*90c8c64dSAndroid Build Coastguard Worker true); 111*90c8c64dSAndroid Build Coastguard Worker if (useFingerprintPreference) { 112*90c8c64dSAndroid Build Coastguard Worker mFragment.setStage( 113*90c8c64dSAndroid Build Coastguard Worker FingerprintAuthenticationDialogFragment.Stage.FINGERPRINT); 114*90c8c64dSAndroid Build Coastguard Worker } else { 115*90c8c64dSAndroid Build Coastguard Worker mFragment.setStage( 116*90c8c64dSAndroid Build Coastguard Worker FingerprintAuthenticationDialogFragment.Stage.PASSWORD); 117*90c8c64dSAndroid Build Coastguard Worker } 118*90c8c64dSAndroid Build Coastguard Worker mFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG); 119*90c8c64dSAndroid Build Coastguard Worker } else { 120*90c8c64dSAndroid Build Coastguard Worker // This happens if the lock screen has been disabled or or a fingerprint got 121*90c8c64dSAndroid Build Coastguard Worker // enrolled. Thus show the dialog to authenticate with their password first 122*90c8c64dSAndroid Build Coastguard Worker // and ask the user if they want to authenticate with fingerprints in the 123*90c8c64dSAndroid Build Coastguard Worker // future 124*90c8c64dSAndroid Build Coastguard Worker mFragment.setStage( 125*90c8c64dSAndroid Build Coastguard Worker FingerprintAuthenticationDialogFragment.Stage.NEW_FINGERPRINT_ENROLLED); 126*90c8c64dSAndroid Build Coastguard Worker mFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG); 127*90c8c64dSAndroid Build Coastguard Worker } 128*90c8c64dSAndroid Build Coastguard Worker } 129*90c8c64dSAndroid Build Coastguard Worker }); 130*90c8c64dSAndroid Build Coastguard Worker } 131*90c8c64dSAndroid Build Coastguard Worker 132*90c8c64dSAndroid Build Coastguard Worker /** 133*90c8c64dSAndroid Build Coastguard Worker * Initialize the {@link Signature} instance with the created key in the 134*90c8c64dSAndroid Build Coastguard Worker * {@link #createKeyPair()} method. 135*90c8c64dSAndroid Build Coastguard Worker * 136*90c8c64dSAndroid Build Coastguard Worker * @return {@code true} if initialization is successful, {@code false} if the lock screen has 137*90c8c64dSAndroid Build Coastguard Worker * been disabled or reset after the key was generated, or if a fingerprint got enrolled after 138*90c8c64dSAndroid Build Coastguard Worker * the key was generated. 139*90c8c64dSAndroid Build Coastguard Worker */ initSignature()140*90c8c64dSAndroid Build Coastguard Worker private boolean initSignature() { 141*90c8c64dSAndroid Build Coastguard Worker try { 142*90c8c64dSAndroid Build Coastguard Worker mKeyStore.load(null); 143*90c8c64dSAndroid Build Coastguard Worker PrivateKey key = (PrivateKey) mKeyStore.getKey(KEY_NAME, null); 144*90c8c64dSAndroid Build Coastguard Worker mSignature.initSign(key); 145*90c8c64dSAndroid Build Coastguard Worker return true; 146*90c8c64dSAndroid Build Coastguard Worker } catch (KeyPermanentlyInvalidatedException e) { 147*90c8c64dSAndroid Build Coastguard Worker return false; 148*90c8c64dSAndroid Build Coastguard Worker } catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException 149*90c8c64dSAndroid Build Coastguard Worker | NoSuchAlgorithmException | InvalidKeyException e) { 150*90c8c64dSAndroid Build Coastguard Worker throw new RuntimeException("Failed to init Cipher", e); 151*90c8c64dSAndroid Build Coastguard Worker } 152*90c8c64dSAndroid Build Coastguard Worker } 153*90c8c64dSAndroid Build Coastguard Worker onPurchased(byte[] signature)154*90c8c64dSAndroid Build Coastguard Worker public void onPurchased(byte[] signature) { 155*90c8c64dSAndroid Build Coastguard Worker showConfirmation(signature); 156*90c8c64dSAndroid Build Coastguard Worker } 157*90c8c64dSAndroid Build Coastguard Worker onPurchaseFailed()158*90c8c64dSAndroid Build Coastguard Worker public void onPurchaseFailed() { 159*90c8c64dSAndroid Build Coastguard Worker Toast.makeText(this, R.string.purchase_fail, Toast.LENGTH_SHORT).show(); 160*90c8c64dSAndroid Build Coastguard Worker } 161*90c8c64dSAndroid Build Coastguard Worker 162*90c8c64dSAndroid Build Coastguard Worker // Show confirmation, if fingerprint was used show crypto information. showConfirmation(byte[] encrypted)163*90c8c64dSAndroid Build Coastguard Worker private void showConfirmation(byte[] encrypted) { 164*90c8c64dSAndroid Build Coastguard Worker findViewById(R.id.confirmation_message).setVisibility(View.VISIBLE); 165*90c8c64dSAndroid Build Coastguard Worker if (encrypted != null) { 166*90c8c64dSAndroid Build Coastguard Worker TextView v = (TextView) findViewById(R.id.encrypted_message); 167*90c8c64dSAndroid Build Coastguard Worker v.setVisibility(View.VISIBLE); 168*90c8c64dSAndroid Build Coastguard Worker v.setText(Base64.encodeToString(encrypted, 0 /* flags */)); 169*90c8c64dSAndroid Build Coastguard Worker } 170*90c8c64dSAndroid Build Coastguard Worker } 171*90c8c64dSAndroid Build Coastguard Worker 172*90c8c64dSAndroid Build Coastguard Worker /** 173*90c8c64dSAndroid Build Coastguard Worker * Generates an asymmetric key pair in the Android Keystore. Every use of the private key must 174*90c8c64dSAndroid Build Coastguard Worker * be authorized by the user authenticating with fingerprint. Public key use is unrestricted. 175*90c8c64dSAndroid Build Coastguard Worker */ createKeyPair()176*90c8c64dSAndroid Build Coastguard Worker public void createKeyPair() { 177*90c8c64dSAndroid Build Coastguard Worker // The enrolling flow for fingerprint. This is where you ask the user to set up fingerprint 178*90c8c64dSAndroid Build Coastguard Worker // for your flow. Use of keys is necessary if you need to know if the set of 179*90c8c64dSAndroid Build Coastguard Worker // enrolled fingerprints has changed. 180*90c8c64dSAndroid Build Coastguard Worker try { 181*90c8c64dSAndroid Build Coastguard Worker // Set the alias of the entry in Android KeyStore where the key will appear 182*90c8c64dSAndroid Build Coastguard Worker // and the constrains (purposes) in the constructor of the Builder 183*90c8c64dSAndroid Build Coastguard Worker mKeyPairGenerator.initialize( 184*90c8c64dSAndroid Build Coastguard Worker new KeyGenParameterSpec.Builder(KEY_NAME, 185*90c8c64dSAndroid Build Coastguard Worker KeyProperties.PURPOSE_SIGN) 186*90c8c64dSAndroid Build Coastguard Worker .setDigests(KeyProperties.DIGEST_SHA256) 187*90c8c64dSAndroid Build Coastguard Worker .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1")) 188*90c8c64dSAndroid Build Coastguard Worker // Require the user to authenticate with a fingerprint to authorize 189*90c8c64dSAndroid Build Coastguard Worker // every use of the private key 190*90c8c64dSAndroid Build Coastguard Worker .setUserAuthenticationRequired(true) 191*90c8c64dSAndroid Build Coastguard Worker .build()); 192*90c8c64dSAndroid Build Coastguard Worker mKeyPairGenerator.generateKeyPair(); 193*90c8c64dSAndroid Build Coastguard Worker } catch (InvalidAlgorithmParameterException e) { 194*90c8c64dSAndroid Build Coastguard Worker throw new RuntimeException(e); 195*90c8c64dSAndroid Build Coastguard Worker } 196*90c8c64dSAndroid Build Coastguard Worker } 197*90c8c64dSAndroid Build Coastguard Worker 198*90c8c64dSAndroid Build Coastguard Worker @Override onCreateOptionsMenu(Menu menu)199*90c8c64dSAndroid Build Coastguard Worker public boolean onCreateOptionsMenu(Menu menu) { 200*90c8c64dSAndroid Build Coastguard Worker getMenuInflater().inflate(R.menu.menu_main, menu); 201*90c8c64dSAndroid Build Coastguard Worker return true; 202*90c8c64dSAndroid Build Coastguard Worker } 203*90c8c64dSAndroid Build Coastguard Worker 204*90c8c64dSAndroid Build Coastguard Worker @Override onOptionsItemSelected(MenuItem item)205*90c8c64dSAndroid Build Coastguard Worker public boolean onOptionsItemSelected(MenuItem item) { 206*90c8c64dSAndroid Build Coastguard Worker int id = item.getItemId(); 207*90c8c64dSAndroid Build Coastguard Worker 208*90c8c64dSAndroid Build Coastguard Worker if (id == R.id.action_settings) { 209*90c8c64dSAndroid Build Coastguard Worker Intent intent = new Intent(this, SettingsActivity.class); 210*90c8c64dSAndroid Build Coastguard Worker startActivity(intent); 211*90c8c64dSAndroid Build Coastguard Worker return true; 212*90c8c64dSAndroid Build Coastguard Worker } 213*90c8c64dSAndroid Build Coastguard Worker return super.onOptionsItemSelected(item); 214*90c8c64dSAndroid Build Coastguard Worker } 215*90c8c64dSAndroid Build Coastguard Worker } 216