Giter VIP home page Giter VIP logo

whorlwind's Introduction

Whorlwind

A reactive wrapper around Android's fingerprint API that handles encrypting/decrypting sensitive data using a fingerprint.

DEPRECATED: Google has released the AndroidX Biometric Library which supports more forms of authentication than fingerprint and should be relied on going forward. See the announcement for more information.

Usage

Create an instance of Whorlwind by calling:

Whorlwind.create(context, storage, keyAlias)

You control where Whorlwind saves your encrypted data by providing a Storage. Whorlwind ships with a SharedPreferencesStorage if you want to store your data to shared preferences.

keyAlias is used when generating a key pair in the KeyStore and should not be shared with any other key aliases in your project.

All attempts to read/write from Whorlwind must be guarded by a call to canStoreSecurely(). This checks for necessary permissions and whether or not the fingerprint manager is available for use. The state of these requirements can change over the lifetime of your activity/application so it is not sufficient to check this once during activity/application creation.

Writing

Whorlwind handles encrypting the value for you so writing a new value is as simple as passing it to the write() method. However, be aware that Whorlwind will be performing cryptographic operations and may also perform some disk I/O regardless of your Storage implementation, so write() should not be called on the main thread.

if (whorlwind.canStoreSecurely()) {
  Observable.just("value")
      .observeOn(Schedulers.io())
      .flatMapCompletable(value -> whorlwind.write("key", ByteString.encodeUtf8(value)))
      .subscribe();
}

Reading

Whorlwind will handle activating the fingerprint reader and decrypting your data for you once you subscribe to the stream returned from read(). Similar to write(), read() should not be subscribed to on the main thread.

if (whorlwind.canStoreSecurely()) {
  whorlwind.read("key")
      .subscribeOn(Schedulers.io())
      .observeOn(AndroidSchedulers.mainThread())
      .subscribe(result -> {
        switch (result.readState) {
            case NEEDS_AUTH:
              // An encrypted value was found, prompt for fingerprint to decrypt.
              // The fingerprint reader is active.
              promptForFingerprint();
              break;
            case UNRECOVERABLE_ERROR:
            case AUTHORIZATION_ERROR:
            case RECOVERABLE_ERROR:
              // Show an error message. One may be provided in result.message.
              // Unless the state is UNRECOVERABLE_ERROR, the fingerprint reader is still
              // active and this stream will continue to emit result updates.
              showFingerprintMessage(result.code, result.message);
              break;
            case READY:
              if (result.value != null) {
                // Value was found and has been decrypted.
                showToast(result.value.utf8());
              } else {
                // No value was found. Fall back to password or fail silently, depending on
                // your use case.
                fingerprintFallback();
              }
              break;
            default:
              throw new IllegalArgumentException("Unknown state: " + result.readState);
          }
      });
}

Sample

A sample application is provided with a more comprehensive example.

Download

Gradle:

implementation 'com.squareup.whorlwind:whorlwind:2.1.0'

License

Copyright 2016 Square, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

whorlwind's People

Contributors

danh32 avatar jakewharton avatar jrodbx avatar mattprecious avatar mauin avatar oldergod avatar shnobee avatar vanniktech avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

whorlwind's Issues

Key generator uses RSA PKCS#1 v1.5 padding

The key generator uses RSA PKCS#1 v1.5 padding, which has security issues. RSA Optimal Asymmetric Encryption Padding (OAEP) appears to be a better choice. Is it possible to switch or to allow configuration of the padding?

RealWhorlwind.java:155

No fingerprint enrolled crash

This occurs after restarting one of the default Android emulators. After rebooting the emulator, it still shows a finger print registered. Adding a new one seems to fix the problem, but there seems to be some sort of underlying issue here. We are also seeing the same issue on a few Samsung and LG phones but have been unable reproduce it locally.

