FingerprintActivity.java 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  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.ThemeUtils;
  50. import java.io.IOException;
  51. import java.security.InvalidAlgorithmParameterException;
  52. import java.security.InvalidKeyException;
  53. import java.security.KeyStore;
  54. import java.security.KeyStoreException;
  55. import java.security.NoSuchAlgorithmException;
  56. import java.security.NoSuchProviderException;
  57. import java.security.UnrecoverableKeyException;
  58. import java.security.cert.CertificateException;
  59. import javax.crypto.Cipher;
  60. import javax.crypto.KeyGenerator;
  61. import javax.crypto.NoSuchPaddingException;
  62. import javax.crypto.SecretKey;
  63. /**
  64. * Activity to handle access to the app based on the fingerprint.
  65. */
  66. @RequiresApi(Build.VERSION_CODES.M)
  67. public class FingerprintActivity extends AppCompatActivity {
  68. private static final String TAG = FingerprintActivity.class.getSimpleName();
  69. public final static String KEY_CHECK_RESULT = "KEY_CHECK_RESULT";
  70. public static final String ANDROID_KEY_STORE = "AndroidKeyStore";
  71. private KeyStore keyStore;
  72. // Variable used for storing the key in the Android Keystore container
  73. private static final String KEY_NAME = "Nextcloud";
  74. private Cipher cipher;
  75. private CancellationSignal cancellationSignal;
  76. /**
  77. * Initializes the activity.
  78. *
  79. * @param savedInstanceState Previously saved state - irrelevant in this case
  80. */
  81. protected void onCreate(Bundle savedInstanceState) {
  82. super.onCreate(savedInstanceState);
  83. setContentView(R.layout.fingerprintlock);
  84. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  85. getWindow().setStatusBarColor(ThemeUtils.primaryDarkColor(this));
  86. }
  87. Toolbar toolbar = findViewById(R.id.toolbar);
  88. toolbar.setTitleTextColor(ThemeUtils.fontColor(this));
  89. toolbar.setBackground(new ColorDrawable(ThemeUtils.primaryColor(this, false)));
  90. }
  91. private void startFingerprint() {
  92. TextView fingerprintTextView = findViewById(R.id.scanfingerprinttext);
  93. FingerprintManager fingerprintManager =
  94. (FingerprintManager) MainApp.getAppContext().getSystemService(Context.FINGERPRINT_SERVICE);
  95. if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT)
  96. != PackageManager.PERMISSION_GRANTED) {
  97. return;
  98. }
  99. KeyguardManager keyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
  100. if (keyguardManager.isKeyguardSecure()) {
  101. generateKey();
  102. if (cipherInit()) {
  103. FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(cipher);
  104. FingerprintHandler.Callback callback = new FingerprintHandler.Callback() {
  105. @Override
  106. public void onAuthenticated() {
  107. fingerprintResult(true);
  108. }
  109. @Override
  110. public void onFailed(String error) {
  111. Toast.makeText(MainApp.getAppContext(), error, Toast.LENGTH_LONG).show();
  112. ImageView imageView = findViewById(R.id.fingerprinticon);
  113. int[][] states = new int[][]{
  114. new int[]{android.R.attr.state_activated},
  115. new int[]{-android.R.attr.state_activated}
  116. };
  117. int[] colors = new int[]{Color.parseColor("#FF0000"), Color.RED};
  118. ColorStateList csl = new ColorStateList(states, colors);
  119. Drawable drawable = DrawableCompat.wrap(imageView.getDrawable());
  120. DrawableCompat.setTintList(drawable, csl);
  121. imageView.setImageDrawable(drawable);
  122. }
  123. };
  124. FingerprintHandler helper = new FingerprintHandler(fingerprintTextView, callback);
  125. cancellationSignal = new CancellationSignal();
  126. if (ActivityCompat.checkSelfPermission(MainApp.getAppContext(), Manifest.permission.USE_FINGERPRINT)
  127. != PackageManager.PERMISSION_GRANTED) {
  128. return;
  129. }
  130. fingerprintManager.authenticate(cryptoObject, cancellationSignal, 0, helper, null);
  131. }
  132. }
  133. }
  134. @Override
  135. public void onResume(){
  136. super.onResume();
  137. startFingerprint();
  138. ImageView imageView = findViewById(R.id.fingerprinticon);
  139. imageView.setImageDrawable(ThemeUtils.tintDrawable(R.drawable.ic_fingerprint, ThemeUtils.primaryColor(this)));
  140. }
  141. @Override
  142. public void onStop(){
  143. super.onStop();
  144. if(cancellationSignal != null) {
  145. cancellationSignal.cancel();
  146. }
  147. }
  148. /**
  149. * Overrides click on the BACK arrow to prevent fingerprint from being worked around.
  150. *
  151. * @param keyCode Key code of the key that triggered the down event.
  152. * @param event Event triggered.
  153. * @return 'True' when the key event was processed by this method.
  154. */
  155. @Override
  156. public boolean onKeyDown(int keyCode, KeyEvent event) {
  157. return keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0 || super.onKeyDown(keyCode, event);
  158. }
  159. protected void generateKey() {
  160. try {
  161. keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
  162. } catch (Exception e) {
  163. Log_OC.e(TAG, "Error getting KeyStore", e);
  164. }
  165. KeyGenerator keyGenerator;
  166. try {
  167. keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
  168. keyStore.load(null);
  169. keyGenerator.init(
  170. new KeyGenParameterSpec.Builder(
  171. KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT
  172. )
  173. .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
  174. .setUserAuthenticationRequired(true)
  175. .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
  176. .build());
  177. keyGenerator.generateKey();
  178. } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | CertificateException | IOException |
  179. NoSuchProviderException e) {
  180. Log_OC.e(TAG, "Exception: " + e.getMessage());
  181. }
  182. }
  183. public boolean cipherInit() {
  184. try {
  185. cipher = Cipher.getInstance(
  186. KeyProperties.KEY_ALGORITHM_AES + "/"
  187. + KeyProperties.BLOCK_MODE_CBC + "/"
  188. + KeyProperties.ENCRYPTION_PADDING_PKCS7);
  189. } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
  190. return false;
  191. }
  192. try {
  193. keyStore.load(null);
  194. SecretKey key = (SecretKey) keyStore.getKey(KEY_NAME, null);
  195. cipher.init(Cipher.ENCRYPT_MODE, key);
  196. return true;
  197. } catch (KeyPermanentlyInvalidatedException e) {
  198. return false;
  199. } catch (KeyStoreException
  200. | CertificateException
  201. | UnrecoverableKeyException
  202. | IOException
  203. | NoSuchAlgorithmException
  204. | InvalidKeyException e) {
  205. return false;
  206. }
  207. }
  208. private void fingerprintResult(boolean fingerOk) {
  209. if (fingerOk) {
  210. Intent resultIntent = new Intent();
  211. resultIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
  212. resultIntent.putExtra(KEY_CHECK_RESULT, true);
  213. setResult(RESULT_OK, resultIntent);
  214. finish();
  215. } else {
  216. showErrorAndRestart(R.string.fingerprint_unknown);
  217. }
  218. }
  219. private void showErrorAndRestart(int errorMessage) {
  220. CharSequence errorSeq = getString(errorMessage);
  221. Toast.makeText(this, errorSeq, Toast.LENGTH_LONG).show();
  222. }
  223. @RequiresApi(api = Build.VERSION_CODES.M)
  224. static public boolean isFingerprintCapable(Context context) {
  225. try {
  226. FingerprintManager fingerprintManager =
  227. (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
  228. if (ActivityCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT)
  229. != PackageManager.PERMISSION_GRANTED) {
  230. return false;
  231. }
  232. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  233. return fingerprintManager.isHardwareDetected();
  234. }
  235. } catch (Exception e) {
  236. return false;
  237. }
  238. return false;
  239. }
  240. static public boolean isFingerprintReady(Context context) {
  241. try {
  242. FingerprintManager fingerprintManager =
  243. (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
  244. return ActivityCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT) ==
  245. PackageManager.PERMISSION_GRANTED && fingerprintManager.isHardwareDetected() &&
  246. fingerprintManager.hasEnrolledFingerprints();
  247. } catch (Exception e) {
  248. return false;
  249. }
  250. }
  251. }
  252. @RequiresApi(api = Build.VERSION_CODES.M)
  253. class FingerprintHandler extends FingerprintManager.AuthenticationCallback {
  254. private TextView text;
  255. private Callback callback;
  256. // Constructor
  257. FingerprintHandler(TextView mText, Callback mCallback) {
  258. text = mText;
  259. callback = mCallback;
  260. }
  261. @Override
  262. public void onAuthenticationError(int errMsgId, CharSequence errString) {
  263. // this.update(String.valueOf(errString), false);
  264. }
  265. @Override
  266. public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
  267. this.update(String.valueOf(helpString), false);
  268. }
  269. @Override
  270. public void onAuthenticationFailed() {
  271. this.update(MainApp.getAppContext().getString(R.string.fingerprint_unknown), false);
  272. }
  273. @Override
  274. public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
  275. this.update("Fingerprint Authentication succeeded.", true);
  276. }
  277. public void update(final String e, Boolean success) {
  278. if(success) {
  279. text.postDelayed(new Runnable() {
  280. @Override
  281. public void run() {
  282. callback.onAuthenticated();
  283. }
  284. }, 0);
  285. } else {
  286. text.postDelayed(new Runnable() {
  287. @Override
  288. public void run() {
  289. callback.onFailed(e);
  290. }
  291. }, 0);
  292. }
  293. }
  294. interface Callback {
  295. void onAuthenticated();
  296. void onFailed(String error);
  297. }
  298. }