From 67bea68a1d96dbf17f7e82c9fb355e44d3261991 Mon Sep 17 00:00:00 2001 From: Mario Danic Date: Thu, 14 Feb 2019 16:24:54 +0100 Subject: [PATCH] Implement parts of screen lock functionality Signed-off-by: Mario Danic --- .../talk/activities/BaseActivity.java | 59 +++++++++++- .../talk/controllers/SettingsController.java | 92 +++++++++++------- .../nextcloud/talk/utils/SecurityUtils.java | 93 +++++++++++++++++++ .../main/res/layout/controller_settings.xml | 8 ++ 4 files changed, 217 insertions(+), 35 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/utils/SecurityUtils.java diff --git a/app/src/main/java/com/nextcloud/talk/activities/BaseActivity.java b/app/src/main/java/com/nextcloud/talk/activities/BaseActivity.java index 8eacbd4f6..5705829fa 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/BaseActivity.java +++ b/app/src/main/java/com/nextcloud/talk/activities/BaseActivity.java @@ -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 + } } } diff --git a/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.java b/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.java index a4349517e..442b659cd 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.java @@ -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 { @Override diff --git a/app/src/main/java/com/nextcloud/talk/utils/SecurityUtils.java b/app/src/main/java/com/nextcloud/talk/utils/SecurityUtils.java new file mode 100644 index 000000000..0c790ad02 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/utils/SecurityUtils.java @@ -0,0 +1,93 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * 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 . + */ + +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"); + } + } +} diff --git a/app/src/main/res/layout/controller_settings.xml b/app/src/main/res/layout/controller_settings.xml index 10631b4dc..0639658da 100644 --- a/app/src/main/res/layout/controller_settings.xml +++ b/app/src/main/res/layout/controller_settings.xml @@ -149,6 +149,14 @@ apc:mpc_title="@string/nc_settings_privacy" apc:mpc_title_color="@color/colorPrimary"> + +