FingerprintActivity.java 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. /*
  2. * Nextcloud Android client application
  3. *
  4. * @author Florian Lentz
  5. * Copyright (C) 2017 Florian Lentz
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  9. * License as published by the Free Software Foundation; either
  10. * version 3 of the License, or any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  16. *
  17. * You should have received a copy of the GNU Affero General Public
  18. * License along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. package com.owncloud.android.ui.activity;
  21. import android.Manifest;
  22. import android.app.KeyguardManager;
  23. import android.content.Context;
  24. import android.content.Intent;
  25. import android.content.pm.PackageManager;
  26. import android.content.res.ColorStateList;
  27. import android.graphics.Color;
  28. import android.graphics.drawable.ColorDrawable;
  29. import android.graphics.drawable.Drawable;
  30. import android.hardware.fingerprint.FingerprintManager;
  31. import android.os.Build;
  32. import android.os.Bundle;
  33. import android.os.CancellationSignal;
  34. import android.security.keystore.KeyGenParameterSpec;
  35. import android.security.keystore.KeyPermanentlyInvalidatedException;
  36. import android.security.keystore.KeyProperties;
  37. import android.support.annotation.RequiresApi;
  38. import android.support.v4.app.ActivityCompat;
  39. import android.support.v4.graphics.drawable.DrawableCompat;
  40. import android.support.v7.app.AppCompatActivity;
  41. import android.support.v7.widget.Toolbar;
  42. import android.view.KeyEvent;
  43. import android.widget.ImageView;
  44. import android.widget.TextView;
  45. import android.widget.Toast;
  46. import com.owncloud.android.MainApp;
  47. import com.owncloud.android.R;
  48. import com.owncloud.android.lib.common.utils.Log_OC;
  49. import com.owncloud.android.utils.AnalyticsUtils;
  50. import com.owncloud.android.utils.ThemeUtils;
  51. import java.io.IOException;
  52. import java.security.InvalidAlgorithmParameterException;
  53. import java.security.InvalidKeyException;
  54. import java.security.KeyStore;
  55. import java.security.KeyStoreException;
  56. import java.security.NoSuchAlgorithmException;
  57. import java.security.NoSuchProviderException;
  58. import java.security.UnrecoverableKeyException;
  59. import java.security.cert.CertificateException;
  60. import javax.crypto.Cipher;
  61. import javax.crypto.KeyGenerator;
  62. import javax.crypto.NoSuchPaddingException;
  63. import javax.crypto.SecretKey;
  64. /**
  65. * Activity to handle access to the app based on the fingerprint.
  66. */
  67. @RequiresApi(Build.VERSION_CODES.M)
  68. public class FingerprintActivity extends AppCompatActivity {
  69. private static final String TAG = FingerprintActivity.class.getSimpleName();
  70. private static final String SCREEN_NAME = "Fingerprint";
  71. public final static String KEY_CHECK_RESULT = "KEY_CHECK_RESULT";
  72. public final static String PREFERENCE_USE_FINGERPRINT = "use_fingerprint";
  73. public static final String ANDROID_KEY_STORE = "AndroidKeyStore";
  74. private KeyStore keyStore;
  75. // Variable used for storing the key in the Android Keystore container
  76. private static final String KEY_NAME = "Nextcloud";
  77. private Cipher cipher;
  78. private CancellationSignal cancellationSignal;
  79. /**
  80. * Initializes the activity.
  81. *
  82. * @param savedInstanceState Previously saved state - irrelevant in this case
  83. */
  84. protected void onCreate(Bundle savedInstanceState) {
  85. super.onCreate(savedInstanceState);
  86. setContentView(R.layout.fingerprintlock);
  87. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  88. getWindow().setStatusBarColor(ThemeUtils.primaryDarkColor());
  89. }
  90. Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
  91. toolbar.setTitleTextColor(ThemeUtils.fontColor());
  92. toolbar.setBackground(new ColorDrawable(ThemeUtils.primaryColor()));
  93. }
  94. private void startFingerprint() {
  95. TextView fingerprintTextView = (TextView) findViewById(R.id.scanfingerprinttext);
  96. FingerprintManager fingerprintManager =
  97. (FingerprintManager) MainApp.getAppContext().getSystemService(Context.FINGERPRINT_SERVICE);
  98. if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT)
  99. != PackageManager.PERMISSION_GRANTED) {
  100. return;
  101. }
  102. KeyguardManager keyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
  103. if (keyguardManager.isKeyguardSecure()) {
  104. generateKey();
  105. if (cipherInit()) {
  106. FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(cipher);
  107. FingerprintHandler.Callback callback = new FingerprintHandler.Callback() {
  108. @Override
  109. public void onAuthenticated() {
  110. fingerprintResult(true);
  111. }
  112. @Override
  113. public void onFailed(String error) {
  114. Toast.makeText(MainApp.getAppContext(), error, Toast.LENGTH_LONG).show();
  115. ImageView imageView = (ImageView) findViewById(R.id.fingerprinticon);
  116. int[][] states = new int[][]{
  117. new int[]{android.R.attr.state_activated},
  118. new int[]{-android.R.attr.state_activated}
  119. };
  120. int[] colors = new int[]{Color.parseColor("#FF0000"), Color.RED};
  121. ColorStateList csl = new ColorStateList(states, colors);
  122. Drawable drawable = DrawableCompat.wrap(imageView.getDrawable());
  123. DrawableCompat.setTintList(drawable, csl);
  124. imageView.setImageDrawable(drawable);
  125. }
  126. };
  127. FingerprintHandler helper = new FingerprintHandler(fingerprintTextView, callback);
  128. cancellationSignal = new CancellationSignal();
  129. if (ActivityCompat.checkSelfPermission(MainApp.getAppContext(), Manifest.permission.USE_FINGERPRINT)
  130. != PackageManager.PERMISSION_GRANTED) {
  131. return;
  132. }
  133. fingerprintManager.authenticate(cryptoObject, cancellationSignal, 0, helper, null);
  134. }
  135. }
  136. }
  137. @Override
  138. public void onResume(){
  139. super.onResume();
  140. AnalyticsUtils.setCurrentScreenName(this, SCREEN_NAME, TAG);
  141. startFingerprint();
  142. ImageView imageView = (ImageView)findViewById(R.id.fingerprinticon);
  143. imageView.setImageDrawable(ThemeUtils.tintDrawable(R.drawable.ic_fingerprint, ThemeUtils.primaryColor()));
  144. }
  145. @Override
  146. public void onStop(){
  147. super.onStop();
  148. cancellationSignal.cancel();
  149. }
  150. /**
  151. * Overrides click on the BACK arrow to prevent fingerprint from being worked around.
  152. *
  153. * @param keyCode Key code of the key that triggered the down event.
  154. * @param event Event triggered.
  155. * @return 'True' when the key event was processed by this method.
  156. */
  157. @Override
  158. public boolean onKeyDown(int keyCode, KeyEvent event) {
  159. return keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0 || super.onKeyDown(keyCode, event);
  160. }
  161. protected void generateKey() {
  162. try {
  163. keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
  164. } catch (Exception e) {
  165. Log_OC.e(TAG, "Error getting KeyStore", e);
  166. }
  167. KeyGenerator keyGenerator;
  168. try {
  169. keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
  170. keyStore.load(null);
  171. keyGenerator.init(
  172. new KeyGenParameterSpec.Builder(
  173. KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT
  174. )
  175. .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
  176. .setUserAuthenticationRequired(true)
  177. .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
  178. .build());
  179. keyGenerator.generateKey();
  180. } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | CertificateException | IOException |
  181. NoSuchProviderException e) {
  182. Log_OC.e(TAG, "Exception: " + e.getMessage());
  183. }
  184. }
  185. public boolean cipherInit() {
  186. try {
  187. cipher = Cipher.getInstance(
  188. KeyProperties.KEY_ALGORITHM_AES + "/"
  189. + KeyProperties.BLOCK_MODE_CBC + "/"
  190. + KeyProperties.ENCRYPTION_PADDING_PKCS7);
  191. } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
  192. return false;
  193. }
  194. try {
  195. keyStore.load(null);
  196. SecretKey key = (SecretKey) keyStore.getKey(KEY_NAME, null);
  197. cipher.init(Cipher.ENCRYPT_MODE, key);
  198. return true;
  199. } catch (KeyPermanentlyInvalidatedException e) {
  200. return false;
  201. } catch (KeyStoreException
  202. | CertificateException
  203. | UnrecoverableKeyException
  204. | IOException
  205. | NoSuchAlgorithmException
  206. | InvalidKeyException e) {
  207. return false;
  208. }
  209. }
  210. private void fingerprintResult(boolean fingerOk) {
  211. if (fingerOk) {
  212. Intent resultIntent = new Intent();
  213. resultIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
  214. resultIntent.putExtra(KEY_CHECK_RESULT, true);
  215. setResult(RESULT_OK, resultIntent);
  216. finish();
  217. } else {
  218. showErrorAndRestart(R.string.fingerprint_unknown);
  219. }
  220. }
  221. private void showErrorAndRestart(int errorMessage) {
  222. CharSequence errorSeq = getString(errorMessage);
  223. Toast.makeText(this, errorSeq, Toast.LENGTH_LONG).show();
  224. }
  225. @RequiresApi(api = Build.VERSION_CODES.M)
  226. static public boolean isFingerprintCapable(Context context) {
  227. try {
  228. FingerprintManager fingerprintManager =
  229. (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
  230. if (ActivityCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT)
  231. != PackageManager.PERMISSION_GRANTED) {
  232. return false;
  233. }
  234. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  235. return fingerprintManager.isHardwareDetected();
  236. }
  237. } catch (Exception e) {
  238. return false;
  239. }
  240. return false;
  241. }
  242. static public boolean isFingerprintReady(Context context) {
  243. try {
  244. FingerprintManager fingerprintManager =
  245. (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
  246. return ActivityCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT) ==
  247. PackageManager.PERMISSION_GRANTED && fingerprintManager.isHardwareDetected() &&
  248. fingerprintManager.hasEnrolledFingerprints();
  249. } catch (Exception e) {
  250. return false;
  251. }
  252. }
  253. }
  254. @RequiresApi(Build.VERSION_CODES.M)
  255. class FingerprintHandler extends FingerprintManager.AuthenticationCallback {
  256. private TextView text;
  257. private Callback callback;
  258. // Constructor
  259. FingerprintHandler(TextView mText, Callback mCallback) {
  260. text = mText;
  261. callback = mCallback;
  262. }
  263. @Override
  264. public void onAuthenticationError(int errMsgId, CharSequence errString) {
  265. // this.update(String.valueOf(errString), false);
  266. }
  267. @Override
  268. public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
  269. this.update(String.valueOf(helpString), false);
  270. }
  271. @Override
  272. public void onAuthenticationFailed() {
  273. this.update(MainApp.getAppContext().getString(R.string.fingerprint_unknown), false);
  274. }
  275. @Override
  276. public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
  277. this.update("Fingerprint Authentication succeeded.", true);
  278. }
  279. public void update(final String e, Boolean success) {
  280. if(success) {
  281. text.postDelayed(new Runnable() {
  282. @Override
  283. public void run() {
  284. callback.onAuthenticated();
  285. }
  286. }, 0);
  287. } else {
  288. text.postDelayed(new Runnable() {
  289. @Override
  290. public void run() {
  291. callback.onFailed(e);
  292. }
  293. }, 0);
  294. }
  295. }
  296. interface Callback {
  297. void onAuthenticated();
  298. void onFailed(String error);
  299. }
  300. }