Skip to content

Commit 895477b

Browse files
authored
Merge branch 'master' into keystore
2 parents 3e771a4 + 6534d43 commit 895477b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+995
-6359
lines changed

README.md

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,16 @@
44
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
55
[![Open Source Love](https://badges.frapsoft.com/os/v2/open-source.png?v=103)](https://github.com/ellerbrock/open-source-badges/)
66

7+
`react-native-sensitive-info` manages all data stored in Android Shared Preferences, iOS Keychain and Windows Credentials. You can set and get all key/value using simple methods.
78

8-
**react-native-sensitive-info** is the next generation of [react-native-get-shared-prefs](https://www.npmjs.com/package/react-native-get-shared-prefs).
99

10-
# Introduction
10+
# Help Wanted
1111

12-
`react-native-sensitive-info` manages all data stored in Android Shared Preferences, iOS Keychain and Windows Credentials. You can set and get all key/value using simple methods.
12+
Hi 👋! It's been 3 years since I released RNSensitiveInfo's first version only to fix a problem that I was facing at that moment. I was starting my career as JS Developer and RNSensitiveInfo helped me a lot through my learning path. Unfortunately, I don't have too much time as I wanted to, to support by myself this awesome library that we've built so far. So, I'm looking for developers who could help during this journey... We have so many pending issues, features, security improvements, unity/integration tests that could be done... I'm still willing to help and take care of releasing it.
1313

14+
Feel free to contact me,
1415

15-
| RN SensitiveInfo Version | Description |
16-
|--------------------------|----------------------------------|
17-
| 4.0+ | Compatible with RN 0.40+ |
18-
| <= 3.0.2 | Compatible with RN 0.40 or below |
16+
Best Regards!
1917

2018
# Install
2119

@@ -169,6 +167,18 @@ strings: {
169167
}
170168
```
171169

170+
#### setInvalidatedByBiometricEnrollment
171+
172+
If you want to control the behaviour on android when new Fingers are enrolled or removed on the device [https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setInvalidatedByBiometricEnrollment(boolean)](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setInvalidatedByBiometricEnrollment(boolean)) on any device with API level greater than 24 (`Android version >= N`). You should call during the initialization of your app to the function `setInvalidatedByBiometricEnrollment`.
173+
This will re-initialise the internal android Key generator with the flag set to keep/invalidate the credentials upon fingers change.
174+
175+
```javascript
176+
import SInfo from 'react-native-sensitive-info';
177+
178+
SInfo.setInvalidatedByBiometricEnrollment(false);
179+
```
180+
If you do not call to this function the default value is set to `true`.
181+
172182
### iOS Specific Options
173183

174184
#### kSecAccessControl
@@ -186,6 +196,13 @@ SInfo.setItem('key1', 'value1', {
186196

187197
Note: By default `kSecAccessControl` will get set to `kSecAccessControlUserPresence`.
188198

199+
#### kSecAttrSynchronizable
200+
201+
You can set this to `true` in order to sync the keychain items with iCloud.
202+
203+
Note: By default `kSecAttrSynchronizable` will get set to `false`.
204+
205+
189206
# How to use?
190207

191208
Here is a simple example:

RNSensitiveInfo.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1-
import { NativeModules } from 'react-native';
1+
import { NativeModules } from "react-native";
22

3-
module.exports = NativeModules.RNSensitiveInfo;
3+
const RNSensitiveInfo = NativeModules.RNSensitiveInfo;
4+
5+
module.exports = {
6+
...RNSensitiveInfo,
7+
setInvalidatedByBiometricEnrollment() {
8+
if (RNSensitiveInfo.setInvalidatedByBiometricEnrollment == null) {
9+
return;
10+
}
11+
12+
return RNSensitiveInfo.setInvalidatedByBiometricEnrollment();
13+
},
14+
cancelFingerprintAuth() {
15+
if (RNSensitiveInfo.cancelFingerprintAuth == null) {
16+
return;
17+
}
18+
19+
return RNSensitiveInfo.cancelFingerprintAuth();
20+
}
21+
};

android/src/main/java/br/com/classapp/RNSensitiveInfo/RNSensitiveInfoModule.java

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package br.com.classapp.RNSensitiveInfo;
22

3+
import android.app.Activity;
4+
import android.app.Fragment;
5+
import android.app.FragmentTransaction;
36
import android.content.Context;
47
import android.content.SharedPreferences;
58
import android.hardware.fingerprint.FingerprintManager;
@@ -12,6 +15,7 @@
1215
import android.util.Base64;
1316
import android.util.Log;
1417
import androidx.annotation.NonNull;
18+
1519
import com.facebook.react.bridge.Promise;
1620
import com.facebook.react.bridge.ReactApplicationContext;
1721
import com.facebook.react.bridge.ReactContextBaseJavaModule;
@@ -67,6 +71,9 @@ public class RNSensitiveInfoModule extends ReactContextBaseJavaModule {
6771
private KeyStore mKeyStore;
6872
private CancellationSignal mCancellationSignal;
6973

74+
// Keep it true by default to maintain backwards compatibility with existing users.
75+
private boolean invalidateEnrollment = true;
76+
7077
public RNSensitiveInfoModule(ReactApplicationContext reactContext) {
7178
super(reactContext);
7279

@@ -125,6 +132,16 @@ private boolean hasSetupFingerprint() {
125132
}
126133
}
127134

135+
@ReactMethod
136+
public void setInvalidatedByBiometricEnrollment(final boolean invalidatedByBiometricEnrollment, final Promise pm) {
137+
this.invalidateEnrollment = invalidatedByBiometricEnrollment;
138+
try {
139+
prepareKey();
140+
} catch (Exception e) {
141+
pm.reject(e);
142+
}
143+
}
144+
128145
@ReactMethod
129146
public void isHardwareDetected(final Promise pm) {
130147
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@@ -290,6 +307,34 @@ private void initKeyStore() {
290307
}
291308
}
292309

310+
private void showDialog(final HashMap strings, Object cryptoObject, FingerprintUiHelper.Callback callback) {
311+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
312+
// DialogFragment.show() will take care of adding the fragment
313+
// in a transaction. We also want to remove any currently showing
314+
// dialog, so make our own transaction and take care of that here.
315+
316+
Activity activity = getCurrentActivity();
317+
if (activity == null) {
318+
callback.onError(AppConstants.E_INIT_FAILURE,
319+
strings.containsKey("cancelled") ? strings.get("cancelled").toString() : "Authentication was cancelled");
320+
return;
321+
}
322+
323+
FragmentTransaction ft = activity.getFragmentManager().beginTransaction();
324+
Fragment prev = getCurrentActivity().getFragmentManager().findFragmentByTag(AppConstants.DIALOG_FRAGMENT_TAG);
325+
if (prev != null) {
326+
ft.remove(prev);
327+
}
328+
ft.addToBackStack(null);
329+
330+
// Create and show the dialog.
331+
FingerprintAuthenticationDialogFragment newFragment = FingerprintAuthenticationDialogFragment.newInstance(strings);
332+
newFragment.setCryptoObject((FingerprintManager.CryptoObject) cryptoObject);
333+
newFragment.setCallback(callback);
334+
newFragment.show(ft, AppConstants.DIALOG_FRAGMENT_TAG);
335+
}
336+
}
337+
293338
/**
294339
* Generates a new AES key and stores it under the { @code KEY_ALIAS_AES } in the
295340
* Android Keystore.
@@ -321,6 +366,14 @@ private void prepareKey() throws Exception {
321366
// forces user authentication with fingerprint
322367
.setUserAuthenticationRequired(true);
323368

369+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
370+
try {
371+
builder.setInvalidatedByBiometricEnrollment(invalidateEnrollment);
372+
} catch (Exception e) {
373+
Log.d("RNSensitiveInfo", "Error setting setInvalidatedByBiometricEnrollment: " + e.getMessage());
374+
}
375+
}
376+
324377
keyGenerator.init(builder.build());
325378
keyGenerator.generateKey();
326379
}
@@ -361,12 +414,7 @@ public void onError(String errorCode, CharSequence errString) {
361414
}
362415

363416
// Show the fingerprint dialog
364-
FingerprintAuthenticationDialogFragment fragment
365-
= FingerprintAuthenticationDialogFragment.newInstance(strings);
366-
fragment.setCryptoObject(new FingerprintManager.CryptoObject(cipher));
367-
fragment.setCallback(new PutExtraWithAESCallback());
368-
369-
fragment.show(getCurrentActivity().getFragmentManager(), AppConstants.DIALOG_FRAGMENT_TAG);
417+
showDialog(strings, new FingerprintManager.CryptoObject(cipher), new PutExtraWithAESCallback());
370418

371419
} else {
372420
mCancellationSignal = new CancellationSignal();
@@ -477,12 +525,7 @@ public void onError(String errorCode, CharSequence errString) {
477525
}
478526

479527
// Show the fingerprint dialog
480-
FingerprintAuthenticationDialogFragment fragment
481-
= FingerprintAuthenticationDialogFragment.newInstance(strings);
482-
fragment.setCryptoObject(new FingerprintManager.CryptoObject(cipher));
483-
fragment.setCallback(new DecryptWithAesCallback());
484-
485-
fragment.show(getCurrentActivity().getFragmentManager(), AppConstants.DIALOG_FRAGMENT_TAG);
528+
showDialog(strings, new FingerprintManager.CryptoObject(cipher), new DecryptWithAesCallback());
486529

487530
} else {
488531
mCancellationSignal = new CancellationSignal();

android/src/main/java/br/com/classapp/RNSensitiveInfo/utils/AppConstants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ public interface AppConstants {
66

77
// error codes
88
String E_AUTHENTICATION_CANCELLED = "E_AUTHENTICATION_CANCELLED";
9+
String E_INIT_FAILURE = "E_INIT_FAILURE";
910
}

android/src/main/java/br/com/classapp/RNSensitiveInfo/view/Fragments/FingerprintAuthenticationDialogFragment.java

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment
5656

5757
private SharedPreferences mSharedPreferences;
5858

59+
private boolean mSavedInstanceStateDone = false;
60+
private boolean mPendingDismiss = false;
61+
private boolean mDidDismiss = false;
62+
private boolean mDidInvokeCallback = false;
63+
5964
public static FingerprintAuthenticationDialogFragment newInstance(HashMap strings) {
6065
FingerprintAuthenticationDialogFragment f = new FingerprintAuthenticationDialogFragment();
6166

@@ -96,10 +101,10 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,
96101
mCancelButton.setOnClickListener(new View.OnClickListener() {
97102
@Override
98103
public void onClick(View view) {
99-
mCallback.onError(
104+
callbackOnError(
100105
AppConstants.E_AUTHENTICATION_CANCELLED,
101106
mStrings.containsKey("cancelled") ? mStrings.get("cancelled").toString() : "Authentication was cancelled");
102-
dismiss();
107+
dismissDialog();
103108
}
104109
});
105110

@@ -120,7 +125,14 @@ public void onClick(View view) {
120125
@Override
121126
public void onResume() {
122127
super.onResume();
123-
mFingerprintUiHelper.startListening(mCryptoObject);
128+
mSavedInstanceStateDone = false;
129+
130+
if (mPendingDismiss) {
131+
// if there's a pending dismiss request, dismiss a fragment when it resumes
132+
dismissDialog();
133+
} else {
134+
mFingerprintUiHelper.startListening(mCryptoObject);
135+
}
124136
}
125137

126138
@Override
@@ -132,7 +144,7 @@ public void onPause() {
132144
@Override
133145
public void onCancel(DialogInterface dialog) {
134146
super.onCancel(dialog);
135-
mCallback.onError(
147+
callbackOnError(
136148
AppConstants.E_AUTHENTICATION_CANCELLED,
137149
mStrings.containsKey("cancelled") ? mStrings.get("cancelled").toString() : "Authentication was cancelled");
138150
}
@@ -144,6 +156,24 @@ public void onAttach(Context context) {
144156
mActivity = getActivity();
145157
}
146158

159+
public void onSaveInstanceState(Bundle outState) {
160+
super.onSaveInstanceState(outState);
161+
mSavedInstanceStateDone = true;
162+
}
163+
164+
private void dismissDialog() {
165+
if (mDidDismiss) return;
166+
167+
if (!mSavedInstanceStateDone) {
168+
// fragment is running, dismiss it
169+
mDidDismiss = true;
170+
dismiss();
171+
} else {
172+
// fragment is paused, dismiss it onResume
173+
mPendingDismiss = true;
174+
}
175+
}
176+
147177
/**
148178
* Sets the crypto object to be passed in when authenticating with fingerprint.
149179
*/
@@ -158,17 +188,31 @@ public void setCallback(FingerprintUiHelper.Callback callback) {
158188
mCallback = callback;
159189
}
160190

191+
public void callbackOnAuthenticated(FingerprintManager.AuthenticationResult result) {
192+
if (!mDidInvokeCallback) {
193+
mDidInvokeCallback = true;
194+
mCallback.onAuthenticated(result);
195+
}
196+
}
197+
198+
public void callbackOnError(String errorCode, CharSequence errString) {
199+
if (!mDidInvokeCallback) {
200+
mDidInvokeCallback = true;
201+
mCallback.onError(errorCode, errString);
202+
}
203+
}
204+
161205
@Override
162206
public void onAuthenticated(FingerprintManager.AuthenticationResult result) {
163207
// Callback from FingerprintUiHelper. Let the activity know that authentication was
164208
// successful.
165-
mCallback.onAuthenticated(result);
166-
dismiss();
209+
callbackOnAuthenticated(result);
210+
dismissDialog();
167211
}
168212

169213
@Override
170214
public void onError(String errorCode, CharSequence errString) {
171-
mCallback.onError(errorCode, errString);
172-
dismiss();
215+
callbackOnError(errorCode, errString);
216+
dismissDialog();
173217
}
174218
}

android/src/main/java/br/com/classapp/RNSensitiveInfo/view/Fragments/FingerprintUiHelper.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ public void stopListening() {
9494
@Override
9595
public void onAuthenticationError(final int errMsgId, final CharSequence errString) {
9696
if (!mSelfCancelled) {
97+
mCancelButton.setEnabled(false);
9798
showError(errString);
9899
mIcon.postDelayed(new Runnable() {
99100
@Override

example/.babelrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"presets": ["react-native"]
2+
"presets": ["module:metro-react-native-babel-preset"]
33
}

0 commit comments

Comments
 (0)