Implement parts of screen lock functionality

Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
Mario Danic 2019-02-14 16:24:54 +01:00
parent 469483d67d
commit 67bea68a1d
4 changed files with 217 additions and 35 deletions

View File

@ -21,14 +21,20 @@
package com.nextcloud.talk.activities;
import android.annotation.SuppressLint;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.WindowManager;
import android.webkit.SslErrorHandler;
import androidx.annotation.RequiresApi;
import com.nextcloud.talk.R;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.events.CertificateEvent;
import com.nextcloud.talk.utils.SecurityUtils;
import com.nextcloud.talk.utils.preferences.AppPreferences;
import com.nextcloud.talk.utils.ssl.MagicTrustManager;
import com.yarolegovich.lovelydialog.LovelyStandardDialog;
@ -51,6 +57,7 @@ import autodagger.AutoInjector;
@AutoInjector(NextcloudTalkApplication.class)
public class BaseActivity extends AppCompatActivity {
private static final String TAG = "BaseActivity";
private static final int REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 112;
@Inject
EventBus eventBus;
@ -58,12 +65,62 @@ public class BaseActivity extends AppCompatActivity {
@Inject
AppPreferences appPreferences;
private KeyguardManager keyguardManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this);
super.onCreate(savedInstanceState);
if (appPreferences.getIsScreenLocked()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
SecurityUtils.createKey();
}
}
}
@Override
public void onResume() {
super.onResume();
if (appPreferences.getIsScreenSecured()) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
} else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
checkIfWeAreSecure();
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
private void checkIfWeAreSecure() {
keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
if (keyguardManager != null && keyguardManager.isKeyguardSecure() && appPreferences.getIsScreenLocked()) {
if (!SecurityUtils.checkIfWeAreAuthenticated()) {
showAuthenticationScreen();
}
}
}
private void showAuthenticationScreen() {
Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(null, null);
if (intent != null) {
startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) {
if (resultCode == RESULT_OK) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (SecurityUtils.checkIfWeAreAuthenticated()) {
// all went well
}
}
} else {
// we didnt auth
}
}
}

View File