FATAL EXCEPTION: RxCachedThreadScheduler-1
   Process: com.squareup.whorlwind.sample, PID: 3221
   java.lang.IllegalStateException: Exception thrown on Scheduler.Worker thread. Add `onError` handling.
       at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:60)
       at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:423)
       at java.util.concurrent.FutureTask.run(FutureTask.java:237)
       at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:269)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
       at java.lang.Thread.run(Thread.java:818)
    Caused by: rx.exceptions.OnErrorNotImplementedException: java.security.InvalidAlgorithmParameterException: java.lang.IllegalStateException: At least one fingerprint must be enrolled to create keys requiring user authentication for every use
       at rx.Observable$27.onError(Observable.java:8139)
       at rx.observers.SafeSubscriber._onError(SafeSubscriber.java:157)
       at rx.observers.SafeSubscriber.onError(SafeSubscriber.java:120)
       at rx.exceptions.Exceptions.throwOrReport(Exceptions.java:200)
       at rx.observers.SafeSubscriber.onNext(SafeSubscriber.java:144)
       at rx.internal.util.ScalarSynchronousObservable$ScalarAsyncProducer.call(ScalarSynchronousObservable.java:200)
       at rx.internal.util.ScalarSynchronousObservable$3$1.call(ScalarSynchronousObservable.java:128)
       at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
       at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:423) 
       at java.util.concurrent.FutureTask.run(FutureTask.java:237) 
       at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:269) 
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113) 
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588) 
       at java.lang.Thread.run(Thread.java:818) 
    Caused by: java.lang.RuntimeException: java.security.InvalidAlgorithmParameterException: java.lang.IllegalStateException: At least one fingerprint must be enrolled to create keys requiring user authentication for every use
       at com.squareup.whorlwind.RealWhorlwind.prepareKeyStore(RealWhorlwind.java:153)
       at com.squareup.whorlwind.RealWhorlwind.write(RealWhorlwind.java:102)
       at com.squareup.whorlwind.sample.SampleActivity.lambda$onCreate$1(SampleActivity.java:76)
       at com.squareup.whorlwind.sample.SampleActivity.access$lambda$1(SampleActivity.java)
       at com.squareup.whorlwind.sample.SampleActivity$$Lambda$5.call(Unknown Source)
       at rx.Observable$27.onNext(Observable.java:8144)
       at rx.observers.SafeSubscriber.onNext(SafeSubscriber.java:139)
       at rx.internal.util.ScalarSynchronousObservable$ScalarAsyncProducer.call(ScalarSynchronousObservable.java:200) 
       at rx.internal.util.ScalarSynchronousObservable$3$1.call(ScalarSynchronousObservable.java:128) 
       at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55) 
       at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:423) 
       at java.util.concurrent.FutureTask.run(FutureTask.java:237) 
       at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:269) 
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113) 
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588) 
       at java.lang.Thread.run(Thread.java:818) 
    Caused by: java.security.InvalidAlgorithmParameterException: java.lang.IllegalStateException: At least one fingerprint must be enrolled to create keys requiring user authentication for every use
       at android.security.keystore.AndroidKeyStoreKeyPairGeneratorSpi.initialize(AndroidKeyStoreKeyPairGeneratorSpi.java:339)
       at java.security.KeyPairGenerator$KeyPairGeneratorImpl.initialize(KeyPairGenerator.java:284)
       at java.security.KeyPairGenerator.initialize(KeyPairGenerator.java:192)
       at com.squareup.whorlwind.RealWhorlwind.prepareKeyStore(RealWhorlwind.java:144)
       at com.squareup.whorlwind.RealWhorlwind.write(RealWhorlwind.java:102) 
       at com.squareup.whorlwind.sample.SampleActivity.lambda$onCreate$1(SampleActivity.java:76) 
       at com.squareup.whorlwind.sample.SampleActivity.access$lambda$1(SampleActivity.java) 
       at com.squareup.whorlwind.sample.SampleActivity$$Lambda$5.call(Unknown Source) 
       at rx.Observable$27.onNext(Observable.java:8144) 
       at rx.observers.SafeSubscriber.onNext(SafeSubscriber.java:139) 
       at rx.internal.util.ScalarSynchronousObservable$ScalarAsyncProducer.call(ScalarSynchronousObservable.java:200) 
       at rx.internal.util.ScalarSynchronousObservable$3$1.call(ScalarSynchronousObservable.java:128) 
       at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55) 
       at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:423) 
       at java.util.concurrent.FutureTask.run(FutureTask.java:237) 
       at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:269) 
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113) 
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588) 
       at java.lang.Thread.run(Thread.java:818) 
    Caused by: java.lang.IllegalStateException: At least one fingerprint must be enrolled to create keys requiring user authentication for every use
       at android.security.keystore.KeymasterUtils.addUserAuthArgs(KeymasterUtils.java:115)
       at android.security.keystore.AndroidKeyStoreKeyPairGeneratorSpi.initialize(AndroidKeyStoreKeyPairGeneratorSpi.java:335)
       at java.security.KeyPairGenerator$KeyPairGeneratorImpl.initialize(KeyPairGenerator.java:284) 
       at java.security.KeyPairGenerator.initialize(KeyPairGenerator.java:192) 
       at com.squareup.whorlwind.RealWhorlwind.prepareKeyStore(RealWhorlwind.java:144) 
       at com.squareup.whorlwind.RealWhorlwind.write(RealWhorlwind.java:102) 
       at com.squareup.whorlwind.sample.SampleActivity.lambda$onCreate$1(SampleActivity.java:76) 
       at com.squareup.whorlwind.sample.SampleActivity.access$lambda$1(SampleActivity.java) 
       at com.squareup.whorlwind.sample.SampleActivity$$Lambda$5.call(Unknown Source) 
       at rx.Observable$27.onNext(Observable.java:8144) 
       at rx.observers.SafeSubscriber.onNext(SafeSubscriber.java:139) 
       at rx.internal.util.ScalarSynchronousObservable$ScalarAsyncProducer.call(ScalarSynchronousObservable.java:200) 
       at rx.internal.util.ScalarSynchronousObservable$3$1.call(ScalarSynchronousObservable.java:128) 
       at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55) 
       at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:423) 
       at java.util.concurrent.FutureTask.run(FutureTask.java:237) 
       at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:269) 
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113) 
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588) 
       at java.lang.Thread.run(Thread.java:818) 

