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