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