@ -22,6 +22,8 @@ package com.nextcloud.talk.controllers;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
@ -36,7 +38,13 @@ import android.view.WindowManager;
import android.widget.Checkable;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import autodagger.AutoInjector;
import butterknife.BindView;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler;
@ -61,37 +69,18 @@ import com.nextcloud.talk.utils.glide.GlideApp;
import com.nextcloud.talk.utils.preferences.AppPreferences;
import com.nextcloud.talk.utils.preferences.MagicUserInputModule;
import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder;
import com.yarolegovich.mp.MaterialChoicePreference;
import com.yarolegovich.mp.MaterialEditTextPreference;
import com.yarolegovich.mp.MaterialPreferenceCategory;
import com.yarolegovich.mp.MaterialPreferenceScreen;
import com.yarolegovich.mp.MaterialStandardPreference;
import com.yarolegovich.mp.MaterialSwitchPreference;
import net.orange_box.storebox.listeners.OnPreferenceValueChangedListener;
import org.greenrobot.eventbus.EventBus;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import autodagger.AutoInjector;
import butterknife.BindView;
import com.yarolegovich.mp.*;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import net.orange_box.storebox.listeners.OnPreferenceValueChangedListener;
import org.greenrobot.eventbus.EventBus;
import javax.inject.Inject;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
@AutoInjector(NextcloudTalkApplication.class)
public class SettingsController extends BaseController {
@ -164,6 +153,9 @@ public class SettingsController extends BaseController {
@BindView(R.id.settings_link_previews)
MaterialSwitchPreference linkPreviewsSwitchPreference;
@BindView(R.id.settings_screen_lock)
MaterialSwitchPreference screenLockSwitchPreference;
@BindView(R.id.message_text)
TextView messageText;
@ -179,6 +171,9 @@ public class SettingsController extends BaseController {
@Inject
UserUtils userUtils;
@Inject
Context context;
private UserEntity currentUser;
private String credentials;
@ -235,9 +230,18 @@ public class SettingsController extends BaseController {
shouldVibrateSwitchPreference.setVisibility(View.GONE);
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
incognitoKeyboardSwitchPreference.setVisibility(View.GONE);
}
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
screenLockSwitchPreference.setVisibility(View.GONE);
} else {
screenLockSwitchPreference.setSummary(String.format(Locale.getDefault(),
getResources().getString(R.string.nc_settings_screen_lock_desc),
getResources().getString(R.string.nc_app_name)));
}
if (!TextUtils.isEmpty(getResources().getString(R.string.nc_privacy_url))) {
privacyButton.addPreferenceClickListener(view12 -> {
@ -343,15 +347,34 @@ public class SettingsController extends BaseController {
}
if (shouldVibrateSwitchPreference.getVisibility() == View.VISIBLE) {
((Checkable)shouldVibrateSwitchPreference.findViewById(R.id.mp_checkable)).setChecked(appPreferences.getShouldVibrateSetting());
((Checkable) shouldVibrateSwitchPreference.findViewById(R.id.mp_checkable)).setChecked(appPreferences.getShouldVibrateSetting());
}
((Checkable)screenSecuritySwitchPreference.findViewById(R.id.mp_checkable)).setChecked(appPreferences.getIsScreenSecured());
((Checkable) screenSecuritySwitchPreference.findViewById(R.id.mp_checkable)).setChecked(appPreferences.getIsScreenSecured());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
((Checkable) incognitoKeyboardSwitchPreference.findViewById(R.id.mp_checkable)).setChecked(appPreferences.getIsKeyboardIncognito());
}
((Checkable)linkPreviewsSwitchPreference.findViewById(R.id.mp_checkable)).setChecked(appPreferences.getAreLinkPreviewsAllowed());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
((Checkable) incognitoKeyboardSwitchPreference.findViewById(R.id.mp_checkable)).setChecked(appPreferences.getIsKeyboardIncognito());
}
((Checkable) linkPreviewsSwitchPreference.findViewById(R.id.mp_checkable)).setChecked(appPreferences.getAreLinkPreviewsAllowed());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
if (keyguardManager.isDeviceSecure()) {
screenLockSwitchPreference.setEnabled(true);
((Checkable) screenLockSwitchPreference.findViewById(R.id.mp_checkable)).setChecked(appPreferences.getIsScreenLocked());
screenLockSwitchPreference.setAlpha(1.0f);
} else {
screenLockSwitchPreference.setEnabled(false);
appPreferences.setScreenLock(false);
((Checkable) screenLockSwitchPreference.findViewById(R.id.mp_checkable)).setChecked(false);
screenLockSwitchPreference.setAlpha(0.38f);
}
}
String ringtoneName = "";
RingtoneSettings ringtoneSettings;
@ -633,6 +656,7 @@ public class SettingsController extends BaseController {
}
}
}
private class ProxyCredentialsChangeListener implements OnPreferenceValueChangedListener<Boolean> {
@Override

View File

@ -0,0 +1,93 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.utils;
import android.os.Build;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.security.keystore.UserNotAuthenticatedException;
import android.util.Log;
import androidx.annotation.RequiresApi;
import javax.crypto.*;
import java.io.IOException;
import java.security.*;
import java.security.cert.CertificateException;
public class SecurityUtils {
private static final String TAG = "SecurityUtils";
private static final String CREDENTIALS_KEY = "KEY_CREDENTIALS";
private static final byte[] SECRET_BYTE_ARRAY = new byte[]{1, 2, 3, 4, 5, 6};
private static final int AUTHENTICATION_DURATION_SECONDS = 10;
@RequiresApi(api = Build.VERSION_CODES.M)
public static boolean checkIfWeAreAuthenticated() {
try {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
SecretKey secretKey = (SecretKey) keyStore.getKey(CREDENTIALS_KEY, null);
Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7);
// Try encrypting something, it will only work if the user authenticated within
// the last AUTHENTICATION_DURATION_SECONDS seconds.
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
cipher.doFinal(SECRET_BYTE_ARRAY);
// If the user has recently authenticated, we will reach here
return true;
} catch (UserNotAuthenticatedException e) {
// User is not authenticated, let's authenticate with device credentials.
return false;
} catch (KeyPermanentlyInvalidatedException e) {
return false;
} catch (BadPaddingException | IllegalBlockSizeException | KeyStoreException |
CertificateException | UnrecoverableKeyException | IOException
| NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
return false;
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
public static void createKey() {
try {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
keyGenerator.init(new KeyGenParameterSpec.Builder(CREDENTIALS_KEY,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
// Require that the user has unlocked in the last 30 seconds
.setUserAuthenticationValidityDurationSeconds(AUTHENTICATION_DURATION_SECONDS)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build());
keyGenerator.generateKey();
} catch (NoSuchAlgorithmException | NoSuchProviderException
| InvalidAlgorithmParameterException | KeyStoreException
| CertificateException | IOException e) {
Log.e(TAG, "Failed to create a symetric key");
}
}
}

View File

@ -149,6 +149,14 @@
apc:mpc_title="@string/nc_settings_privacy"
apc:mpc_title_color="@color/colorPrimary">
<com.yarolegovich.mp.MaterialSwitchPreference
android:id="@+id/settings_screen_lock"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_default_value="@bool/value_false"
apc:mp_key="@string/nc_settings_screen_lock_key"
apc:mp_title="@string/nc_settings_screen_lock_title" />
<com.yarolegovich.mp.MaterialSwitchPreference
android:id="@+id/settings_screen_security"
android:layout_width="match_parent"