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

feat: implement android keystore #150

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

max-wittig
Copy link
Contributor

Notice: This PR should not be merged, before #141
is merged, because #141 also implements the new permission system, which is needed because the min API level is bumped to 23

This PR uses the Android Keystore, which replaces the SharedPreference save method. This improves the security of the tokens, without causing an inconvenience for the user.

Tested:

  • Token migration from SharedPreferences to Keystore
  • Add token via scan
  • Add token via add
  • Edit existing token
  • Delete token

Added libraries

  • com.bottlerocketstudios:vault is available under the Apache-2.0 license and provides a wrapper for the Android Keystore API https://github.com/BottleRocketStudios/Android-Vault
  • com.android.support.test.espresso:espresso-core is the instrumental test suite by google for running the new TokenPersistence tests, as the Keystore cannot be mocked. Can be run with the command ./gradlew connectedAndroidTest

@joekir
Copy link
Contributor

joekir commented Oct 24, 2017

Nice work!

I have a question around the bottlerocket addition though.
As TOTP/HOTP only ever uses the key for HMAC I thought we'd go for an implementation that restricts that? (See "Example: HMAC key for generating MACs using SHA-512" in https://developer.android.com/reference/android/security/keystore/KeyProtection.html)

I started work on this a month back but didn't get time to finish adding code to migrate legacy shared prefs keys. It also includes some test stuff there using JKECKS store so we can run unittests separate from the device.

master...joekir:keystore-123

I'd be in favor of not adding that bottlerocket code if we can directly use the Android API, just thought I'd share what I'd done in this direction, I think it's a big improvement either way for FreeOTP to move secrets out of SharedPrefs

@npmccallum
Copy link
Contributor

I would very much like to merge a patch like this. However, now that we are targeting minimum API version 23+, we can implement this with much stronger guarantees than what is implemented in this patch. We can actually import the key into the keystore and then do hmac against a reference to the key. This way, once the token is imported FreeOTP never sees the key again.

@max-wittig Would you be willing to implement something like this with better guarantees?

@max-wittig
Copy link
Contributor Author

max-wittig commented Nov 20, 2017

@npmccallum
It also seems like, that there is no library to support this, which would mean using the raw Google APIs for this, which can contain quite a lot of boilerplate code.

I would really like to implement this, but I'm not quite sure how. Do you have any additonal pointers on how to approach this?

Also does the user need to authentificate, if we use the KeyProtection APIs? Because that would be overkill and deter users from using this app imo.

@npmccallum
Copy link
Contributor

The native KeyStore API doesn't seem to be that hard. You basically just want to import the key into the keystore and then do HMAC against the reference to the key (rather than the key directly).

I think we should make authentication an two-fold option. If the token provider provides a flag (an extension to the OTP URI) that indicates authentication is required, we should respect that. Otherwise, we should offer the user the choice.

@max-wittig
Copy link
Contributor Author

If the token provider provides a flag (an extension to the OTP URI) that indicates authentication is required, we should respect that

Where can I find a reference to this extension of the OTP URI ? I think, we shouldn't invent a new flag, if it doesn't exist

@max-wittig
Copy link
Contributor Author

do HMAC against the reference to the key

But don't we need the URI string directly to read the values and generate a HTOP code?
I'm not sure how this can work

@npmccallum
Copy link
Contributor

@max-wittig

We already support the lock parameter on iOS.

To make things work, the app receives the URI during provisioning. Then it takes the secret and stores it in the key store under the appropriate HMAC algorithm. Once this is done, the secret never comes out of the key store again. When you want to generate the OTP code, you simply use the reference to the secret and ask the key store to perform HMAC on your behalf. You provide as input either the counter or the time stamp. Then you perform OTP truncation on the result.

The really positive step here is that FreeOTP can use the secret but can never again see the secret.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants