Open EduardDurech opened 2 years ago
I will contribute to this as much as I can; however, I am not an Android Engineer nor do I usually use Java, @agnostic-apollo would you be able to assist/refactor my code? As well so it fits nicely into KeystoreAPI
Thanks
I am not an Android Engineer nor do I usually use Java
Directly working on encryption seems like a great way to start learning! :D
It's been a while since I looked/used keystore apis, so it will require me to research things as well. Encryption stuff needs to be handled with care and proper knowledge. It would also be good to have an external review as well from a good encryption apps dev as well, since its not my domain currently.
But yes, I can try to assist. Any refactoring will have to be done after next termux-app release, since that's a priority right now.
You can open a pull request and we can see.
Thanks
Directly working on encryption seems like a great way to start learning! :D
Exactly what I've been up to ;)
@agnostic-apollo would you happen to remember if you had issues with using the "AndroidKeyStore" PROVIDER
such as in the key .getInstance calls https://github.com/termux/termux-api/blob/6e80b07f66a6b959a8b92e91daaf52ac30b371c0/app/src/main/java/com/termux/api/apis/KeystoreAPI.java#L227
I keep getting java.security.NoSuchProviderException: no such provider: AndroidKeyStore
apparently this happens with unit tests (regardless of using Roboletric, such as in my case using termux with ecj and dx).
I have tried so many different ways of circumventing it, that has been the better part of 2 days, and I cannot get a security provider implemented via Termux and ECJ, I've tried using BouncyCastle, custom providers, using GCMParameterSpec or IVParameterSpec directly, and all I can find for solutions to these online is to use the provider as "AndroidKeyStore", looping back to the same problem. I cannot run the KeystoreAPI.java generateKey either (removing Termux calls) for the same reason.
For now, I am just using anonymous keys without an alias using Javax Crypto, as Android KeyStore uses as a backend (analogues to Java Security for symmetric encryption it seems), just to test the encryption/decryption ciphers, but in order to use Android's Keystore API for Termux, Biometric Authentication, et cetera for integration with Android, I need to resolve this Provider issue
What is/was your development environment? If I can get the provider working I'm quite confident in getting this implemented
termux-api
app doesn't have any unit tests so that can't be the reason.
Apps should be built with gradle
. Are you building on mobile? Don't have access to pc? I use Android Studio mainly.
For building in termux, check https://github.com/termux/termux-packages/pull/7227#issuecomment-893022283. Some github release urls are inactive now.
Some reference implementation I used many years ago is at
I have a PC, just wanted access to my phone's hardware-backed keystore for testing. Again, not familiar with Android development so not sure if it's possible to do this in Android Studio over ADB, although this isn't mandatory, I just thought it would be easier to test e.g. Biometric Authentication, I don't know how these are handled in Studio. Same reason I've not used gradle as I rarely use it and just used ECJ in Termux, but I assume there's a reason Android developers use the tools they do :-) So will migrate the project over
Your KeyHelper code is similar to the other implementations of e.g. AES that I've been seeing, and in the Documentation, I just have a problem getting the AndroidKeyStore provider working as you have here
Play button in android studio will build and install the app on your phone, then you can test however you like.
Code will be similar since it would have been copied and modified a bit.
termux-api
app doesn't have any unit tests so that can't be the reason.
BTW by "unit testing" I meant a stand-alone Java code that I built in ECJ on Termux, I took the generateKey method, removed the Termux references, et cetera, just to see if I could use the same Java and Android Keystore API calls, as termux-keystore works, but referencing those libraries in a stand-alone code seems to be an issue
Play button in android studio will build and install the app on your phone, then you can test however you like.
Sounds good, but I should rebuild Termux-API and Termux, right? So they are signed by the same source?
Without more info on code content and design and how you building and running, can't comment. Any ECJ issues is not something I have time to deal with anyway.
If you are using Github action builds or releases, all apps will have same key as local debug builds sourced from dev_keystore.jks.
I was using the FDroid builds, but I've switched over and got everything running via Github builds in Android Studio, tested and working
Just a quick question, I'd prefer the "Apply Code Changes" in Android Studio to rebuilding every time, but I get
Error Changes were not applied. Class not found: com.termux.api.apis.KeystoreAPI$1 Reinstall and restart app
From what I read online, this is a problem with older Gradle versions, but updating to 7.2.2 does not resolve it. Any ideas off the top of your head?
Thank you so much for your help, that should be the last of my questions and now I can implement this
Click app
drop down next to play button -> Edit Configuration
-> Enable Always install with package manager
Welcome.
Have to exit
Termux and have Termux:API open while doing it for it to work, assuming it's a conflict
@agnostic-apollo this is nearly done, I am just trying to support an option to store the encrypted data in SharedPreferences but am not familiar with the whole Context
part of Android, for example in FTPLab it seems that it is passed to the constructor, but from where?
Let's say given this snippet
public class KeystoreAPI {
public static void onReceive(TermuxApiReceiver apiReceiver, Intent intent) {
switch (intent.getStringExtra("command")) {
....
case "encrypt":
encryptData(apiReceiver, intent);
break;
}
}
...
public static void encryptData(TermuxApiReceiver apiReceiver, final Intent intent) {
ResultReturner.returnData(apiReceiver, intent, new WithInput() {
@Override
public void writeResult(PrintWriter out) throws ...{
...
encryptedData = ...
}
}
}
}
Would it make the most sense to:
import com.termux.shared.termux.settings.preferences.*;
as TermuxAPIApplication.java
doesor
onReceive
call as UsbAPI.java does https://github.com/termux/termux-api/blob/ba2836ba60083b54dcdb6a3a926777f3f0704829/app/src/main/java/com/termux/api/apis/UsbAPI.java#L33 send it to encryptData(apiReceiver, context, intent);
and received as public static void encryptData(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) {
, do I also need to add it into ResultReturner.returnData(...)
?Then I should be able to ??
encryptedData = ...
SharedPreferences sharedPref = context.getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString("FooPrefName", encryptedData);
editor.apply();
...
for example in FTPLab it seems that it is passed to the constructor, but from where?
Different places like https://github.com/agnostic-apollo/FTP/blob/068f9d57247cc70609d612bd6272fcc916b5ffd4/app/src/main/java/com/allonsy/android/ftp/FTPListFragment.java#L170
- Call some version of
import com.termux.shared.termux.settings.preferences.*;
asTermuxAPIApplication.java
does
Yes, you must use it. Rebase against master
branch for 9e72088 and then you should be able to do something like following. The last true
is for commitToFile
immediately, some phones have issues with saving shared preferences so it must be done, otherwise will lose data. The key
name would likely vary if clients want to store different data under different keys, but should prefix them with keystore_api__encrypted_data__<client_key>
. Make sure client key is not empty, and possibly matches ^[a-zA-Z0-9_-]+$
TermuxAPIAppSharedPreferences preferences = TermuxAPIAppSharedPreferences.build(context);
if (preferences == null) return; // log and return error
SharedPreferenceUtils.setString(preferences.getSharedPreferences(), key, data, true);
Include context in onReceive
Yeah, do that.
What I did was add context in the onReceive API call https://github.com/termux/termux-api/blob/b296a0c29eea7f9f9f88eb560e53b088605ad575/app/src/main/java/com/termux/api/TermuxApiReceiver.java#L148-L149
case "Keystore":
KeystoreAPI.onReceive(this, **context**, intent);
then
public class KeystoreAPI {
public static void onReceive(TermuxApiReceiver apiReceiver, **Context context**, Intent intent) {
switch (intent.getStringExtra("command")) {
....
case "encrypt":
encryptData(apiReceiver, **context**, intent);
break;
}
}
...
public static void encryptData(TermuxApiReceiver apiReceiver, **final Context context**, final Intent intent) {
ResultReturner.returnData(apiReceiver, intent, new WithInput() {
@Override
public void writeResult(PrintWriter out) throws ...{
...
encryptedData = ...
SharedPreferences.Editor editor = context.getSharedPreferences("foo", Context.MODE_PRIVATE).edit();
editor.putString("foobar", encryptedData);
editor.apply()
}
}
}
}
Is this the best way? This is one thing I am not familiar with so feedback would be appreciated
Edit: You responded while I was typing, will check haha
Okay, so given what you posted, all is the same except
encryptedData = ...
TermuxAPIAppSharedPreferences preferences = TermuxAPIAppSharedPreferences.build(context);
SharedPreferenceUtils.setString(preferences.getSharedPreferences(),
"keystore_api__encrypted_data__"+userSuppliedFoo,
encryptedData,
true);
And for decryption
TermuxAPIAppSharedPreferences preferences = TermuxAPIAppSharedPreferences.build(context);
toBeDecrypted = SharedPreferenceUtils.getString(preferences.getSharedPreferences(),
"keystore_api__encrypted_data__"+userSuppliedFoo,
null,
true);
It works, so long as this seems proper
Looks good, other than static constant. Also make sure to use space between +
and items as per convention.
public static final String KEYSTORE_API__ENCRYPTED_DATA_PREFERENCES_SCOPE = "keystore_api__encrypted_data__";
...
KEYSTORE_API__ENCRYPTED_DATA_PREFERENCES_SCOPE + userSuppliedFoo,
Code is available
It would also be good to have an external review as well from a good encryption apps dev as well
Any suggestions? Some of my references were @lokile, @nelenkov, @yakivmospan, and @WithSecureLabs has a Keystore Audit
@agnostic-apollo I'm trying to prompt for authentication when the key is locked by calling FingerprintAPI.onReceive(context, intent);
From KeystoreAPI but the subsequent code executes regardless of if FingerprintAPI has been finished (such as initializing cipher)
Any idea how to wait for that receiver to finish before moving on? I've tried creating
Intent fingerprintIntent = new Intent(context, FingerprintAPI.FingerprintActivity.class);
fingerprintIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(fingerprintIntent);
with the same problems
Not sure what the easiest way to do this is - create a new receiver? Add activity to manifest? Or do I have to edit FingerprintAPI https://github.com/termux/termux-api/blob/9e72088e8b1112deae67992f1f94029a58a7445d/app/src/main/java/com/termux/api/apis/FingerprintAPI.java#L81
You would need to start FingerprintActivity
and send it a PendingIntent
or special extras so that it sends result back to KeystoreAPI
instead of normal behaviour to socket server. Will have to send back result on all error outs and authentication success. Then in KeystoreAPI
, do whatever you originally intended to do. That should work.
I've been trying things for the past 2 days on this and am not able to get the KeystoreAPI
to wait, I've been searching a lot online but to no avail, I think I am understanding PendingIntent
wrong. Here are some methods I tried:
Not waiting:
Intent fingerprintIntent = new Intent(context, FingerprintAPI.FingerprintActivity.class);
fingerprintIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, fingerprintIntent, 0);
pendingIntent.send();
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
pendingIntent.send();
context.startActivity(...)
FingerprintAPI.onReceive(...)
will wait up until it calls .startActivity(...) then KeystoreAPI
continues, obviously. Though, this doesn't matter because once biometricPrompt.authenticate(...)
is called it seems that a new thread is created anyway (this is why I thought you mentioned I need to have the callback in every public void onAuthentication...(...)
in the new BiometricPrompt.AuthenticationCallback(){...}
) https://github.com/termux/termux-api/blob/9e72088e8b1112deae67992f1f94029a58a7445d/app/src/main/java/com/termux/api/apis/FingerprintAPI.java#L164The way I understand Pending Intents is that I initialize the request code and then in the onAuthentication...(...)
methods I would do the pendingIntent.send(requestCode)
, which would start something back in KeystoreAPI
. My issue is 1) How to send the Pending Intent, 2) How to "wait" for the request code. of course I could just add in the method arguments of authenticatedWithFingerprint(..., PendingIntent pendingIntent)
and then call that from KeystoreAPI
but then I have the issue of sending FragmentActivity context
https://github.com/termux/termux-api/blob/9e72088e8b1112deae67992f1f94029a58a7445d/app/src/main/java/com/termux/api/apis/FingerprintAPI.java#L163 "Method addObserver must be called on the main thread
", checking online this seems to be a whole lot of other issues
I think I am making this far too complicated. But even once I am able to send PendingIntent
, how would KeystoreAPI
know to wait for the request code? Wouldn't I need to create a new activity within KeystoreAPI
, create a new Intent
from that class, wrap it with a PendingIntent pendingIntent = ...
, send that pendingIntent
to FingerprintAPI
(previously mentioned problem), then in the onAuthentication...(...)
call that pendingIntent.send()
? I don't know if this helps what I'm trying to do as right now I try cipher.init(...)
, if it catches UserNotAuthenticatedException
then it tries the fingerprint and retries the block, I need it to wait before retrying:
int count = 0;
do {
try {
cipher.init(mode, key);
} catch (UserNotAuthenticatedException e) {
if (count < MAX_AUTH_RETRIES) {
// Fingerprint
} else {
Logger.logError(LOG_TAG, String.valueOf(e));
throw e;
}
}
} while (count++ < MAX_AUTH_RETRIES);
So I need it to wait at // Fingerprint
, do I need to create a BroadcastReceiver
? As I mentioned, biometricPrompt.authenticate(...)
seems to create a new thread regardless, which just initiates the continuation on KeystoreAPI
before any onAuthentication...(...)
, so I really do not know how to make KeystoreAPI
"wait" or "listen" for the callback
Sorry for the long information, I've been trying numerous things
So the only way so far I can get this to "work" is by adding a new activity
to AndroidManifest
which is defined in KeystoreAPI
as
public static class FooActivity extends FragmentActivity{
@Override
public void onCreate(Bundle savedInstanceState) {
...
}
}
where onCreate
will call the cipher action
within KeystoreAPI
a mutable (so intent extras are inherited, but hopefully you can give more information on this) PendingIntent
wraps an Intent
created referencing this FooActivity class, which I include as an extra in the original intent sent to FingerprintAPI.onReceive(...)
Intent fooIntent = new Intent(context, FooActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, fooIntent, 0);
intent.putExtra("pi", pendingIntent);
FingerprintAPI.onReceive(context, intent);
then in the onAuthentication...{...}
callback of the BiometricPrompt
PendingIntent pi = (PendingIntent) intent.getParcelableExtra("pi");
pi.send();
which instantiates the FooActivity
onCreate
If this seems like the proper/safest way to do it please let me know as it will take a bit of code-editing and before I commit I would like to know if there is a better way, thanks
You would need to start
FingerprintActivity
and send it aPendingIntent
or special extras so that it sends result back toKeystoreAPI
instead of normal behaviour to socket server. Will have to send back result on all error outs and authentication success. Then inKeystoreAPI
, do whatever you originally intended to do. That should work.
@agnostic-apollo sorry if you are just busy, not sure if my replies pinged you
My responses often take days and weeks. I get requests from people everyday and have my own work as well, can't respond immediately to everyone.
In KeystoreAPI
to call FingerprintAPI
use
public static final String EXTRA_ORIGINAL_INTENT = "original_intent";
public static final String EXTRA_PENDING_INTENT = "pending_intent";
public static final String EXTRA_FINGERPRINT_RESULT = "fingerprint_result";
public static final String EXTRA_RESULT_BUNDLE = "result";
Intent keyStoreAPIIntent = new Intent(context, TermuxApiReceiver.class);
keyStoreAPIIntent.putExtra("api_method", "Keystore");
keyStoreAPIIntent.putExtra(EXTRA_ORIGINAL_INTENT, intent);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, getLastPendingIntentRequestCode(context), keyStoreAPIIntent, PendingIntent.FLAG_ONE_SHOT);
Intent fingerprintAPIIntent = new Intent(context, TermuxApiReceiver.class);
fingerprintAPIIntent.putExtra("api_method", "Fingerprint");
fingerprintAPIIntent.putExtra(EXTRA_PENDING_INTENT, pendingIntent);
// Put additional extras like title, description, etc
context.sendBroadcast(fingerprintAPIIntent);
In FingerprintAPI
to send result to KeystoreAPI
use
PendingIntent pendingIntent = intent.getParcelableExtra(KeystoreAPI.EXTRA_PENDING_INTENT);
if(pendingIntent != null) {
final Bundle resultBundle = new Bundle();
resultBundle.putSerializable(KeystoreAPI.EXTRA_FINGERPRINT_RESULT, fingerprintResult);
Intent resultIntent = new Intent();
resultIntent.putExtra(KeystoreAPI.EXTRA_RESULT_BUNDLE, resultBundle);
try {
pendingIntent.send(context, Activity.RESULT_OK, resultIntent);
} catch (PendingIntent.CanceledException e) {
// The caller doesn't want the result? That's fine, just ignore
}
} else {
postFingerprintResult(context, intent, fingerprintResult);
}
To receive result from FingerprintAPI
at start of KeystoreAPI
use
final Intent originalIntent = intent.getParcelableExtra(EXTRA_ORIGINAL_INTENT);
if (originalIntent != null) {
// Must be response from FingerprintAPI
final Bundle resultBundle = intent.getBundleExtra(EXTRA_RESULT_BUNDLE);
if (resultBundle == null) {
Logger.logError(context, "The intent passed to KeystoreAPI() must contain the result bundle at the " + EXTRA_RESULT_BUNDLE + " key.");
return;
}
// Do whatever and then send result back
ResultReturner.returnData(apiReceiver, originalIntent /* not intent */, out -> {
}
}
For getLastPendingIntentRequestCode()
, check https://github.com/termux/termux-app/commit/ac32fbc53d8e91a5b7e53d08c06b936699d53ca2 and https://github.com/termux/termux-tasker/commit/d9a172d759adf080b6d8e05be0b64df7051699c1
Handle this case and any other potential ones in both apis.
For reference, check https://github.com/termux/termux-tasker/blob/d52f84fa6759ba3efbf6d12c91fc1943b78d33a9/app/src/main/java/com/termux/tasker/PluginResultsService.java
I haven't tested this, but should work.
@agnostic-apollo thank you :) I've been trying this, it does work with slight modifications but it has the issue as my previous suggestion that now I'd have to send the cipher over broadcast. I'm assuming it is safe as it's an explicit intent, but I'd prefer not sending the cipher and encrypted/decrypted data over broadcasts or intent extras when the user specifies the -q quiet flag, but that seems unavoidable using these methods as I'd have to send them from before the pending intent and then to the onReceive
call
This is why I wanted to try and create a "lock" that would wait until the FingerprintActivity
finished. I have figured out a round-about way but I'd like to know if this follows Java/Android standards
Thanks
Okay, I have found a way to avoid external global variable setting and busy-wait loops
Essentially, I had a context.wait()
after calling the fingerprintIntent
, which then calls pendingIntent.send(...)
and in KeystoreAPI
onReceive
I call the context.notifyAll()
from PluginUtils and TermuxTaskerAppSharedPreferences, I will add the analogous TermuxTaskerAppSharedPreferences
to TermuxAPIAppSharedPreferences
instead, with similar logic and adding values to termux-shared
Why do you need to wait at all? The shell command will keep on waiting if ResultSender
is not called anyways. Once PendingIntent
is sent after authentication, and KeystoreApi
receives second intent, it will continue on like it were processing the first intent. A postDelayed()
itself is a good idea to timeout in case user doesn't authenticate within a certain time or something went wrong and PendingIntent
is not received back. The SENSOR_TIMEOUT
could default to like 60s
but should be configurable from command line where no timeout if value is 0
in case command was started from background and user wasn't active on the device to authenticate in time, although not sure what happens if display is off and fingerprint authentication is started.
Why do you need to wait at all? The shell command will keep on waiting if
ResultSender
is not called anyways.
Mainly because of the logic that follows before creating and sending the PendingIntent
, I have to first create the cipher, try to initialize it, and only if it catches a UserNotAuthenticatedException
then it will call FingerprintActivity
, wait for it to finish, then retry the cipher initialization loop n times until either a successful cipher initialization (if authorization was successful), or n times did not work
So, if I refactor the code to include everything in the onReceive
, it means I would need to send the cipher as an intent extra which onReceive
would then call a function to try and cipher.init(...)
, otherwise I need to re-create the cipher again. The way I sent in the last comment hangs the thread until a BiometricCallback and then continues as normal, so the cipher, sensitive data, et cetera are still in the initial thread, less things having to be sent around between threads and keeps all data and crypto objects in the initial thread
I hope that makes sense, if you agree?
The SENSOR_TIMEOUT could default to like 60s but should be configurable from command line where no timeout if value is 0 in case command was started from background and user wasn't active on the device to authenticate in time
Currently I was using FingerprintAPI.SENSOR_TIMEOUT
(=10s), but with the method I sent in the last comment, a postDelayed()
is not necessary, although I may want to add a time to the context.wait(fooTimeout)
lock
[According to the documentation](https://developer.android.com/reference/androidx/biometric/BiometricPrompt#ERROR_TIMEOUT()) the BiometricPrompt times out on its own ~30s
not sure what happens if display is off and fingerprint authentication is started.
Nor am I :) One of the things I was going to test once I get this all working, but here is a tracker
No need to create the cipher again, just send it in PendingIntent
and any other required data and skip generation on result intent. Explicit intents are secure and there will be minimal overhead for putting and sending an extra. The intent and arg limits would also apply though.
https://github.com/termux/termux-tasker#arguments-and-result-data-limits
https://www.reddit.com/r/tasker/comments/prro8t/autoshare_crashed_when_i_pasted_the_file_path/
BiometricPrompt times out on its own ~30s
I see but there should be some extra seconds for intents overhead.
There would also be issue of multiple commands requesting Fingerprint
authentication at a given time, so some kind of queuing would be required in FingerprintApi
, not in KeystoreApi
and so timeouts would need to consider that too. Timer could technically be started in queue entry, so that time used by other earlier requests is ignored.
Nor am I :) One of the things I was going to test once I get this all working, but here is a tracker
Cool. I guess that will need to be handled as well as the comment or wait till screen is unlocked.
No need to create the cipher again, just send it in PendingIntent and any other required data and skip generation on result intent. Explicit intents are secure and there will be minimal overhead for putting and sending an extra. The intent and arg limits would also apply though.
Couldn't this be avoided by using the context.wait()
and context.notifyAll()
though? Or is there a problem with that method? I would also have to send the input data and other input args over intent extras, which is a bit, I'd like to avoid that if unnecessary
I don't know if notifyAll()
is internally called by android or something for context
object. And since you would be passing original intent in PendingIntent
anyways, you won't need to do anything else. Your way may work somehow, but you need to start another thread and not keep the TermuxApiReceiver
thread on hold, and not really sure what other advantage you would getting.
I think .wait()
and .notify()
are methods of Object
, and context
supports it, I've tested that it does indeed work though, it foregoes the need to be sending things in extras is essentially its benefit, I also don't need to handle different calls to cipher in both the onReceive
and another cipher call, more consistent logic
you need to start another thread and not keep the TermuxApiReceiver thread on hold
Another thread is started by biometricPrompt.authenticate(...)
, it is partly why I have to do all this as the Biometric prompt/interface is created by Android in a different thread
One more question in creating getLastPendingIntentRequestCode
, since there is no PluginUtils.java
for TermuxAPI
as there is for TermuxTasker
, would you like me to create one? Or include a getLastPendingIntentRequestCode
method in one of the existing util classes of TermuxAPI?
I think .wait() and .notify() are methods of Object, and context supports it,
I am aware, but since android is passed the same object, there is risk of android calling it. A separate object would be safer.
Another thread is started by
biometricPrompt.authenticate(...)
,
That's in FingerprintApi
, not in KeystoreApi
, but ResultSender.returnData()
does start a thread, so keep logic there.
would you like me to create one?
Yeah, create a util class.
but since android is passed the same object, there is risk of android calling it. A separate object would be safer.
Oh okay, I get what you mean, the documentation suggests to include a while check loop anyway due to spurious wakeup
ResultSender.returnData()
does start a thread, so keep logic there.
I am not sure what you mean, right now what I've noticed (not familiar with Android/Java, so just while testing) is that the context.startActivity(fingerprintAPIIntent)
in KeystoreAPI
starts a thread, as does the bmPrompt.authenticate(...)
in FingerprintAPI
, are you just suggesting that I wrap the context.startActivity(fingerprintAPIIntent)
and stuff preceding, following it in a ResultReturner.returnData(...)
? If so, I am currently doing this, or are you suggesting that I call ResultReturner.returnData(...)
afterwards? I'm not sure if you're suggesting that I create another thread or if you are just saying that ResultReturner.returnData(...)
just needs to return something so it doesn't remain indefinitely on hold (as we do in all the API calls)?
Also, I think the cleanest thing (would also mean I don't need to add more to KeystoreAPI
onReceive(...)
would be to do all the PendingIntent
.wait()
and .notifyall()
logic within FingerprintAPI
onReceive
and FingerprintActivity
, this would also make it universal to other calls to it, This would mean that KeystoreAPI.onReceive(...)
calls wouldn't return until either a timeout or auth_result (right now that is the same case when TermuxAPIReceiver
calls it, due to ReturnData
being called, but this would also make it work for non-Broadcast calls)
Cool, I can just do a lock in FingerprintAPI
meaning that this can go for any future calls
And in KeystoreAPI
intent.putExtra(FingerprintAPI.EXTRA_LOCK_ACTION, true);
FingerprintAPI.onReceive(context, intent);
Doesn't require PluginUtils
either, but I sent a PR just so the functionality is there for PendingIntent
in case it will be used in the future
@agnostic-apollo all done! I understand it will take some time to review but the interface is all in https://github.com/termux/termux-api-package/pull/161
and thank you so much for all of your help, I learned a lot during this, it is greatly appreciated
@agnostic-apollo actually, one more problem, sorry
When calling the fingerprint fragment it seems to leave an invisible overlay even after finishing, the grey bar seems to be from the Termux:API app, but I am not sure how to "exit" it when it finishes. The screen is unable to be interacted with
I tried calling this.finish()
after handleFingerprint()
returns to onCreate
and this.onBackPressed()
, but they don't work. I am not sure what exactly the problem is but I'd like to "close" the Termux:API overlay, calling termux-fingerprint
does not do this
https://github.com/termux/termux-api/blob/9e72088e8b1112deae67992f1f94029a58a7445d/app/src/main/java/com/termux/api/apis/FingerprintAPI.java#L144-L150
Thank you
Calling finish()
in onCreate()
will finish the activity before it or dialog is shown. The finish()
call is already made in ResultSender
if correct Activity
context is passed. Are you saying even master
branch shows grey bar or your pull branch?
Are you saying even
master
branch shows grey bar or your pull branch?
My pull branch, I cannot test on master
because KeystoreAPI
it doesn't call FingerprintAPI
, although this is not a problem when calling termux-fingerprint
from the CLI, only when calling the new termux-keystore
which calls FingerprintAPI.onReceive(...)
which then calls the context.startActivity(fingerprintIntent)
, I think when the FragmentActivity
starts is when this starts
It actually never goes to activity.finish()
but instead https://github.com/termux/termux-api/blob/9e72088e8b1112deae67992f1f94029a58a7445d/app/src/main/java/com/termux/api/util/ResultReturner.java#L224
I assume because the KeystoreAPI is a Receiver and not an Activity
Thoughts?
Since the intent you are passing to FingerprintAPI
does not contain SOCKET_OUTPUT_EXTRA
, an exception will be thrown each time by ResultReturner
, the second way should work. The ResultReturner
started by KeystoreAPI
will be a BroadcastReceiver
context, hence asyncResult
is called, instead of activity, and calling it again in FingerprintAPI
would be a nested call and will result in duplicate calls to functions. Although, I currently haven't taken a close look at your design.
Since following will not pass Activity
context, add an if (context instanceof Activity)
before calling finish()
.
I understand it will take some time to review but the interface is all in termux/termux-api-package#161
Great. Will take a look when I get time to look into termux-api
.
and thank you so much for all of your help, I learned a lot during this, it is greatly appreciated
You are very welcome. Good for you. :)
if i understand correctly, this would allow setups for setting up remote decrypt-root-at-boot (e.g. through dropbear/initramfs) using fingerprint authentication without storing the key (or whatever secret) in regular termux storage.
that's what i've been trying to set up and landed here :)
what i'd do is set up fingerprint to unlock rbw
, a CLI for bitwarden, and then i could do all sorts of unlocks just with fingerprint, which would be awesome.
Feature description Implement encrypt and decrypt in the
termux-keystore
(currently it only supports signing and verifying) via CipherThis would enable passcodes, secrets, et cetera to be stored in the Android KeyStore, an example would be for automatic decryption of an rclone config file without storing the password in a text file (e.g. encrypted by gpg) using rclone's
--password-command
, and would enable easy integration with theFingerprintAPI
/Biometric Authentication, which would resolve #246 and would also be more convenient than a passphrase or using pass (possibly more secure)Reference implementation
cipher.getIV()
which may be preferable so the IV is not stored separately. termux-keystore can also set a constant IV usingIVParameterSpec
but not preferable or derived from the alias, secret, such as what rclone doessetUserAuthenticationValidityDurationSeconds
withsetUserAuthenticationParameters
AUTH_DEVICE_CREDENTIAL
(see above)setInvalidatedByBiometricEnrollment