mirror of
https://github.com/oxen-io/session-android.git
synced 2025-04-19 22:21:31 +00:00
feat: handle KeyStore backed fingerprint verification
This commit is contained in:
parent
6ad00dab9b
commit
db92034a8a
@ -39,11 +39,14 @@ import android.widget.ImageView;
|
|||||||
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
|
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
|
||||||
import androidx.core.os.CancellationSignal;
|
import androidx.core.os.CancellationSignal;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.util.AnimationCompleteListener;
|
|
||||||
import org.thoughtcrime.securesms.components.AnimatingToggle;
|
|
||||||
import org.session.libsignal.utilities.Log;
|
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
|
||||||
import org.session.libsession.utilities.TextSecurePreferences;
|
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.Signature;
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
@ -61,6 +64,8 @@ public class PassphrasePromptActivity extends BaseActionBarActivity {
|
|||||||
private CancellationSignal fingerprintCancellationSignal;
|
private CancellationSignal fingerprintCancellationSignal;
|
||||||
private FingerprintListener fingerprintListener;
|
private FingerprintListener fingerprintListener;
|
||||||
|
|
||||||
|
private final BiometricSecretProvider biometricSecretProvider = new BiometricSecretProvider();
|
||||||
|
|
||||||
private boolean authenticated;
|
private boolean authenticated;
|
||||||
private boolean failure;
|
private boolean failure;
|
||||||
|
|
||||||
@ -200,7 +205,7 @@ public class PassphrasePromptActivity extends BaseActionBarActivity {
|
|||||||
if (fingerprintManager.isHardwareDetected() && fingerprintManager.hasEnrolledFingerprints()) {
|
if (fingerprintManager.isHardwareDetected() && fingerprintManager.hasEnrolledFingerprints()) {
|
||||||
Log.i(TAG, "Listening for fingerprints...");
|
Log.i(TAG, "Listening for fingerprints...");
|
||||||
fingerprintCancellationSignal = new CancellationSignal();
|
fingerprintCancellationSignal = new CancellationSignal();
|
||||||
fingerprintManager.authenticate(null, 0, fingerprintCancellationSignal, fingerprintListener, null);
|
fingerprintManager.authenticate(new FingerprintManagerCompat.CryptoObject(biometricSecretProvider.getOrCreateBiometricSignature(this)), 0, fingerprintCancellationSignal, fingerprintListener, null);
|
||||||
} else {
|
} else {
|
||||||
Log.i(TAG, "firing intent...");
|
Log.i(TAG, "firing intent...");
|
||||||
Intent intent = keyguardManager.createConfirmDeviceCredentialIntent("Unlock Session", "");
|
Intent intent = keyguardManager.createConfirmDeviceCredentialIntent("Unlock Session", "");
|
||||||
@ -224,6 +229,27 @@ public class PassphrasePromptActivity extends BaseActionBarActivity {
|
|||||||
@Override
|
@Override
|
||||||
public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
|
public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
|
||||||
Log.i(TAG, "onAuthenticationSucceeded");
|
Log.i(TAG, "onAuthenticationSucceeded");
|
||||||
|
if (result.getCryptoObject() == null || result.getCryptoObject().getSignature() == null) {
|
||||||
|
// authentication failed
|
||||||
|
onAuthenticationFailed();
|
||||||
|
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.setImageResource(R.drawable.ic_check_white_48dp);
|
||||||
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.green_500), PorterDuff.Mode.SRC_IN);
|
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() {
|
fingerprintPrompt.animate().setInterpolator(new BounceInterpolator()).scaleX(1.1f).scaleY(1.1f).setDuration(500).setListener(new AnimationCompleteListener() {
|
||||||
@ -239,7 +265,7 @@ public class PassphrasePromptActivity extends BaseActionBarActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAuthenticationFailed() {
|
public void onAuthenticationFailed() {
|
||||||
Log.w(TAG, "onAuthenticatoinFailed()");
|
Log.w(TAG, "onAuthenticationFailed()");
|
||||||
|
|
||||||
fingerprintPrompt.setImageResource(R.drawable.ic_close_white_48dp);
|
fingerprintPrompt.setImageResource(R.drawable.ic_close_white_48dp);
|
||||||
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.SRC_IN);
|
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.SRC_IN);
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
package org.thoughtcrime.securesms.crypto
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.security.keystore.KeyGenParameterSpec
|
||||||
|
import android.security.keystore.KeyProperties
|
||||||
|
import org.session.libsession.utilities.Util
|
||||||
|
import java.security.KeyPairGenerator
|
||||||
|
import java.security.KeyStore
|
||||||
|
import java.security.PrivateKey
|
||||||
|
import java.security.Signature
|
||||||
|
|
||||||
|
class BiometricSecretProvider {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val BIOMETRIC_ASYM_KEY_ALIAS = "Session-biometric-asym"
|
||||||
|
private const val ANDROID_KEYSTORE = "AndroidKeyStore"
|
||||||
|
private const val SIGNATURE_ALGORITHM = "SHA512withECDSA"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getRandomData() = Util.getSecretBytes(32)
|
||||||
|
|
||||||
|
private fun createAsymmetricKey(context: Context) {
|
||||||
|
val keyGenerator = KeyPairGenerator.getInstance(
|
||||||
|
KeyProperties.KEY_ALGORITHM_EC, ANDROID_KEYSTORE
|
||||||
|
)
|
||||||
|
|
||||||
|
val builder = KeyGenParameterSpec.Builder(BIOMETRIC_ASYM_KEY_ALIAS,
|
||||||
|
KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
|
||||||
|
)
|
||||||
|
.setDigests(
|
||||||
|
KeyProperties.DIGEST_SHA256,
|
||||||
|
KeyProperties.DIGEST_SHA512
|
||||||
|
)
|
||||||
|
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
|
||||||
|
.setUserAuthenticationRequired(true)
|
||||||
|
.setUserAuthenticationValidityDurationSeconds(-1)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
builder.setUnlockedDeviceRequired(true)
|
||||||
|
if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)) {
|
||||||
|
builder.setIsStrongBoxBacked(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
builder.setInvalidatedByBiometricEnrollment(true)
|
||||||
|
}
|
||||||
|
keyGenerator.initialize(builder.build())
|
||||||
|
keyGenerator.generateKeyPair()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getOrCreateBiometricSignature(context: Context): Signature {
|
||||||
|
val ks = KeyStore.getInstance(ANDROID_KEYSTORE)
|
||||||
|
ks.load(null)
|
||||||
|
if (!ks.containsAlias(BIOMETRIC_ASYM_KEY_ALIAS)) {
|
||||||
|
createAsymmetricKey(context)
|
||||||
|
}
|
||||||
|
val key = ks.getKey(BIOMETRIC_ASYM_KEY_ALIAS, null) as PrivateKey
|
||||||
|
val signature = Signature.getInstance(SIGNATURE_ALGORITHM)
|
||||||
|
signature.initSign(key)
|
||||||
|
return signature
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifySignature(data: ByteArray, signedData: ByteArray): Boolean {
|
||||||
|
val ks = KeyStore.getInstance(ANDROID_KEYSTORE)
|
||||||
|
ks.load(null)
|
||||||
|
val certificate = ks.getCertificate(BIOMETRIC_ASYM_KEY_ALIAS)
|
||||||
|
val signature = Signature.getInstance(SIGNATURE_ALGORITHM)
|
||||||
|
signature.initVerify(certificate)
|
||||||
|
signature.update(data)
|
||||||
|
return signature.verify(signedData)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user