MixinNetwork / libsignal_protocol_dart

Signal Protocol library for Dart/Flutter
https://pub.dev/packages/libsignal_protocol_dart
GNU General Public License v3.0
160 stars 42 forks source link

Instance of 'InvalidKeyException' #23

Closed alsiPanda closed 3 years ago

alsiPanda commented 3 years ago

I am getting the following error when tryng to encrypt text:

Assertion failed: Instance of 'InvalidKeyException'

0 SessionState.getSenderRatchetKey (package:libsignal_protocol_dart/src/state/SessionState.dart:103:7)

1 SessionCipher.encrypt (package:libsignal_protocol_dart/src/SessionCipher.dart:59:40)

2 SignalProtocol.sessionEncrypt (package:Okuna/services/signal_protocol.dart:166:50)

3 OBDConversationState.sendMessage (package:Okuna/pages/home/pages/channel_chat/conversation.dart:155:26)

4 ChatInputToolbar._sendMessage (package:dash_chat/src/chat_input_toolbar.dart:163:19)

5 ChatInputToolbar.build. (package:dash_chat/src/chat_input_toolbar.dart:149:31)

6 _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:993:19)

7 _InkResponseState.build. (package:flutter/src/material/ink_well.dart:1111:38)

8 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:183:24)

9 TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:598:11)

10 BaseTapGestureRecognizer._checkUp

This is my code - SignalProtocol class:

`class SignalProtocol{

  IdentityKeyPair identityKeyPair;
  int registrationId;

  List<PreKeyRecord> preKeys;

  SignedPreKeyRecord signedPreKey;

  int userId, deviceid;

  UserService userService;

  // No way to store
  InMemorySessionStore sessionStore;
  InMemoryPreKeyStore preKeyStore;
  InMemorySignedPreKeyStore signedPreKeyStore;
  InMemoryIdentityKeyStore identityStore;
  PreKeyBundle myBundle;

  Map<String, String> bundleMap = Map<String, String>();

  SignalProtocol({@required this.userService});

  void install(int UserId, int DeviceId) async {

    await userService.getIdentityKeyPair().then((s){
      if(s==null){
        identityKeyPair = KeyHelper.generateIdentityKeyPair();
      }else{
        Uint8List d = Uint8List.fromList(jsonDecode(s).cast<int>());
        identityKeyPair = IdentityKeyPair.fromSerialized(d);
      }
    });

    await userService.getUserId().then((s){
      if(s==null){
        userId = UserId;
      }else{
        userId = int.parse(s);
      }
    });
    await userService.getRegistrationId().then((s){
      if(s==null){
        registrationId = KeyHelper.generateRegistrationId(false);
      }else{
        registrationId = int.parse(s);
      }
    });
    await userService.getDeviceid().then((s){
      if(s==null){
        deviceid = DeviceId;
      }else{
        deviceid = int.parse(s);
      }
    });

    await userService.getSignedPreKeyRecord().then((s){
      if(s==null){
        signedPreKey = KeyHelper.generateSignedPreKey(identityKeyPair, 0);
      }else{
        Uint8List d = Uint8List.fromList(jsonDecode(s).cast<int>());
        signedPreKey = SignedPreKeyRecord.fromSerialized(d);
      }
    });
    await userService.getPreKeyRecordList().then((s){
      // print('sp prekeys - ${s} ');
      if(s==null){
        preKeys = KeyHelper.generatePreKeys(0, 110);
        print('prekeys init - ${preKeys.runtimeType}}');
      }else{
        print('jsonDecode prekeys ${(jsonDecode(s)).runtimeType}');

        var stringList = jsonDecode(s) as List<dynamic>;
        print('sp string list - ${stringList.runtimeType}');
        preKeys = stringList.map((e) {
          print('inloop ${e.runtimeType}');
          var elist = Uint8List.fromList((e as List<dynamic>).cast<int>());
          print('after cast ${elist.runtimeType}');
          return PreKeyRecord.fromBuffer(elist);
        }
             ).toList();

      }
      print('prekeys - ${preKeys.length} ');
    });

    sessionStore = InMemorySessionStore();
    preKeyStore = InMemoryPreKeyStore();
    signedPreKeyStore = InMemorySignedPreKeyStore();
    identityStore = InMemoryIdentityKeyStore(identityKeyPair, registrationId);

    print('prekeys - ${preKeys.length} ');
    for (var p in preKeys) {
      preKeyStore.storePreKey(p.id, p);
    }
    signedPreKeyStore.storeSignedPreKey(signedPreKey.id, signedPreKey);

    myBundle = PreKeyBundle(
        registrationId,
        0, // deviceId
        preKeys.first.id,
        preKeys.first.getKeyPair().publicKey,
        signedPreKey.id,
        signedPreKey.getKeyPair().publicKey,
        signedPreKey.signature,
        identityKeyPair.getPublicKey());

    bundleMap['registrationId'] = registrationId.toString();
// List<String> pks = preKeys.map((e) => e.serialize().toString()).toList();
    PreKeyRecord pks = preKeys.first;
    bundleMap['preKey'] = jsonEncode(pks.getKeyPair().publicKey.serialize());
    bundleMap['preKeyId'] = preKeys.first.id.toString();
    bundleMap['signedPreKeyId'] = signedPreKey.id.toString();
    bundleMap['signedPreKey'] = jsonEncode(signedPreKey.getKeyPair().publicKey.serialize());
    bundleMap['signature'] = signedPreKey.signature.toString();
    bundleMap['identityKeyPair'] = identityKeyPair.getPublicKey().serialize().toString();

    userService.setSignalData(identityKeyPair: identityKeyPair,
          registrationId: registrationId,
          preKeys: preKeys, signedPreKey: signedPreKey,
          userId: UserId, deviceid: DeviceId);

}

  String sessionEncrypt(PreKeyBundle recievedBundle, String username, String msg) {
    var remoteAddress = SignalProtocolAddress('remote', 1);
    var sessionBuilder = SessionBuilder(sessionStore, preKeyStore,
        signedPreKeyStore, identityStore, remoteAddress);

// sessionBuilder.processPreKeyBundle(recievedBundle);

    var sessionCipher = SessionCipher(sessionStore, preKeyStore,
        signedPreKeyStore, identityStore, remoteAddress);
    CiphertextMessage ciphertext = sessionCipher.encrypt(utf8.encode(msg));

    return ciphertext.serialize().toString();
  }

  String sessionDecrypt(PreKeyBundle recievedBundle, String username, String ciphertext){
    var remoteAddress = SignalProtocolAddress(username, 1);
    var sessionBuilder = SessionBuilder(sessionStore, preKeyStore,
        signedPreKeyStore, identityStore, remoteAddress);

    sessionBuilder.processPreKeyBundle(recievedBundle);

   var sessionCipher = SessionCipher(sessionStore, preKeyStore,
        signedPreKeyStore, identityStore, remoteAddress);

    PreKeySignalMessage mess = PreKeySignalMessage(Uint8List.fromList(ciphertext.codeUnits));
    String text = sessionCipher.decrypt(mess).toString();

    print('Decrypted = ${text}');

    return text;
  }

  void groupSessionEncrypt() {
    var senderKeyName = SenderKeyName("", SignalProtocolAddress("sender", 1));
    var senderKeyStore = InMemorySenderKeyStore();
    var groupSession = GroupCipher(senderKeyStore, senderKeyName);
    groupSession.encrypt(utf8.encode("Hello Mixin"));
  }

}`

