VirgilSecurity / virgil-e3kit-js

E3Kit is a security framework that simplifies work with Virgil services and presents the easiest way to add full end-to-end security to your chat application to become HIPAA and GDPR compliant and more.
https://virgilsecurity.com/e3kit/
BSD 3-Clause "New" or "Revised" License
58 stars 19 forks source link

[React Native] Why fetch stops to work and other issue about hasLocalPrivateKey #24

Closed paolospag closed 4 years ago

paolospag commented 5 years ago

Hello, I’m trying to set up a test chat app end-to-end encryption based with react-native-firebase and virgil-e3kit-js following the e3kit SDK official guide for Firebase, but I had a couple of problems that I am going to illustrate.

First of all, after e3kit SDK installation, fetch stops to work regularly and I encountered a similar warning on the console: “fetch is not a function”. I found a solution by looking at this line from the E3kitReactNative example, but I don't understand why I have to import the module.

Apart from that, I have another problem to solve. As already mentioned, I'm following the official guide and not the react native app example, so I'm trying to adapt the code to my needs. I configured an authentication flow based on react-navigation and in particular on createSwitchNavigator. In my switch navigator-component I have this:

...
import { EThree } from '@virgilsecurity/e3kit';
import createNativeKeyEntryStorage from '@virgilsecurity/key-storage-rn/native';
import 'whatwg-fetch';
...
const CLOUD_FUNCTION_ENDPOINT = 'https://MY-FIREBASE-URL.cloudfunctions.net/api/virgil-jwt';
const fetchToken = async (authToken) => {
  const response = await fetch(CLOUD_FUNCTION_ENDPOINT, {
    headers: new Headers({
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${authToken}`,
    })
  });
  if (!response.ok) {
    throw `Error code: ${response.status} \nMessage: ${response.statusText}`;
  }
  return response.json().then(data => data.token);
};
...
  componentDidMount = () => {
    this.unsubscribe = firebase.auth().onAuthStateChanged(this.authChangedHandler);
  }
  componentWillUnmount = () => {
    if (this.unsubscribe) { this.unsubscribe(); }
  }
...

and this.authChangedHandler looks more or less like this:

  authChangedHandler = (auth) => {
    if (!auth) {
      return this.setState({ authUser: false }, this._unbootstrapAuthUser);
    }
    return this.setState({ authUser: auth._user }, () => this._bootstrapAuthUser(auth));
  }

In this._bootstrapAuthUser I have this:

  _bootstrapAuthUser = async (auth) => {
    const keyEntryStorage = createNativeKeyEntryStorage();
    const getToken = () => auth.getIdToken().then(fetchToken);
    this.eThree = EThree.initialize(getToken, {keyEntryStorage});
    const eThree = await this.eThree;
    const hasPrivateKey = await eThree.hasLocalPrivateKey();
    try {
      if (!hasPrivateKey) await eThree.restorePrivateKey(MY_BRAINKEY_PASSWORD);
    } catch (e) {
      throw e;
    }
    return this._getUserDataFromDatabase(authUser);
  }

but when I try to call eThree.hasLocalPrivateKey() I encountered this warning:

Possible Unhandled Promise Rejection (id: 0):
TypeError: values.map is not a function
TypeError: values.map is not a function
    at Object.deserializeKeyEntries (blob:file:///631c6bbf-4e1b-4120-bd81-d017ea884c78:146946:37)
    at NativeStorage.<anonymous> (blob:file:///631c6bbf-4e1b-4120-bd81-d017ea884c78:146711:42)
    at step (blob:file:///631c6bbf-4e1b-4120-bd81-d017ea884c78:146645:21)
    at Object.next (blob:file:///631c6bbf-4e1b-4120-bd81-d017ea884c78:146575:16)
    at fulfilled (blob:file:///631c6bbf-4e1b-4120-bd81-d017ea884c78:146527:26)
    at tryCallOne (blob:file:///631c6bbf-4e1b-4120-bd81-d017ea884c78:3156:14)
    at blob:file:///631c6bbf-4e1b-4120-bd81-d017ea884c78:3257:17
    at blob:file:///631c6bbf-4e1b-4120-bd81-d017ea884c78:27811:21
    at _callTimer (blob:file:///631c6bbf-4e1b-4120-bd81-d017ea884c78:27700:9)
    at _callImmediatesPass (blob:file:///631c6bbf-4e1b-4120-bd81-d017ea884c78:27736:9)

I don't know if this can be useful to help me solve the problem, but I'm stuck at this point despite I think I have installed virgil-key-storage-rn correctly, installing also buffer and react-native-keychain.

How can I best configure the authentication flow with e3kit SDK?

xLEWKANx commented 5 years ago

Hi, @paolospag I'm investigating your issues, thank you for all provided information.

xLEWKANx commented 5 years ago

Okay, fetch is not working in debug mod, so that's why I required it. It seems to be react native bug. Fetch isn't used anywhere in the our packages, except function that receive a token.

About the second problem, I cannot reproduce it. Could you please share reproducible code with me? It will help to locate the problem faster.

paolospag commented 5 years ago

Hi @xlwknx thanks for your support.

Fetch worked fine even in dev mode before I installed the SDK. So why?

For the second problem, I can send you a package by email next week. Can you give me your email address?

xLEWKANx commented 5 years ago

@paolospag Oh, it used in our root Virgil SDK, so it can be our fault. Thanks for pointing that. My email is asmirnov@virgilsecurity.com

paolospag commented 5 years ago

Hi @xlwknx I sent you an email with all specifications.

I hope you can help me solve the problem.

xLEWKANx commented 5 years ago

Good news, @paolospag! We found the origin of the problem, bug was appeared when virgil-key-storage-rn was used with react-native-firebase. We fixed that problem, so please update virgil-key-storage-rn to version 0.1.3, it should help.

About fetch problem, we cannot fix it without breaking change and this case is not worth it. You can use 3rd party fetch package for development and debugging. When your app will come to production you can just delete this module and not include it to the bundle.

And while looking on your code, I have a tip for you. You don't need to initialize e3kit in all component. You could just initialize it once in separate module. You can use initialize snippet from official guide and add export eThreePromise;. Then you can import that promise from different components, and this promise will be resolved with actual user eThree instance.

paolospag commented 5 years ago

Thanks for the support @xlwknx , I'll try to update virgil-key-storage-rn and I'll let you know.

About fetch, I would like to understand if it is possible to create a cloud function and call it via firebase httpsCallable() method, e.g. firebase.functions().httpsCallable('getVirgilJWT') thus avoiding using fetch. Moreover this would help me not to upset the structure of my already existing cloud functions file in which other httpsCallable methods are written. Do you think it is possible to rewrite this in httpsCallable way? How can I possibly do it?

About initialization, can you tell me how to import eThree instance and use it in the Switcher.js file with my code?

xLEWKANx commented 5 years ago

@paolospag please tell the results was the fix successful and if everything is ok, we can close this issue.

I think there is no problem to make firebase function callable through firebase library.

For all further questions which is not related to the e3kit-js client library I invite you to our slack and write me directly or via #firebase channel.

Beaudinn commented 4 years ago

Hey @paolospag, I know this is an old topic. but have the same problem, I see that you have gone further through slack. Have you come to a conclusion? I use the Virgil E3Kit SDK for React Native have tried different things even tried in a clean project with no success.

xLEWKANx commented 4 years ago

@Beaudinn could you please describe your problem in more details? Tell us:

  1. Your react-native version
  2. Error logs
  3. Which virgil packages are you using and their versions
  4. Sample code which reproduces your problem would let as to help you as fast as possible. But if is it not an option you can send here e3kit initialization code and the code, which cause the problem.
Beaudinn commented 4 years ago

@xlwknx here is my setup

My react-native version and virgil packages. "react": "~16.9.0", "react-native": "~0.61.5", "@virgilsecurity/e3kit-native": "^2.0.0", "@virgilsecurity/key-storage-rn": "^0.2.2", "react-native-virgil-crypto": "^0.5.3", "react-native-keychain": "^4.0.5", "@react-native-community/async-storage": "^1.8.0",

Error logs YellowBox.js:71 Possible Unhandled Promise Rejection (id: 0): TypeError: values.map is not a function TypeError: values.map is not a function at Object.deserializeKeyEntries (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false:182542:37) at NativeStorage.<anonymous> (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false:182308:42) at step (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false:182255:21) at Object.next (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false:182185:16) at fulfilled (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false:182139:26) at tryCallOne (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false:3246:14) at http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false:3347:17 at http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false:30732:21 at _callTimer (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false:30621:9)

For storage i used both keyEntryStorage and react native AsyncStorage same result on both. I also tried fetching the token with the firebase httpsCallable function, same result . seems to me also not related, has to do with saving the keys tho te storage.

    // here we will keep link to promise with initialized e3kit library
    this.eThree = new Promise((resolve, reject) => {
      firebase.auth().onAuthStateChanged(async user => {
        if (user) {
          //const getToken = firebase.functions().httpsCallable('getVirgilJwt'); //() => getToken().then(result => result.data.token)
          const getToken = () => user.getIdToken().then(fetchToken);

          this.state.setState({authUser : user})
          //const keyEntryStorage = createNativeKeyEntryStorage();

          this.eThree = EThree.initialize(getToken, {AsyncStorage});
          this.eThree.then(resolve).catch(reject);
          const eThree = await this.eThree;

          // if user has private key locally, then he didn't logout
          if (await eThree.hasLocalPrivateKey()) await this.openChatWindow(user, eThree)

        } else {

          this.state.setState(state.defaultState);
          // cleanup private key on logout
          this.eThree.then(eThree => eThree.cleanup());
        }
      });
    });
  }
snanovskyi commented 4 years ago

Hey @Beaudinn!

Thank you for detailed report 👍

We saw similar issue for a few times already. It happens in virgil-key-storage-rn because Keychain has some invalid data that we can't read properly.

Right now we don't have a proper fix for it and we're trying to figure out the best way to do it. Hopefully we will release an update soon.

For now you can do the following to clear it:

await eThree.keyEntryStorage.storage.clear();

Hope it helps!

Beaudinn commented 4 years ago

Aah thanx @snanovskyi ! makes sense now. and it is resolved!