Abstracting away errors

The 3 operations: canStoreSecurely, write, and read are executed in wild environments.
How about allowing callbacks to be passed to each that would be executed if anything gets thrown?

Basically, this

boolean canStoreSecurely();
void write(String name, ByteString value);
Observable<ReadResult> read(String name);

would become

interface OnErrorListener {
  void onError(Throwable t);
}

boolean canStoreSecurely(@Nullable OnErrorListener onErrorListener);
void write(String name, ByteString value, @Nullable OnErrorListener onErrorListener);
Observable<ReadResult> read(String name, @Nullable OnErrorListener onErrorListener);

Cancel authentication

Is there a way to cancel the authentication process when running read? I'm using this library, but when user is prompted to read the secret, they can't cancel the process with this current interface.

The write method should throw when writing fails

RSA encryption only allows 256 bytes to be encrypted, so if you try to write a large value then Whorlwind will silently fail with a log in the following format:

Failed to write value for <name>
javax.crypto.IllegalBlockSizeException: input must be under 256 bytes

Of course there are other reasons why this might fail too, but this one is probably one that people will run into often. It would be better to be able to handle this as an error. An alternative solution would be to have this method return a boolean that returns true only when the write was successful.

Provide RxJava 2.x variant

I'm migrating my app to use RxJava 2 and it would be nice to use a variant of Whorlwind that uses RxJava 2, instead of relying on RxJava2Interop to use Whorlwind in my app.

AndroidX version

When will the androidX release version will be created for this library?

rx.Schedulers and io.reactivex.schedulers.Schedulers

In library Whorlwind.java used from rx.Observable but in your sample project io.reactivex.Observable imported and this conflict confused my environment and I get this error:
error: incompatible types: io.reactivex.Scheduler cannot be converted to rx.Scheduler

How can I fix it?
Thanks in advance

Crash when attempting to decrypt after adding new fingerprints

Steps to reproduce

  1. Open the sample app and register at least one value
  2. Close the app and head to settings to add additional fingerprints
  3. Reopen the sample app and try to read the previously stored value.. and it will crash

A few issues

  1. Upon reading the previously stored value, it returns a NEEDS_AUTH instead of an error state, (because new fingerprints was added)
  2. If you use any finger other than whats registered, its returns a RECOVERABLE_ERROR and asking users to retry - even though the number of fingerprints had changed
  3. And it crashes.. when you try to use any of the registered fingers