I should have mostly followed the example and cant figure out why I am still getting error. The receivedBundle is being reconstructed from its components saved in the server and fetched. The code used for bundle is as follows:

`var bundle = json.decode(otherUser.publicKey);

int registrationId = int.parse(bundle['registrationId'].toString());
List<int> stringList = jsonDecode(bundle['preKey']).cast<int>();
final key = DjbECPublicKey(Uint8List.fromList(stringList));

ECPublicKey preKey = libs.Curve.decodePoint(key.serialize(), 1);
final key2 = DjbECPublicKey(Uint8List.fromList(jsonDecode(bundle['signedPreKey']).cast<int>()));
ECPublicKey signedPreKey = libs.Curve.decodePoint(key2.serialize(), 1);
Uint8List signedPreKeySignature = Uint8List.fromList(jsonDecode(bundle['signature']).cast<int>());
int preKeyId = int.parse(bundle['preKeyId']);
int signedPreKeyId = int.parse(bundle['signedPreKeyId']);
IdentityKey identityKey = IdentityKey.fromBytes(Uint8List.fromList(jsonDecode(bundle['identityKeyPair']).cast<int>()), 0) ;
setState(() {
  recieverBundle = PreKeyBundle(registrationId, 1, preKeyId, preKey, signedPreKeyId, signedPreKey, signedPreKeySignature, identityKey);
});`

Need some help in figuring out what I am doing wrong in the implementation.

xni06 commented 3 years ago

I see that you're not making use of the SignalProtocolStore - take a look at how it should be used.

Then, in your sessionEncrypt method, instead of doing the following:

var sessionBuilder = SessionBuilder(sessionStore, 
                                    preKeyStore,
                                    signedPreKeyStore, 
                                    identityStore, 
                                    remoteAddress);

var sessionCipher = SessionCipher(sessionStore, 
                                  preKeyStore,
                                  signedPreKeyStore, 
                                  identityStore, 
                                  remoteAddress);

... do something like this instead:

String sessionEncrypt(PreKeyBundle preKeyBundle, 
                      SignalProtocolStore store, // NEW
                      String msg) {

    final remoteAddress = SignalProtocolAddress('remote', 1);

    if (!store.containsSession(remoteAddress)) {
        SessionBuilder.fromSignalStore(store, remoteAddress).processPreKeyBundle(preKeyBundle);
    }

    final sessionCipher = SessionCipher.fromStore(store, address);
    return ciphertext.serialize().toString();
alsiPanda commented 3 years ago

Thanks @xni06 , The encryption part seems to be working with this. The decryption part is giving the following error:

InvalidKeyIdException - No such signedprekeyrecord! 0

The code for my decryption is :

String sessionDecrypt(PreKeyBundle recievedBundle, String username, String ciphertext){
    final remoteAddress = SignalProtocolAddress('remote', 1);
    if (!spStore.containsSession(remoteAddress)) {
      SessionBuilder.fromSignalStore(spStore, remoteAddress).processPreKeyBundle(
          recievedBundle);
    }
    final sessionCipher = SessionCipher.fromStore(spStore, remoteAddress);

    PreKeySignalMessage mess = PreKeySignalMessage(Uint8List.fromList(ciphertext.codeUnits));
    String text = sessionCipher.decrypt(mess).toString();
    print('Decrypted = ${text}');
    return text;
  }

Also, can the 'remote' used to create remoteAddress be replaced by a unique username ?

xni06 commented 3 years ago

Similar to when encrypting a message - it all depends on whether your store contains a session or not. In the case of decrypting:

if store contains the session {
    use SessionCipher.decryptFromSignal
}
else {
  use SessionCipher.decrypt
}

Also, can the 'remote' used to create remoteAddress be replaced by a unique username ?

Yes

alsiPanda commented 3 years ago

Thanks @xni06 , I have made the changes, but now getting the following error on the recieving end:

InvalidMessageException - InvalidProtocolBufferException: Protocol message contained an invalid tag (zero).

I am converting the encrypted message from Uint8List to String using String.fromCharCodes(ciphertext.serialize()) and send this via websocket. On the reciever end I am using

String sessionDecrypt(PreKeyBundle recievedBundle, String username, String ciphertext){
    final remoteAddress = SignalProtocolAddress('$username', 1);
    final sessionCipher = SessionCipher.fromStore(spStore, remoteAddress);
    String text = '';

    if (!spStore.containsSession(remoteAddress)) {
      SignalMessage mess = SignalMessage.fromSerialized(Uint8List.fromList(ciphertext.codeUnits));
      text = String.fromCharCodes(sessionCipher.decryptFromSignal(mess));
    }else{
      PreKeySignalMessage mess = PreKeySignalMessage(Uint8List.fromList(ciphertext.codeUnits));
      text = String.fromCharCodes(sessionCipher.decrypt(mess));
    }

    print('Decrypted = ${text}');

    return text;
  }

I have checked the string after encrypting and the string recieved before decrypting, and they are the same. I have also tried saving the uint8list as List instead of string. Still getting the same error. Here is one of the messages encrypted :

[51,8,254,255,255,7,18,33,5,229,245,176,108,213,142,164,187,91,113,153,179,193,53,235,54,200,101,235,116,45,52,121,239,12,187,81,209,165,158,7,14,26,33,5,3 2,183,174,91,146,188,54,92,226,166,144,233,29,245,85,104,210,117,149,252,103,136,105,53,227,229,76,86,4,123,157,82,34,66,51,10,33,5,202,205,163,241,85,15,135,138,226,57,154,203,19,118,1 27,191,113,65,41,101,87,0,53,36,58,146,118,193,153,45,84,117,16,0,24,0,34,16,191,79,16,99,95,30,15,0,250,92,154,245,236,69,228,101,113,2,228,241,239,148,51,57,40,163,94,48,0]

It has a length of 514. I tried removing the final 0 using removeLast(), but still getting the same error.

xni06 commented 3 years ago

Are there any obvious differences in approach between your implementation and that of the existing unit tests?

woinbo commented 3 years ago

Hey, @xni06 I am not getting how to implement this package into my flutter chatting app. Can you please guide me on how to implement signal protocall?

crossle commented 3 years ago

Example work now.