Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KeyStoreAPI Upgrade (Encrypt/Decrypt + more) #556

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion app/src/main/java/com/termux/api/TermuxApiReceiver.java
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ private void doWork(Context context, Intent intent) {
JobSchedulerAPI.onReceive(this, context, intent);
break;
case "Keystore":
KeystoreAPI.onReceive(this, intent);
KeystoreAPI.onReceive(this, context, intent);
break;
case "Location":
if (TermuxApiPermissionActivity.checkAndRequestPermissions(context, intent, Manifest.permission.ACCESS_FINE_LOCATION)) {
Expand Down
155 changes: 104 additions & 51 deletions app/src/main/java/com/termux/api/apis/FingerprintAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.JsonWriter;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.biometric.BiometricManager;
import androidx.biometric.BiometricPrompt;
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
import androidx.fragment.app.FragmentActivity;
Expand All @@ -20,6 +22,10 @@
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;

Expand All @@ -32,8 +38,7 @@ public class FingerprintAPI {
protected static final String KEY_NAME = "TermuxFingerprintAPIKey";
protected static final String KEYSTORE_NAME = "AndroidKeyStore";

// milliseconds to wait before canceling
protected static final int SENSOR_TIMEOUT = 10000;
protected static int SENSOR_TIMEOUT;

// maximum authentication attempts before locked out
protected static final int MAX_ATTEMPTS = 5;
Expand All @@ -53,22 +58,26 @@ public class FingerprintAPI {
protected static final String AUTH_RESULT_FAILURE = "AUTH_RESULT_FAILURE";
protected static final String AUTH_RESULT_UNKNOWN = "AUTH_RESULT_UNKNOWN";



// store result of fingerprint initialization / authentication
protected static FingerprintResult fingerprintResult = new FingerprintResult();

// have we posted our result back?
protected static boolean postedResult = false;

protected static boolean timedOut = false;

private static final String LOG_TAG = "FingerprintAPI";

private static final Lock lock = new ReentrantLock();
private static final Condition condition = lock.newCondition();
protected static final String EXTRA_LOCK_ACTION = "EXTRA_LOCK_ACTION";

/**
* Handles setup of fingerprint sensor and writes Fingerprint result to console
*/
public static void onReceive(final Context context, final Intent intent) {
Logger.logDebug(LOG_TAG, "onReceive");
SENSOR_TIMEOUT = intent.getIntExtra("authenticationTimeout", 10);
if (SENSOR_TIMEOUT != -1) SENSOR_TIMEOUT *= 1000;

resetFingerprintResult();

Expand All @@ -79,6 +88,22 @@ public static void onReceive(final Context context, final Intent intent) {
fingerprintIntent.putExtras(intent.getExtras());
fingerprintIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
context.startActivity(fingerprintIntent);

if (intent.getBooleanExtra(EXTRA_LOCK_ACTION, false)) {
lock.lock();
try {
if (SENSOR_TIMEOUT != -1) {
if (!condition.await(SENSOR_TIMEOUT+5000, TimeUnit.MILLISECONDS)) {
timedOut = true;
Logger.logDebug(LOG_TAG, "Lock timed out");
}
} else condition.await();
} catch (InterruptedException e) {
// If interrupted, nothing currently
} finally {
lock.unlock();
}
}
} else {
postFingerprintResult(context, intent, fingerprintResult);
}
Expand All @@ -88,28 +113,38 @@ public static void onReceive(final Context context, final Intent intent) {
* Writes the result of our fingerprint result to the console
*/
protected static void postFingerprintResult(Context context, Intent intent, final FingerprintResult result) {
ResultReturner.returnData(context, intent, new ResultReturner.ResultJsonWriter() {
@Override
public void writeJson(JsonWriter out) throws Exception {
out.beginObject();
if (intent.getBooleanExtra(EXTRA_LOCK_ACTION, false)) {
lock.lock();
try {
condition.signalAll();
} finally {
lock.unlock();
if (context instanceof Activity) ((Activity) context).finish();
}
} else {
ResultReturner.returnData(context, intent, new ResultReturner.ResultJsonWriter() {
@Override
public void writeJson(JsonWriter out) throws Exception {
out.beginObject();

out.name("errors");
out.beginArray();
out.name("errors");
out.beginArray();

for (String error : result.errors) {
out.value(error);
}
out.endArray();
for (String error : result.errors) {
out.value(error);
}
out.endArray();

out.name("failed_attempts").value(result.failedAttempts);
out.name("auth_result").value(result.authResult);
out.endObject();
out.name("failed_attempts").value(result.failedAttempts);
out.name("auth_result").value(result.authResult);
out.endObject();

out.flush();
out.close();
postedResult = true;
}
});
out.flush();
out.close();
postedResult = true;
}
});
}
}

/**
Expand Down Expand Up @@ -161,48 +196,64 @@ protected void handleFingerprint() {
* Handles authentication callback from our fingerprint sensor
*/
protected static void authenticateWithFingerprint(final FragmentActivity context, final Intent intent, final Executor executor) {
BiometricPrompt biometricPrompt = new BiometricPrompt(context, executor, new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
if (errorCode == BiometricPrompt.ERROR_LOCKOUT) {
appendFingerprintError(ERROR_LOCKOUT);

// first time locked out, subsequent auth attempts will fail immediately for a bit
if (fingerprintResult.failedAttempts >= MAX_ATTEMPTS) {
appendFingerprintError(ERROR_TOO_MANY_FAILED_ATTEMPTS);
BiometricPrompt biometricPrompt = new BiometricPrompt(context, executor,
new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
if (errorCode == BiometricPrompt.ERROR_LOCKOUT) {
appendFingerprintError(ERROR_LOCKOUT);

// first time locked out, subsequent auth attempts will fail immediately for a bit
if (fingerprintResult.failedAttempts >= MAX_ATTEMPTS) {
appendFingerprintError(ERROR_TOO_MANY_FAILED_ATTEMPTS);
}
}
setAuthResult(AUTH_RESULT_FAILURE);
if (timedOut) timedOut = false;
else postFingerprintResult(context, intent, fingerprintResult);
Logger.logError(LOG_TAG, errString.toString());
}
}
setAuthResult(AUTH_RESULT_FAILURE);
postFingerprintResult(context, intent, fingerprintResult);
Logger.logError(LOG_TAG, errString.toString());
}

@Override
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
setAuthResult(AUTH_RESULT_SUCCESS);
postFingerprintResult(context, intent, fingerprintResult);
}
@Override
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
setAuthResult(AUTH_RESULT_SUCCESS);
postFingerprintResult(context, intent, fingerprintResult);
}

@Override
public void onAuthenticationFailed() {
addFailedAttempt();
}
});
@Override
public void onAuthenticationFailed() {
addFailedAttempt();
}
});

boolean[] auths = intent.getBooleanArrayExtra("auths");
BiometricPrompt.PromptInfo.Builder builder = new BiometricPrompt.PromptInfo.Builder();
builder.setTitle(intent.hasExtra("title") ? intent.getStringExtra("title") : "Authenticate");
builder.setNegativeButtonText(intent.hasExtra("cancel") ? intent.getStringExtra("cancel") : "Cancel");
builder.setTitle(intent.hasExtra("title") ?
intent.getStringExtra("title") : "Authenticate");
if (intent.hasExtra("description")) {
builder.setDescription(intent.getStringExtra("description"));
}
if (intent.hasExtra("subtitle")) {
builder.setSubtitle(intent.getStringExtra("subtitle"));
}

if (auths == null || !auths[0] || Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
builder.setNegativeButtonText(intent.hasExtra("cancel") ?
intent.getStringExtra("cancel") : "Cancel");
builder.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
} else if (!auths[1]) {
builder.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL); //Can't test yet
} else {
builder.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG |
BiometricManager.Authenticators.DEVICE_CREDENTIAL);
}

// listen to fingerprint sensor
biometricPrompt.authenticate(builder.build());

addSensorTimeout(context, intent, biometricPrompt);
if (SENSOR_TIMEOUT != -1) {
addSensorTimeout(context, intent, biometricPrompt);
}
}

/**
Expand All @@ -215,7 +266,9 @@ protected static void addSensorTimeout(final Context context, final Intent inten
if (!postedResult) {
appendFingerprintError(ERROR_TIMEOUT);
biometricPrompt.cancelAuthentication();
postFingerprintResult(context, intent, fingerprintResult);
if (!intent.getBooleanExtra(EXTRA_LOCK_ACTION, false)) {
postFingerprintResult(context, intent, fingerprintResult);
}
}
}, SENSOR_TIMEOUT);
}
Expand Down
Loading