Crashlog

2018-10-30 13:24:11.113 26955-26955/com.squareup.whorlwind.sample I/Whorlwind: Failed to decrypt.
    javax.crypto.IllegalBlockSizeException
        at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:519)
        at javax.crypto.Cipher.doFinal(Cipher.java:1736)
        at com.squareup.whorlwind.FingerprintAuthOnSubscribe$2.onAuthenticationSucceeded(FingerprintAuthOnSubscribe.java:138)
        at android.hardware.fingerprint.FingerprintManager$MyHandler.sendAuthenticatedSucceeded(FingerprintManager.java:1314)
        at android.hardware.fingerprint.FingerprintManager$MyHandler.handleMessage(FingerprintManager.java:1224)
        at android.os.Handler.dispatchMessage(Handler.java:105)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6938)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)
     Caused by: android.security.KeyStoreException: Key user not authenticated
        at android.security.KeyStore.getKeyStoreException(KeyStore.java:1137)
        at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.update(KeyStoreCryptoOperationChunkedStreamer.java:132)
        at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:217)
        at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:506)
        at javax.crypto.Cipher.doFinal(Cipher.java:1736) 
        at com.squareup.whorlwind.FingerprintAuthOnSubscribe$2.onAuthenticationSucceeded(FingerprintAuthOnSubscribe.java:138) 
        at android.hardware.fingerprint.FingerprintManager$MyHandler.sendAuthenticatedSucceeded(FingerprintManager.java:1314) 
        at android.hardware.fingerprint.FingerprintManager$MyHandler.handleMessage(FingerprintManager.java:1224) 
        at android.os.Handler.dispatchMessage(Handler.java:105) 
        at android.os.Looper.loop(Looper.java:164) 
        at android.app.ActivityThread.main(ActivityThread.java:6938) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374) 

EDIT: ITS A SAMSUNG!!!

Update to use AES cipher and generated symmetric key

This is somewhat related to #18 and before I go through submit a PR I'm wondering if it's something that would be accepted.

  1. Store a generated AES symmetric key in shared preferences that will be encrypted/decrypted using the generated RSA public/private key.
  2. Encrypt/decrypt shared preference content using Alice and the symmetric key

NullPointerException FingerprintManager on

I see currently a crash that is only happening on a specific device where isHardwareDetected() throws a NullPointerException.

My proposal would be to check if FingerprintManager == null and return NullWhorlwind. Shall I create a pull request?

Device details:

Android: 8.0.0
Android Build: 48.1.A.0.129
Manufacturer: Sony
Model: G3121
Thread: main-2

Stacktrace:

