session-android/app/src/main/java/org/thoughtcrime/securesms/PassphrasePromptActivity.java
Morgan Pretty 0256735135 Fixed a few bugs, added logging and removed some old code
Added the ability to copy the sessionId of open group URL from the conversation menu
Added additional logging to the BatchMessageReceiveJob to make future debugging easier
Removed the OpenGroupMigrator
Updated the JobQueue logging to provide more insight
Fixed an issue where the database migrations weren't blocking which could result in failing/crashing SQL queries
Fixed an issue where the new database file wouldn't be removed if a migration error was thrown
Fixed an issue where the new database could exist in an invalid state and the app wouldn't attempt to remigrate
Fixed an incorrectly throw exception in the PassphrasePromptActivity
2023-02-03 13:33:52 +11:00

323 lines
12 KiB
Java

/*
* Copyright (C) 2011 Whisper Systems
*
* 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 org.thoughtcrime.securesms;
import android.animation.Animator;
import android.app.KeyguardManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.os.IBinder;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.RelativeSizeSpan;
import android.text.style.TypefaceSpan;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.BounceInterpolator;
import android.view.animation.TranslateAnimation;
import android.widget.Button;
import android.widget.ImageView;
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
import androidx.core.os.CancellationSignal;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsignal.utilities.Log;
import org.thoughtcrime.securesms.components.AnimatingToggle;
import org.thoughtcrime.securesms.crypto.BiometricSecretProvider;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.AnimationCompleteListener;
import java.security.InvalidKeyException;
import java.security.Signature;
import network.loki.messenger.R;
//TODO Rename to ScreenLockActivity and refactor to Kotlin.
public class PassphrasePromptActivity extends BaseActionBarActivity {
private static final String TAG = PassphrasePromptActivity.class.getSimpleName();
private ImageView fingerprintPrompt;
private Button lockScreenButton;
private AnimatingToggle visibilityToggle;
private FingerprintManagerCompat fingerprintManager;
private CancellationSignal fingerprintCancellationSignal;
private FingerprintListener fingerprintListener;
private final BiometricSecretProvider biometricSecretProvider = new BiometricSecretProvider();
private boolean authenticated;
private boolean failure;
private boolean hasSignatureObject = true;
private KeyCachingService keyCachingService;
@Override
public void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "onCreate()");
super.onCreate(savedInstanceState);
setContentView(R.layout.prompt_passphrase_activity);
initializeResources();
// Start and bind to the KeyCachingService instance.
Intent bindIntent = new Intent(this, KeyCachingService.class);
startService(bindIntent);
bindService(bindIntent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
keyCachingService = ((KeyCachingService.KeySetBinder)service).getService();
}
@Override
public void onServiceDisconnected(ComponentName name) {
keyCachingService.setMasterSecret(new Object());
keyCachingService = null;
}
}, Context.BIND_AUTO_CREATE);
}
@Override
public void onResume() {
super.onResume();
setLockTypeVisibility();
if (TextSecurePreferences.isScreenLockEnabled(this) && !authenticated && !failure) {
resumeScreenLock();
}
failure = false;
}
@Override
public void onPause() {
super.onPause();
if (TextSecurePreferences.isScreenLockEnabled(this)) {
pauseScreenLock();
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
@Override
public void onActivityResult(int requestCode, int resultcode, Intent data) {
super.onActivityResult(requestCode, resultcode, data);
if (requestCode != 1) return;
if (resultcode == RESULT_OK) {
handleAuthenticated();
} else {
Log.w(TAG, "Authentication failed");
failure = true;
}
}
private void handleAuthenticated() {
authenticated = true;
//TODO Replace with a proper call.
if (keyCachingService != null) {
keyCachingService.setMasterSecret(new Object());
}
// Finish and proceed with the next intent.
Intent nextIntent = getIntent().getParcelableExtra("next_intent");
if (nextIntent != null) {
try {
startActivity(nextIntent);
} catch (java.lang.SecurityException e) {
Log.w(TAG, "Access permission not passed from PassphraseActivity, retry sharing.", e);
}
}
finish();
}
private void initializeResources() {
visibilityToggle = findViewById(R.id.button_toggle);
fingerprintPrompt = findViewById(R.id.fingerprint_auth_container);
lockScreenButton = findViewById(R.id.lock_screen_auth_container);
fingerprintManager = FingerprintManagerCompat.from(this);
fingerprintCancellationSignal = new CancellationSignal();
fingerprintListener = new FingerprintListener();
SpannableString hint = new SpannableString(" " + getString(R.string.PassphrasePromptActivity_enter_passphrase));
hint.setSpan(new RelativeSizeSpan(0.9f), 0, hint.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
hint.setSpan(new TypefaceSpan("sans-serif"), 0, hint.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN);
lockScreenButton.setOnClickListener(v -> resumeScreenLock());
}
private void setLockTypeVisibility() {
if (TextSecurePreferences.isScreenLockEnabled(this)) {
if (fingerprintManager.isHardwareDetected() && fingerprintManager.hasEnrolledFingerprints()) {
fingerprintPrompt.setVisibility(View.VISIBLE);
lockScreenButton.setVisibility(View.GONE);
} else {
fingerprintPrompt.setVisibility(View.GONE);
lockScreenButton.setVisibility(View.VISIBLE);
}
} else {
fingerprintPrompt.setVisibility(View.GONE);
lockScreenButton.setVisibility(View.GONE);
}
}
private void resumeScreenLock() {
KeyguardManager keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
assert keyguardManager != null;
if (!keyguardManager.isKeyguardSecure()) {
Log.w(TAG ,"Keyguard not secure...");
TextSecurePreferences.setScreenLockEnabled(getApplicationContext(), false);
TextSecurePreferences.setScreenLockTimeout(getApplicationContext(), 0);
handleAuthenticated();
return;
}
if (fingerprintManager.isHardwareDetected() && fingerprintManager.hasEnrolledFingerprints()) {
Log.i(TAG, "Listening for fingerprints...");
fingerprintCancellationSignal = new CancellationSignal();
Signature signature;
try {
signature = biometricSecretProvider.getOrCreateBiometricSignature(this);
hasSignatureObject = true;
} catch (Exception e) {
signature = null;
hasSignatureObject = false;
Log.e(TAG, "Error getting / creating signature", e);
}
fingerprintManager.authenticate(
signature == null ? null : new FingerprintManagerCompat.CryptoObject(signature),
0,
fingerprintCancellationSignal,
fingerprintListener,
null
);
} else {
Log.i(TAG, "firing intent...");
Intent intent = keyguardManager.createConfirmDeviceCredentialIntent("Unlock Session", "");
startActivityForResult(intent, 1);
}
}
private void pauseScreenLock() {
if (fingerprintCancellationSignal != null) {
fingerprintCancellationSignal.cancel();
}
}
private class FingerprintListener extends FingerprintManagerCompat.AuthenticationCallback {
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
Log.w(TAG, "Authentication error: " + errMsgId + " " + errString);
onAuthenticationFailed();
}
@Override
public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
Log.i(TAG, "onAuthenticationSucceeded");
if (result.getCryptoObject() == null || result.getCryptoObject().getSignature() == null) {
if (hasSignatureObject) {
// authentication failed
onAuthenticationFailed();
} else {
fingerprintPrompt.setImageResource(R.drawable.ic_check_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.green_500), PorterDuff.Mode.SRC_IN);
fingerprintPrompt.animate().setInterpolator(new BounceInterpolator()).scaleX(1.1f).scaleY(1.1f).setDuration(500).setListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
handleAuthenticated();
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN);
}
}).start();
}
return;
}
// Signature object now successfully unlocked
boolean authenticationSucceeded = false;
try {
Signature signature = result.getCryptoObject().getSignature();
byte[] random = biometricSecretProvider.getRandomData();
signature.update(random);
byte[] signed = signature.sign();
authenticationSucceeded = biometricSecretProvider.verifySignature(random, signed);
} catch (Exception e) {
Log.e(TAG, "onAuthentication signature generation and verification failed", e);
}
if (!authenticationSucceeded) {
onAuthenticationFailed();
return;
}
fingerprintPrompt.setImageResource(R.drawable.ic_check_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.green_500), PorterDuff.Mode.SRC_IN);
fingerprintPrompt.animate().setInterpolator(new BounceInterpolator()).scaleX(1.1f).scaleY(1.1f).setDuration(500).setListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
handleAuthenticated();
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN);
}
}).start();
}
@Override
public void onAuthenticationFailed() {
Log.w(TAG, "onAuthenticationFailed()");
fingerprintPrompt.setImageResource(R.drawable.ic_close_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.SRC_IN);
TranslateAnimation shake = new TranslateAnimation(0, 30, 0, 0);
shake.setDuration(50);
shake.setRepeatCount(7);
shake.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN);
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
fingerprintPrompt.startAnimation(shake);
}
}
}