Skip to content

Commit

Permalink
Update documentation in README
Browse files Browse the repository at this point in the history
  • Loading branch information
oblador committed Feb 27, 2018
1 parent c2cc328 commit ad8061e
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 132 deletions.
256 changes: 126 additions & 130 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,135 +4,97 @@

[![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)

Keychain Access for React Native. Currently functionality is limited to just storing internet and generic passwords.
Keychain/Keystore Access for React Native.

### New 2.0.0 with improved android implementation
## Installation

The KeychainModule will now automatically use the appropriate CipherStorage implementation based on API level:
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`

* API level 16-22 will en/de crypt using Facebook Conceal
* API level 23+ will en/de crypt using Android Keystore
See manual installation below if you have issues with `react-native link`.

Encrypted data is stored in SharedPreferences.
## Usage

## Installation
```js
import * as Keychain from 'react-native-keychain';

1 . `$ npm install --save react-native-keychain`
async () => {
const username = 'zuck';
const password = 'poniesRgr8';

or
// Store the credentials
await Keychain.setGenericPassword(username, password);

`$ yarn add react-native-keychain`
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()
}
```

See `KeychainExample` for fully working project example.

2 . `$ react-native link react-native-keychain` and check `MainApplication.java` to verify the package was added.
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.

3 .  rebuild your project
### `setGenericPassword(username, password, [{ accessControl, accessible, accessGroup, service }])`

Will store the username/password combination in the secure storage. Resolves to `{ username, password }` if an entry exists or `false` if it doesn't.

* on Android, 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.
### `getGenericPassword([{ authenticationPrompt, service }])`

Check out the "releases" tab for breaking changes and RN version compatibility. v1.0.0 is for RN >= 0.40
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.

## ❗ Enable `Keychain Sharing` entitlement for iOS 10
### `resetGenericPassword([{ service }])`

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.
Will remove the username/password combination from the secure storage.

![screen shot 2016-09-16 at 20 56 33](https://cloud.githubusercontent.com/assets/512692/18597833/15316342-7c50-11e6-92e7-781651e61563.png)
### `setInternetCredentials(server, username, password, [{ accessControl, accessible, accessGroup }])`

```
Error: {
code = "-34018";
domain = NSOSStatusErrorDomain;
message = "The operation couldn\U2019t be completed. (OSStatus error -34018.)";
}
```
Will store the server/username/password combination in the secure storage.

### `getInternetCredentials(server, [{ authenticationPrompt }])`

## Usage
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.

See `KeychainExample` for fully working project example.
### `resetInternetCredentials(server)`

Both `setGenericPassword` and `setInternetCredentials` allow to store strings only!
Will remove the server/username/password combination from the secure storage.

Both `getInternetCredentials` and `getGenericPassword` will resolve to the stored value, or to false, in case there is no record stored. They will reject only if an unexpected error is encountered.
### `requestSharedWebCredentials()` (iOS only)

```js
import * as Keychain from 'react-native-keychain';
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.

const username = 'zuck';
const password = 'poniesRgr8';

// Generic Password, service argument optional
Keychain
.setGenericPassword(username, password)
.then(function() {
console.log('Credentials saved successfully!');
});

// service argument optional
Keychain
.getGenericPassword()
.then(function(credentials) {
console.log('Credentials successfully loaded for user ' + credentials.username);
}).catch(function(error) {
console.log('Keychain couldn\'t be accessed!', error);
});

// service argument optional
Keychain
.resetGenericPassword()
.then(function() {
console.log('Credentials successfully deleted');
});

// Internet Password, server argument required
const server = 'http://facebook.com';
Keychain
.setInternetCredentials(server, username, password)
.then(function() {
console.log('Credentials saved successfully!');
});

Keychain
.getInternetCredentials(server)
.then(function(credentials) {
if (credentials) {
console.log('Credentials successfully loaded for user ' + credentials.username);
}
});
### `setSharedWebCredentials(server, username, password)` (iOS only)

Keychain
.resetInternetCredentials(server)
.then(function() {
console.log('Credentials successfully deleted');
});
Sets a shared web credential. Resolves to `true` when successful.

Keychain
.requestSharedWebCredentials()
.then(function(credentials) {
if (credentials) {
console.log('Shared web credentials successfully loaded for user ' + credentials.username);
}
})
### `canImplyAuthentication([{ authenticationType }])`

Keychain
.setSharedWebCredentials(server, username, password)
.then(function() {
console.log('Shared web credentials saved successfully!');
})
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.

```
### `getSupportedBiometryType()`

Get what type of hardware biometry support the device has. Resolves to a `Keychain.BIOMETRY_TYPE` value when supported, otherwise `null`.

### Options

| Key | Applies to | Description | Default |
| Key | Platform | Description | Default |
|---|---|---|---|
|**`accessControl`**|`setGenericPassword`, `setInternetCredentials`|This dictates how a keychain item may be used, see possible values in `Keychain.ACCESS_CONTROL`. |*None*|
|**`accessible`**|`setGenericPassword`, `setInternetCredentials`|This dictates when a keychain item is accessible, see possible values in `Keychain.ACCESSIBLE`. |*`Keychain.ACCESSIBLE.WHEN_UNLOCKED`*|
|**`accessGroup`**|`setGenericPassword`, `setInternetCredentials`|In which App Group to share the keychain. Requires additional setup with entitlements. |*None*|
|**`authenticationPrompt`**|`getGenericPassword`, `getInternetCredentials`|What to prompt the user when unlocking the keychain with biometry or device password. |`Authenticate to retrieve secret`|
|**`authenticationType`**|`canImplyAuthentication`|Policies specifying which forms of authentication are acceptable. |`Keychain.AUTHENTICATION_TYPE.DEVICE_PASSCODE_OR_BIOMETRICS`|
|**`service`**|`setGenericPassword`, `getGenericPassword`|Reverse domain name qualifier for the service associated with password. |*App bundle ID*|
|**`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*|

#### `Keychain.ACCESS_CONTROL` enum

Expand Down Expand Up @@ -165,9 +127,12 @@ Keychain
|**`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).|

### Note on security
#### `Keychain.BIOMETRY_TYPE` enum

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.
| Key | Description |
|-----|-------------|
|**`TOUCH_ID`**|Device supports authentication with Touch ID.|
|**`FACE_ID`**|Device supports authentication with Face ID.|

## Manual Installation

Expand All @@ -186,59 +151,73 @@ Add the following to your `Podfile` and run `pod update`:
pod 'RNKeychain', :path => '../node_modules/react-native-keychain'
```

#### 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

#### Option: Manually

* Edit `android/settings.gradle` to look like this (without the +):

```diff
rootProject.name = 'MyApp'
```diff
rootProject.name = 'MyApp'

include ':app'
include ':app'

+ include ':react-native-keychain'
+ project(':react-native-keychain').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keychain/android')
```
+ 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:

```diff
apply plugin: 'com.android.application'
```diff
apply plugin: 'com.android.application'

android {
...
}
android {
...
}

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')
}
```
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')
}
```

* Edit your `MainApplication.java` (deep in `android/app/src/main/java/...`) to look like this (note **two** places to edit):

```diff
package com.myapp;
```diff
package com.myapp;

+ import com.oblador.keychain.KeychainPackage;
+ import com.oblador.keychain.KeychainPackage;

....
....

public class MainActivity extends extends ReactActivity {
public class MainActivity extends extends ReactActivity {

@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
+ new KeychainPackage()
);
}
...
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
+ new KeychainPackage()
);
}
```
...
}
```

#### Proguard Rules

Expand All @@ -257,6 +236,23 @@ If so, add a proguard rule in `proguard-rules.pro`:
}
```

## 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>
Expand Down
3 changes: 1 addition & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,7 @@ export function canImplyAuthentication(options?: Options): Promise {
}

/**
* Get what type of local authentication policy (LAPolicy) is supported
* on this device with the device settings the user chose.
* Get what type of hardware biometry support the device has.
* @return {Promise} Resolves to a `BIOMETRY_TYPE` when supported, otherwise `null`
*/
export function getSupportedBiometryType(): Promise {
Expand Down

0 comments on commit ad8061e

Please sign in to comment.