Skip to content

Commit 2ba30fa

Browse files
authored
Merge pull request #162 from mtpilarek/keystore
Keystore
2 parents e706175 + 895477b commit 2ba30fa

Some content is hidden

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

54 files changed

+995
-6360
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 & 13 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;
@@ -9,9 +12,9 @@
912
import android.security.keystore.KeyInfo;
1013
import android.security.KeyPairGeneratorSpec;
1114
import android.security.keystore.KeyProperties;
12-
import android.support.annotation.NonNull;
1315
import android.util.Base64;
1416
import android.util.Log;
17+
import androidx.annotation.NonNull;
1518

1619
import com.facebook.react.bridge.Promise;
1720
import com.facebook.react.bridge.ReactApplicationContext;
@@ -68,6 +71,9 @@ public class RNSensitiveInfoModule extends ReactContextBaseJavaModule {
6871
private KeyStore mKeyStore;
6972
private CancellationSignal mCancellationSignal;
7073

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

@@ -126,6 +132,16 @@ private boolean hasSetupFingerprint() {
126132
}
127133
}
128134

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+
129145
@ReactMethod
130146
public void isHardwareDetected(final Promise pm) {
131147
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@@ -291,6 +307,34 @@ private void initKeyStore() {
291307
}
292308
}
293309

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+
294338
/**
295339
* Generates a new AES key and stores it under the { @code KEY_ALIAS_AES } in the
296340
* Android Keystore.
@@ -322,6 +366,14 @@ private void prepareKey() throws Exception {
322366
// forces user authentication with fingerprint
323367
.setUserAuthenticationRequired(true);
324368

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+
325377
keyGenerator.init(builder.build());
326378
keyGenerator.generateKey();
327379
}
@@ -362,12 +414,7 @@ public void onError(String errorCode, CharSequence errString) {
362414
}
363415

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

372419
} else {
373420
mCancellationSignal = new CancellationSignal();
@@ -478,12 +525,7 @@ public void onError(String errorCode, CharSequence errString) {
478525
}
479526

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

488530
} else {
489531
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)