java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.hardware.fingerprint.FingerprintManager.isHardwareDetected()' on a null object reference
	at com.squareup.whorlwind.RealWhorlwind.canStoreSecurely(RealWhorlwind.java:77)
	at com.moovel.pay.fingerprint.WhorlwindFingerprintStorage.canStoreSecurely(WhorlwindFingerprintStorage.java:25)
	at com.moovel.pay.screens.pin.RequestPinPresenter.onStart(RequestPinPresenter.java:57)
	at com.moovel.mvp.PresenterLifecycleObserver.onStart(PresenterLifecycleObserver.java:57)
	at com.moovel.mvp.CompositeLifecycleObserver.onStart(CompositeLifecycleObserver.java:50)
	at com.moovel.mvp.DaggerMVPFragment.onStart(DaggerMVPFragment.java:1049)
	at android.support.v4.app.Fragment.performStart(Fragment.java:2287)
	at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1458)
	at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1750)
	at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1819)
	at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:797)
	at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2590)
	at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2377)
	at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2332)
	at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2239)
	at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:700)
	at android.os.Handler.handleCallback(Handler.java:869)
	at android.os.Handler.dispatchMessage(Handler.java:101)
	at android.os.Looper.loop(Looper.java:206)
	at android.app.ActivityThread.main(ActivityThread.java:6749)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:845)```

Adding Fingerprint after call to .read() gives READY state

In SampleActivity, there's a "How How did you do that!?" toast. Here's how:

  1. Launch the Whorlwind sample app
  2. Click "READ" on a row
  3. Navigate to Android's fingerprint settings
  4. Add a new fingerprint
  5. Navigate back to Whorlwind sample app
  6. Tap READ again
  7. Toast appears

I haven't had a chance to debug any further but will update here if I find any more.

Adding a new fingerprint throws an IllegalBlockSizeException

I implemented this library a while back and it has been working well until the Android O update. Editing the fingerprint data i.e adding a new fingerprint throws an IllegalBlockSizeException. I have tested this on the sample app as well and the same exception is thrown. It seems to be happening when Cipher.doFinal() is called.

I have tested this on multiple operating systems and this seems to be a problem introduced in android 8.0 and fixed in 8.1. A lot of devices are still unable to upgrade to 8.1, is there a workaround/fix for this?

04-12 09:06:55.785 21711-21711/com.squareup.whorlwind.sample E/AndroidRuntime: FATAL EXCEPTION: main Process: com.squareup.whorlwind.sample, PID: 21711 io.reactivex.exceptions.OnErrorNotImplementedException at io.reactivex.internal.functions.Functions$14.accept(Functions.java:229) at io.reactivex.internal.functions.Functions$14.accept(Functions.java:226) at io.reactivex.internal.observers.LambdaObserver.onError(LambdaObserver.java:72) at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.checkTerminated(ObservableObserveOn.java:276) at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:172) at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:252) at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:109) at android.os.Handler.handleCallback(Handler.java:789) at android.os.Handler.dispatchMessage(Handler.java:98) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6809) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767) Caused by: javax.crypto.IllegalBlockSizeException at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:519) at javax.crypto.Cipher.doFinal(Cipher.java:1736) at com.squareup.whorlwind.FingerprintAuthOnSubscribe$2.onAuthenticationSucceeded(FingerprintAuthOnSubscribe.java:142) at android.hardware.fingerprint.FingerprintManager$MyHandler.sendAuthenticatedSucceeded(FingerprintManager.java:1005) at android.hardware.fingerprint.FingerprintManager$MyHandler.handleMessage(FingerprintManager.java:926) at android.os.Handler.dispatchMessage(Handler.java:105) at android.os.Looper.loop(Looper.java:164)  at android.app.ActivityThread.main(ActivityThread.java:6809)  at java.lang.reflect.Method.invoke(Native Method)  at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)  Caused by: android.security.KeyStoreException: Key user not authenticated at android.security.KeyStore.getKeyStoreException(KeyStore.java:695) at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.update(KeyStoreCryptoOperationChunkedStreamer.java:132) at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:217) at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:506) at javax.crypto.Cipher.doFinal(Cipher.java:1736)  at com.squareup.whorlwind.FingerprintAuthOnSubscribe$2.onAuthenticationSucceeded(FingerprintAuthOnSubscribe.java:142)  at android.hardware.fingerprint.FingerprintManager$MyHandler.sendAuthenticatedSucceeded(FingerprintManager.java:1005)  at android.hardware.fingerprint.FingerprintManager$MyHandler.handleMessage(FingerprintManager.java:926)  at android.os.Handler.dispatchMessage(Handler.java:105)  at android.os.Looper.loop(Looper.java:164)  at android.app.ActivityThread.main(ActivityThread.java:6809)  at java.lang.reflect.Method.invoke(Native Method)  at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767) 

UnrecoverableKeyException in prepareKeyStore

On Android 9, and possibly other Android versions, if you do the following steps:

  1. whorlwind.write("Key", ByteString.encodeUtf8("This is the secret data.")
  2. Go and disable the screen lock.
  3. Re-enable the screen lock and enable fingerprints.
  4. whorlwind.read("Key")...

The onError will get called back with an exception like the following:

java.lang.RuntimeException: java.security.UnrecoverableKeyException: Failed to obtain information about key

W: java.lang.RuntimeException: java.security.UnrecoverableKeyException: Failed to obtain information about key
W:     at com.squareup.whorlwind.RealWhorlwind.prepareKeyStore(RealWhorlwind.java:151)
W:     at com.squareup.whorlwind.RealWhorlwind$1.run(RealWhorlwind.java:104)
W:     at io.reactivex.internal.operators.completable.CompletableFromAction.subscribeActual(CompletableFromAction.java:34)
W:     at io.reactivex.Completable.subscribe(Completable.java:2171)
W:     at io.reactivex.Completable.subscribe(Completable.java:2244)
W:     at io.reactivex.rxkotlin.SubscribersKt.subscribeBy(subscribers.kt:86)
W:     at com.rnp.whorltest.MainActivity$onCreate$1$2.invoke(MainActivity.kt:62)
W:     at com.rnp.whorltest.MainActivity$onCreate$1$2.invoke(MainActivity.kt:20)
W:     at io.reactivex.rxkotlin.SubscribersKt$sam$io_reactivex_functions_Consumer$0.accept(Unknown Source:2)
W:     at io.reactivex.internal.observers.LambdaObserver.onError(LambdaObserver.java:77)
W:     at io.reactivex.internal.operators.observable.ObservableCreate$CreateEmitter.tryOnError(ObservableCreate.java:85)
W:     at io.reactivex.internal.operators.observable.ObservableCreate$CreateEmitter.onError(ObservableCreate.java:73)
W:     at io.reactivex.internal.operators.observable.ObservableCreate.subscribeActual(ObservableCreate.java:43)
W:     at io.reactivex.Observable.subscribe(Observable.java:12030)
W:     at io.reactivex.Observable.subscribe(Observable.java:12016)
W:     at io.reactivex.Observable.subscribe(Observable.java:11976)
W:     at io.reactivex.rxkotlin.SubscribersKt.subscribeBy(subscribers.kt:38)
W:     at io.reactivex.rxkotlin.SubscribersKt.subscribeBy$default(subscribers.kt:37)
W:     at com.rnp.whorltest.MainActivity$onCreate$1.onClick(MainActivity.kt:37)
W:     at android.view.View.performClick(View.java:6597)
W:     at android.view.View.performClickInternal(View.java:6574)
W:     at android.view.View.access$3100(View.java:778)
W:     at android.view.View$PerformClick.run(View.java:25885)
W:     at android.os.Handler.handleCallback(Handler.java:873)
W:     at android.os.Handler.dispatchMessage(Handler.java:99)
W:     at android.os.Looper.loop(Looper.java:193)
W:     at android.app.ActivityThread.main(ActivityThread.java:6669)
W:     at java.lang.reflect.Method.invoke(Native Method)
W:     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
W:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
W: Caused by: java.security.UnrecoverableKeyException: Failed to obtain information about key
W:     at android.security.keystore.AndroidKeyStoreProvider.getKeyCharacteristics(AndroidKeyStoreProvider.java:234)
W:     at android.security.keystore.AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(AndroidKeyStoreProvider.java:356)
W:     at android.security.keystore.AndroidKeyStoreSpi.engineGetKey(AndroidKeyStoreSpi.java:101)
W:     at java.security.KeyStore.getKey(KeyStore.java:1062)
W:     at com.squareup.whorlwind.RealWhorlwind.prepareKeyStore(RealWhorlwind.java:127)
W: 	... 29 more
W: Caused by: android.security.KeyStoreException: Key blob corrupted
W:     at android.security.KeyStore.getKeyStoreException(KeyStore.java:823)
W:     at android.security.keystore.AndroidKeyStoreProvider.getKeyCharacteristics(AndroidKeyStoreProvider.java:236)
W: 	... 33 more

This sort of exception should likely clear the keystore and surface the exception, otherwise once a device gets into this state, it'll never get out of it again. Or, is there another way the library is supposed to be used to 'reset' the keystore so that a user can encrypt something again later?

Android 10 System Error

Android 10 decided to add new exceptions for us to handle.

Caused by: android.security.KeyStoreException: System error
        at android.security.KeyStore.getKeyStoreException(KeyStore.java:1268)
        at android.security.keystore.AndroidKeyStoreKeyPairGeneratorSpi.generateKeystoreKeyPair(AndroidKeyStoreKeyPairGeneratorSpi.java:514)
        at android.security.keystore.AndroidKeyStoreKeyPairGeneratorSpi.generateKeyPair(AndroidKeyStoreKeyPairGeneratorSpi.java:470)
        at java.security.KeyPairGenerator$Delegate.generateKeyPair(KeyPairGenerator.java:727)
        at com.squareup.whorlwind.RealWhorlwind.prepareKeyStore(RealWhorlwind.java:149

Androidx Biometric Support

Hello,

I found this library really interesting. My question is: is there a plan to integrate official Google androidx biometric library ?

It could be a good option providing a way (via an interface definition and a factory, a bridge or another pattern) to switch or use different implementations of the "under the hood" crypto operations / authentication.

I know that probably you want to have a faceless SDK, but providing an optional biometricx based implemetation (maybe in an external module that depends from the core) could provide a more ready to use and scalable solution.

If this sound good, i can work to a fork and trying to submit a pull request.

Back-to-back calls to read() throws IllegalStateException

I'm seeing some java.lang.IllegalStateException: Already attempting to read another value. being thrown in the wild. I'm able to reproduce locally by rotating my device a couple of times with our FingerprintFragment up. As best as I can tell, there's a race condition between the CancellationSignal notifying the AuthenticationCallbacks which resets the AtomicBoolean and then the Activity/Fragment lifecycle's onPause and onResume.

I can also repro in SampleActivity by hardcoding immediate calls at the end of onResume:

    readSubject.onNext("sample");
    readSubject.onNext("sample");

supported version?

question

this lib support all version like
6.0 or <6.0

and is it support like xiami note 3,Samsung alpha devices ?

because they are using own API i think 0_0

How do you cancel a scan?

In my app, I initiate a scan with whorlwind.read(), and I pop a dialog. The dialog has a cancel option, so if that is clicked, how do I cancel the scan?

java.lang.IllegalStateException · Failed parsing settings file: /data/system/users/0/settings_fingerprint.xml

This is an exception we are getting a lot. Might be worth just swallow it and return false on canStoreSecurely()

java.lang.IllegalStateException: Failed parsing settings file: /data/system/users/0/settings_fingerprint.xml
        at android.os.Parcel.readException(Parcel.java:1628)
        at android.os.Parcel.readException(Parcel.java:1573)
        at android.hardware.fingerprint.IFingerprintService$Stub$Proxy.hasEnrolledFingerprints(IFingerprintService.java:503)
        at android.hardware.fingerprint.FingerprintManager.hasEnrolledFingerprints(FingerprintManager.java:776)
        at com.squareup.whorlwind.RealWhorlwind.canStoreSecurely(RealWhorlwind.java:78)

No cause attached to it: probably being thrown from around Parcel.java#L1691.

This is what #34 was trying to fox but the stack trace says the PR is wrong.

Should one `whorldwind#read` on main thread?

