react-native-keychain/README.md

295 lines
12 KiB
Markdown
Raw Normal View History

2018-02-25 14:12:36 +00:00
<p align="center"><img src="https://user-images.githubusercontent.com/378279/36642269-6195b10c-1a3d-11e8-9e1b-37a3d1bcf7b3.png" align="center" width="150" height="201" alt="" /></p>
<h1 align="center">react-native-keychain</h1>
[![Travis](https://img.shields.io/travis/oblador/react-native-keychain.svg)](https://travis-ci.org/oblador/react-native-keychain) [![npm](https://img.shields.io/npm/v/react-native-keychain.svg)](https://npmjs.com/package/react-native-keychain) [![npm](https://img.shields.io/npm/dm/react-native-keychain.svg)](https://npmjs.com/package/react-native-keychain)
2018-02-27 12:58:37 +00:00
Keychain/Keystore Access for React Native.
2015-05-20 18:39:52 +00:00
2018-02-27 12:58:37 +00:00
## Installation
2018-02-27 12:58:37 +00:00
1. `$ yarn add react-native-keychain`
2. `$ react-native link react-native-keychain` and check `MainApplication.java` to verify the package was added.
3. Rebuild your project with `react-native run-ios/android`
2018-02-27 12:58:37 +00:00
See manual installation below if you have issues with `react-native link`.
2018-02-27 12:58:37 +00:00
## Usage
2018-02-27 12:58:37 +00:00
```js
import * as Keychain from 'react-native-keychain';
2015-05-20 18:39:52 +00:00
2018-02-27 12:58:37 +00:00
async () => {
const username = 'zuck';
const password = 'poniesRgr8';
2018-02-27 12:58:37 +00:00
// Store the credentials
await Keychain.setGenericPassword(username, password);
2017-03-26 23:29:36 +00:00
2018-02-27 12:58:37 +00:00
try {
// Retreive the credentials
const credentials = Keychain.getGenericPassword();
if (credentials) {
console.log('Credentials successfully loaded for user ' + credentials.username);
} else {
console.log('No credentials stored')
}
} catch (error) {
console.log('Keychain couldn\'t be accessed!', error);
}
await Keychain.resetGenericPassword()
}
```
2018-02-27 12:58:37 +00:00
See `KeychainExample` for fully working project example.
2015-05-20 18:39:52 +00:00
2018-02-27 12:58:37 +00:00
Both `setGenericPassword` and `setInternetCredentials` are limited to strings only, so if you need to store objects etc, please use `JSON.stringify`/`JSON.parse` when you store/access it.
2018-02-27 12:58:37 +00:00
### `setGenericPassword(username, password, [{ accessControl, accessible, accessGroup, service }])`
2018-02-27 12:58:37 +00:00
Will store the username/password combination in the secure storage. Resolves to `{ username, password }` if an entry exists or `false` if it doesn't.
2018-02-27 12:58:37 +00:00
### `getGenericPassword([{ authenticationPrompt, service }])`
2018-02-27 12:58:37 +00:00
Will retreive the username/password combination from the secure storage. It will reject only if an unexpected error is encountered like lacking entitlements or permission.
2018-02-27 12:58:37 +00:00
### `resetGenericPassword([{ service }])`
2018-02-27 12:58:37 +00:00
Will remove the username/password combination from the secure storage.
2018-02-27 12:58:37 +00:00
### `setInternetCredentials(server, username, password, [{ accessControl, accessible, accessGroup }])`
2018-02-27 12:58:37 +00:00
Will store the server/username/password combination in the secure storage.
2015-05-20 18:39:52 +00:00
2018-02-27 12:58:37 +00:00
### `getInternetCredentials(server, [{ authenticationPrompt }])`
2016-01-19 17:19:04 +00:00
2018-02-27 12:58:37 +00:00
Will retreive the server/username/password combination from the secure storage. Resolves to `{ username, password }` if an entry exists or `false` if it doesn't. It will reject only if an unexpected error is encountered like lacking entitlements or permission.
2015-05-20 18:39:52 +00:00
2018-02-27 12:58:37 +00:00
### `resetInternetCredentials(server)`
2015-05-20 18:41:10 +00:00
2018-02-27 12:58:37 +00:00
Will remove the server/username/password combination from the secure storage.
2018-02-25 21:18:26 +00:00
2018-02-27 12:58:37 +00:00
### `requestSharedWebCredentials()` (iOS only)
2018-02-25 21:18:26 +00:00
2018-02-27 12:58:37 +00:00
Asks the user for a shared web credential. Requires additional setup both in the app and server side, see [Apple documentation](https://developer.apple.com/documentation/security/shared_web_credentials). Resolves to `{ server, username, password }` if approved and `false` if denied and throws an error if not supported on platform or there's no shared credentials.
2015-05-20 18:39:52 +00:00
2018-02-27 12:58:37 +00:00
### `setSharedWebCredentials(server, username, password)` (iOS only)
2015-05-20 18:39:52 +00:00
2018-02-27 12:58:37 +00:00
Sets a shared web credential. Resolves to `true` when successful.
2015-05-20 18:39:52 +00:00
2018-02-27 12:58:37 +00:00
### `canImplyAuthentication([{ authenticationType }])`
2018-02-27 12:58:37 +00:00
Inquire if the type of local authentication policy is supported on this device with the device settings the user chose. Should be used in combination with `accessControl` option in the setter functions. Resolves to `true` if supported.
2018-02-27 12:58:37 +00:00
### `getSupportedBiometryType()`
Get what type of hardware biometry support the device has. Resolves to a `Keychain.BIOMETRY_TYPE` value when supported, otherwise `null`.
2015-05-20 18:39:52 +00:00
2018-02-25 23:38:31 +00:00
### Options
2018-02-27 12:58:37 +00:00
| Key | Platform | Description | Default |
2018-02-25 23:38:31 +00:00
|---|---|---|---|
2018-02-27 12:58:37 +00:00
|**`accessControl`**|iOS only|This dictates how a keychain item may be used, see possible values in `Keychain.ACCESS_CONTROL`. |*None*|
|**`accessible`**|iOS only|This dictates when a keychain item is accessible, see possible values in `Keychain.ACCESSIBLE`. |*`Keychain.ACCESSIBLE.WHEN_UNLOCKED`*|
|**`accessGroup`**|iOS only|In which App Group to share the keychain. Requires additional setup with entitlements. |*None*|
|**`authenticationPrompt`**|iOS only|What to prompt the user when unlocking the keychain with biometry or device password. |`Authenticate to retrieve secret`|
|**`authenticationType`**|iOS only|Policies specifying which forms of authentication are acceptable. |`Keychain.AUTHENTICATION_TYPE.DEVICE_PASSCODE_OR_BIOMETRICS`|
|**`service`**|All|Reverse domain name qualifier for the service associated with password. |*App bundle ID*|
2018-02-25 23:38:31 +00:00
#### `Keychain.ACCESS_CONTROL` enum
| Key | Description |
|-----|-------------|
|**`USER_PRESENCE`**|Constraint to access an item with either Touch ID or passcode.|
|**`BIOMETRY_ANY`**|Constraint to access an item with Touch ID for any enrolled fingers.|
|**`BIOMETRY_CURRENT_SET`**|Constraint to access an item with Touch ID for currently enrolled fingers.|
|**`DEVICE_PASSCODE`**|Constraint to access an item with a passcode.|
|**`APPLICATION_PASSWORD`**|Constraint to use an application-provided password for data encryption key generation.|
2018-02-25 23:38:31 +00:00
|**`BIOMETRY_ANY_OR_DEVICE_PASSCODE`**|Constraint to access an item with Touch ID for any enrolled fingers or passcode.|
|**`BIOMETRY_CURRENT_SET_OR_DEVICE_PASSCODE`**|Constraint to access an item with Touch ID for currently enrolled fingers or passcode.|
#### `Keychain.ACCESSIBLE` enum
| Key | Description |
|-----|-------------|
|**`WHEN_UNLOCKED`**|The data in the keychain item can be accessed only while the device is unlocked by the user.|
|**`AFTER_FIRST_UNLOCK`**|The data in the keychain item cannot be accessed after a restart until the device has been unlocked once by the user.|
|**`ALWAYS`**|The data in the keychain item can always be accessed regardless of whether the device is locked.|
|**`WHEN_PASSCODE_SET_THIS_DEVICE_ONLY`**|The data in the keychain can only be accessed when the device is unlocked. Only available if a passcode is set on the device. Items with this attribute never migrate to a new device.|
|**`WHEN_UNLOCKED_THIS_DEVICE_ONLY`**|The data in the keychain item can be accessed only while the device is unlocked by the user. Items with this attribute do not migrate to a new device.|
|**`AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY`**|The data in the keychain item cannot be accessed after a restart until the device has been unlocked once by the user. Items with this attribute never migrate to a new device.|
|**`ALWAYS_THIS_DEVICE_ONLY`**|The data in the keychain item can always be accessed regardless of whether the device is locked. Items with this attribute never migrate to a new device.|
#### `Keychain.AUTHENTICATION_TYPE` enum
| Key | Description |
|-----|-------------|
|**`DEVICE_PASSCODE_OR_BIOMETRICS`**|Device owner is going to be authenticated by biometry or device passcode.|
|**`BIOMETRICS`**|Device owner is going to be authenticated using a biometric method (Touch ID or Face ID).|
2018-02-27 12:58:37 +00:00
#### `Keychain.BIOMETRY_TYPE` enum
2018-02-27 12:58:37 +00:00
| Key | Description |
|-----|-------------|
|**`TOUCH_ID`**|Device supports authentication with Touch ID.|
|**`FACE_ID`**|Device supports authentication with Face ID.|
2016-08-26 06:40:09 +00:00
## Manual Installation
2016-08-26 06:40:09 +00:00
### iOS
#### Option: Manually
2016-08-26 06:40:09 +00:00
* Right click on Libraries, select **Add files to "…"** and select `node_modules/react-native-keychain/RNKeychain.xcodeproj`
* Select your project and under **Build Phases** -> **Link Binary With Libraries**, press the + and select `libRNKeychain.a`.
#### Option: With [CocoaPods](https://cocoapods.org/)
Add the following to your `Podfile` and run `pod update`:
```
pod 'RNKeychain', :path => '../node_modules/react-native-keychain'
```
2018-02-27 12:58:37 +00:00
#### Enable `Keychain Sharing` entitlement for iOS 10+
For iOS 10 you'll need to enable the `Keychain Sharing` entitlement in the `Capabilities` section of your build target. (See screenshot). Otherwise you'll experience the error shown below.
![screen shot 2016-09-16 at 20 56 33](https://cloud.githubusercontent.com/assets/512692/18597833/15316342-7c50-11e6-92e7-781651e61563.png)
```
Error: {
code = "-34018";
domain = NSOSStatusErrorDomain;
message = "The operation couldn\U2019t be completed. (OSStatus error -34018.)";
}
```
### Android
2016-08-26 06:40:09 +00:00
#### Option: Manually
2016-08-26 06:40:09 +00:00
* Edit `android/settings.gradle` to look like this (without the +):
2018-02-27 12:58:37 +00:00
```diff
rootProject.name = 'MyApp'
2018-02-27 12:58:37 +00:00
include ':app'
2018-02-27 12:58:37 +00:00
+ include ':react-native-keychain'
+ project(':react-native-keychain').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keychain/android')
```
* Edit `android/app/build.gradle` (note: **app** folder) to look like this:
2018-02-27 12:58:37 +00:00
```diff
apply plugin: 'com.android.application'
2018-02-27 12:58:37 +00:00
android {
...
}
2018-02-27 12:58:37 +00:00
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.0.1'
compile 'com.facebook.react:react-native:0.19.+'
+ compile project(':react-native-keychain')
}
```
2017-01-10 21:56:19 +00:00
* Edit your `MainApplication.java` (deep in `android/app/src/main/java/...`) to look like this (note **two** places to edit):
2018-02-27 12:58:37 +00:00
```diff
package com.myapp;
2018-02-27 12:58:37 +00:00
+ import com.oblador.keychain.KeychainPackage;
2018-02-27 12:58:37 +00:00
....
2018-02-27 12:58:37 +00:00
public class MainActivity extends extends ReactActivity {
2018-02-27 12:58:37 +00:00
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
+ new KeychainPackage()
);
}
2018-02-27 12:58:37 +00:00
...
}
```
#### Proguard Rules
On Android builds that use proguard (like release), you may see the following error:
```
RNKeychainManager: no keychain entry found for service:
JNI DETECTED ERROR IN APPLICATION: JNI FindClass called with pending exception java.lang.NoSuchFieldError: no "J" field "mCtxPtr" in class "Lcom/facebook/crypto/cipher/NativeGCMCipher;" or its superclasses
```
If so, add a proguard rule in `proguard-rules.pro`:
```
-keep class com.facebook.crypto.** {
*;
}
```
2018-02-27 12:58:37 +00:00
## Notes
### Android
The module will automatically use the appropriate CipherStorage implementation based on API level:
* API level 16-22 will en/de crypt using Facebook Conceal
* API level 23+ will en/de crypt using Android Keystore
Encrypted data is stored in SharedPreferences.
The `setInternetCredentials(server, username, password)` call will be resolved as call to `setGenericPassword(username, password, server)`. Use the `server` argument to distinguish between multiple entries.
### Security
On API levels that do not support Android keystore, Facebook Conceal is used to en/decrypt stored data. The encrypted data is then stored in SharedPreferences. Since Conceal itself stores its encryption key in SharedPreferences, it follows that if the device is rooted (or if an attacker can somehow access the filesystem), the key can be obtained and the stored data can be decrypted. Therefore, on such a device, the conceal encryption is only an obscurity. On API level 23+ the key is stored in the Android Keystore, which makes the key non-exportable and therefore makes the entire process more secure. Follow best practices and do not store user credentials on a device. Instead use tokens or other forms of authentication and re-ask for user credentials before performing sensitive operations.
## Maintainers
<table>
<tbody>
<tr>
<td align="center">
<a href="https://github.com/oblador">
<img width="150" height="150" src="https://github.com/oblador.png?v=3&s=150">
<br>
<strong>Joel Arvidsson</strong>
</a>
<br>
Author
</td>
<td align="center">
<a href="https://github.com/vonovak">
<img width="150" height="150" src="https://github.com/vonovak.png?v=3&s=150">
</br>
<strong>Vojtech Novak</strong>
</a>
<br>
Maintainer
</td>
<td align="center">
<a href="https://github.com/pcoltau">
<img width="150" height="150" src="https://github.com/pcoltau.png?v=3&s=150">
</br>
<strong>Pelle Stenild Coltau</strong>
</a>
<br>
Maintainer
</td>
</tr>
<tbody>
</table>
2015-05-20 18:39:52 +00:00
## License
2018-01-08 21:23:24 +00:00
MIT © Joel Arvidsson 2016-2018