Begin implementing biometric magic

Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
Mario Danic 2019-02-15 10:59:33 +01:00
parent f6f35028a2
commit a5605fc89f
6 changed files with 112 additions and 2 deletions

View file

@ -120,7 +120,7 @@ dependencies {
implementation "android.arch.work:work-rxjava2:${workVersion}"
androidTestImplementation "android.arch.work:work-testing:${workVersion}"
implementation 'androidx.biometric:biometric:1.0.0-alpha03'
implementation "androidx.lifecycle:lifecycle-extensions:2.0.0"
implementation 'androidx.multidex:multidex:2.0.0'

View file

@ -34,6 +34,10 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<!-- This permission is deprecated in Android P -->
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<application
android:name=".application.NextcloudTalkApplication"
android:allowBackup="true"

View file

@ -26,16 +26,19 @@ import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.WindowManager;
import android.webkit.SslErrorHandler;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.biometric.BiometricPrompt;
import autodagger.AutoInjector;
import com.nextcloud.talk.R;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.events.CertificateEvent;
import com.nextcloud.talk.utils.HandlerExecutor;
import com.nextcloud.talk.utils.SecurityUtils;
import com.nextcloud.talk.utils.preferences.AppPreferences;
import com.nextcloud.talk.utils.ssl.MagicTrustManager;
@ -49,6 +52,7 @@ import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.util.List;
import java.util.concurrent.Executor;
@AutoInjector(NextcloudTalkApplication.class)
public class BaseActivity extends AppCompatActivity {
@ -61,6 +65,9 @@ public class BaseActivity extends AppCompatActivity {
@Inject
AppPreferences appPreferences;
@Inject
Context context;
private KeyguardManager keyguardManager;
@Override
@ -93,11 +100,33 @@ public class BaseActivity extends AppCompatActivity {
keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
if (keyguardManager != null && keyguardManager.isKeyguardSecure() && appPreferences.getIsScreenLocked()) {
if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences.getScreenLockTimeout())) {
showAuthenticationScreen();
if (SecurityUtils.isFingerprintAvailable(context)) {
showBiometricDialog();
} else {
showAuthenticationScreen();
}
}
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
private void showBiometricDialog() {
final BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setTitle(String.format(context.getString(R.string.nc_biometric_unlock), context.getString(R.string.nc_app_name)))
.setNegativeButtonText(context.getString(R.string.nc_cancel))
.build();
Executor mainExecutor = new HandlerExecutor(new Handler(getMainLooper()));
final BiometricPrompt biometricPrompt = new BiometricPrompt(this, mainExecutor,
new BiometricPrompt.AuthenticationCallback() {
}
);
biometricPrompt.authenticate(promptInfo, SecurityUtils.getCryptoObject());
}
private void showAuthenticationScreen() {
Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(null, null);
if (intent != null) {
@ -190,4 +219,12 @@ public class BaseActivity extends AppCompatActivity {
super.onStop();
eventBus.unregister(this);
}
@Override
public void onPause() {
super.onPause();
if (!disposable.isDisposed()) {
disposable.dispose();
}
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.nextcloud.talk.utils;
import android.os.Handler;
import androidx.annotation.NonNull;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
/**
* An adapter {@link Executor} that posts all executed tasks onto the given
* {@link Handler}.
*
* @hide
*/
public class HandlerExecutor implements Executor {
private final Handler mHandler;
public HandlerExecutor(@NonNull Handler handler) {
mHandler = handler;
}
@Override
public void execute(Runnable command) {
if (!mHandler.post(command)) {
throw new RejectedExecutionException(mHandler + " is shutting down");
}
}
}

View file

@ -20,6 +20,7 @@
package com.nextcloud.talk.utils;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.security.keystore.KeyGenParameterSpec;
@ -28,6 +29,8 @@ import android.security.keystore.KeyProperties;
import android.security.keystore.UserNotAuthenticatedException;
import android.util.Log;
import androidx.annotation.RequiresApi;
import androidx.biometric.BiometricPrompt;
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
import com.nextcloud.talk.R;
import com.nextcloud.talk.application.NextcloudTalkApplication;
@ -77,6 +80,24 @@ public class SecurityUtils {
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
public static BiometricPrompt.CryptoObject getCryptoObject() {
Cipher cipher = null;
try {
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE);
} catch (NoSuchAlgorithmException e) {
Log.w(TAG, e.getLocalizedMessage());
} catch (NoSuchPaddingException e) {
Log.w(TAG, e.getLocalizedMessage());
}
BiometricPrompt.CryptoObject cryptoObject = null;
if (cipher != null) {
cryptoObject = new BiometricPrompt.CryptoObject(cipher);
}
return cryptoObject;
}
@RequiresApi(api = Build.VERSION_CODES.M)
public static void createKey(String validity) {
try {
@ -109,4 +130,9 @@ public class SecurityUtils {
int indexOfValidity = entryValues.indexOf(validity);
return entryIntValues[indexOfValidity];
}
public static boolean isFingerprintAvailable(Context context) {
FingerprintManagerCompat fingerprintManager = FingerprintManagerCompat.from(context);
return fingerprintManager.isHardwareDetected() && fingerprintManager.hasEnrolledFingerprints();
}
}

View file

@ -112,6 +112,8 @@
<string name="nc_screen_lock_timeout_three_hundred">300</string>
<string name="nc_screen_lock_timeout_six_hundred">600</string>
<string name="nc_biometric_unlock">Unlock %1$s</string>
<string name="nc_cancel">Cancel</string>
<string name="nc_no_proxy">No proxy</string>
<string name="nc_username">Username</string>