Right now, read on a worker thread because we assumed this didn't have to run on the main thread:

whorlwind.read(name)
  .subscribeOn(Schedulers.io())
  .observeOn(AndroidSchedulers.mainThread())
  .subscribe()

On the other hand, we have a lot of crashes for Samsung devices, for it looks like they do stuff that has to run on the main thread:

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:200)
        at android.os.Handler.<init>(Handler.java:114)
        at com.samsung.android.camera.iris.SemIrisManager$GetterHandler.<init>(SemIrisManager.java:948)
        at com.samsung.android.camera.iris.SemIrisManager.<init>(SemIrisManager.java:456)
        at com.samsung.android.camera.iris.SemIrisManager.getSemIrisManager(SemIrisManager.java:1898)
        at android.security.keystore.KeymasterUtils.addUserAuthArgs(KeymasterUtils.java:120)
        at android.security.keystore.AndroidKeyStoreKeyPairGeneratorSpi.initialize(AndroidKeyStoreKeyPairGeneratorSpi.java:380)
        at java.security.KeyPairGenerator$Delegate.initialize(KeyPairGenerator.java:669)
        at java.security.KeyPairGenerator.initialize(KeyPairGenerator.java:418)
        at com.squareup.whorlwind.RealWhorlwind.prepareKeyStore(RealWhorlwind.java:143)
        at com.squareup.whorlwind.RealWhorlwind$1.run(RealWhorlwind.java:105)

I'm not sure what's the right path is but

  1. Should one, as a consumer of the lib, not need to care about the work whorlwind is doing and run whatever on the main thread? So no crash.
  2. If the lib considers its work heavy, should it manage threads internally?
  3. What else is there?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.