MixinNetwork / libsignal_protocol_dart

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

whisperType CiphertextMessage #61

Closed abhay-s-rawat closed 2 years ago

abhay-s-rawat commented 2 years ago

I am but confused with the following types of CiphertextMessage.

When we will get whisperType type message ? I guess it will be message when reciever bundle had no 1 time key present , right ? In that case when creating PreKeyBundle the int _preKeyId, ECPublicKey _preKeyPublic should be nullable , right ? as stated in https://signal.org/docs/specifications/x3dh/

If the bundle does not contain a one-time prekey, she calculates:

    DH1 = DH(IKA, SPKB)
    DH2 = DH(EKA, IKB)
    DH3 = DH(EKA, SPKB)
    SK = KDF(DH1 || DH2 || DH3)

If the bundle does contain a one-time prekey, the calculation is modified to include an additional DH:

    DH4 = DH(EKA, OPKB)
    SK = KDF(DH1 || DH2 || DH3 || DH4)
  static const int whisperType = 2; //=> When we will get this ?
  static const int prekeyType = 3; //  When sender had one time pre key of receiver
  static const int senderKeyType = 4; // group msg type, I guess
  static const int senderKeyDistributionType = 5; // This is initial kdm msg , used in groups

bundle implementation as below

PreKeyBundle getPreKeyBundle(Map<String, dynamic> remoteBundle) {
    ECPublicKey tempPrePublicKey = Curve.decodePoint(
        DjbECPublicKey(base64Decode(remoteBundle["preKeys"].first['key']))
            .serialize(),
        1);
    int tempPreKeyId = remoteBundle["preKeys"].first['id'];
    int tempSignedPreKeyId = remoteBundle["signedPreKey"]['id'];
    ECPublicKey? tempSignedPreKeyPublic = Curve.decodePoint(
        DjbECPublicKey(base64Decode(remoteBundle["signedPreKey"]['key']))
            .serialize(),
        1);
    Uint8List? tempSignedPreKeySignature =
        base64Decode(remoteBundle["signedPreKey"]['signature']);
    IdentityKey tempIdentityKey = IdentityKey(Curve.decodePoint(
        DjbECPublicKey(base64Decode(remoteBundle["identityKey"])).serialize(),
        1));
    return PreKeyBundle(
      remoteBundle['registrationId'],
      1,
      tempPreKeyId,
      tempPrePublicKey,
      tempSignedPreKeyId,
      tempSignedPreKeyPublic,
      tempSignedPreKeySignature,
      tempIdentityKey,
    );
  }
abhay-s-rawat commented 2 years ago

I will do a pull request as soon I as I am finished with my example to make example more informative. Group part is remaining.

abhay-s-rawat commented 2 years ago

Seems like its already implemented but not pushed to pub.dev.

can you please answer When we will get whisperType type message ?

Tougee commented 2 years ago

Yes, nullable one-time prekey is implemented in https://github.com/MixinNetwork/libsignal_protocol_dart/commit/7a486a57efc1bc7026b8c7f5fd4cbe08d49d1b56 but are not yet published.

The whisperType is related to SignalMessage, and as part of the Double Ratchet Protocol, you will receive this type of message when users are texting each other.

crossle commented 2 years ago

Published https://pub.dev/packages/libsignal_protocol_dart

abhay-s-rawat commented 2 years ago

Yes, nullable one-time prekey is implemented in 7a486a5 but are not yet published.

The whisperType is related to SignalMessage, and as part of the Double Ratchet Protocol, you will receive this type of message when users are texting each other.

But I am always getting prekeyType message . What to do ?

PreKeySignalMessage pre = PreKeySignalMessage(base64Decode(data["msg"]));
Tougee commented 2 years ago

whisperType messages are generated by encrypt in session_cipher.dart, and examples in session_cipher_test.dart

abhay-s-rawat commented 2 years ago

Sorry for the confusion. When session is established from both ends I am getting whispertype messages. I am working on a example to demonstrate things clearly. Well in latest PreKeyBundle we can establish session without one time pre keys, that ok but why are signedpre keys nullable ? Am I missing something ?

(new) PreKeyBundle PreKeyBundle(
  int _registrationId,
  int _deviceId,
  int? _preKeyId,
  ECPublicKey? _preKeyPublic,
  int _signedPreKeyId,
  ECPublicKey? _signedPreKeyPublic,
  Uint8List? _signedPreKeySignature,
  IdentityKey _identityKey,
)
abhay-s-rawat commented 2 years ago

I am currently decrypting like below, is there a class that is parent of both PreKeySignalMessage ,SignalMessage ? Because I have to send message type in payload seperatly to deal with msg types.

Map data = jsonDecode(msg);
    if (data["type"] == CiphertextMessage.prekeyType) {
      PreKeySignalMessage pre = PreKeySignalMessage(base64Decode(data["msg"]));
      Uint8List plaintext = await session.decrypt(pre);
      String dectext = utf8.decode(plaintext);
      return dectext;
    } else if (data["type"] == CiphertextMessage.whisperType) {
      SignalMessage signalMsg =
          SignalMessage.fromSerialized(base64Decode(data["msg"]));
      Uint8List plaintext = await session.decryptFromSignal(signalMsg);
      String dectext = utf8.decode(plaintext);
      return dectext;
    } else {
      return "signal msg";
    }
Tougee commented 2 years ago

Well in latest PreKeyBundle we can establish session without one time pre keys, that ok but why are signedpre keys nullable ? Am I missing something ?

I don't think this is necessary, but it will help this library have better interoperability with other libraries implemented by other languages, like Java. If we receive a nullable signed prekey from the Java side, then we can successfully deserialize and throw the right exception to the caller.

is there a class that is parent of both PreKeySignalMessage ,SignalMessage ?

CiphertextMessage?

abhay-s-rawat commented 2 years ago

CiphertextMessage is abstract.

Currently I am using a map which is jsonencoded as a payload to send , in that payload I was sending the type of message and while decrypting I was checking that type and decrypt accordingly.

I made example project look like below as soon as I finish for group , I will give pull request. Screenshot_1665327481

Tougee commented 2 years ago

This is great! Expect your PR.

abhay-s-rawat commented 2 years ago

https://github.com/MixinNetwork/libsignal_protocol_dart/pull/62 Did pr.

abhay-s-rawat commented 2 years ago

I was also thinking to show how to change signedprekeys but I fell short of vision. Did you remember "your security code has been changed with $user" from whatsapp, is it because of signed prekeys or user changed phone and identity keys got reset .

How changing signed prekey will hurt existing sessions, can you help me out in this ? I have to update example for this. Already gave fingerprint comparision aka safety number in terms of signal protocol in example.

Tougee commented 2 years ago

👍 Thanks for the great work, I'll review it asap.

Did you remember "your security code has been changed with $user" from whatsapp, is it because of signed prekeys or user changed phone and identity keys got reset .

AFAIK yes.

How changing signed prekey will hurt existing sessions

A new session should be established via the X3DH protocol if one side clear/reset keys.

abhay-s-rawat commented 2 years ago

1 more thing to ask. Some of the classes below throw errors when something is not present, can it return null if key not present instead of throwing error ? like Future loadPreKey will become Future<PreKeyRecord?> loadPreKey.

import 'dart:collection';
import 'dart:typed_data';

import '../../invalid_key_id_exception.dart';
import '../pre_key_record.dart';
import '../pre_key_store.dart';

class InMemoryPreKeyStore extends PreKeyStore {
  final store = HashMap<int, Uint8List>();

  @override
  Future<bool> containsPreKey(int preKeyId) async =>
      store.containsKey(preKeyId);

  @override
  Future<PreKeyRecord> loadPreKey(int preKeyId) async {
    if (!store.containsKey(preKeyId)) {
      throw InvalidKeyIdException('No such prekeyrecord! - $preKeyId');
    }
    return PreKeyRecord.fromBuffer(store[preKeyId]!);
  }

  @override
  Future<void> removePreKey(int preKeyId) async {
    store.remove(preKeyId);
  }

  @override
  Future<void> storePreKey(int preKeyId, PreKeyRecord record) async {
    store[preKeyId] = record.serialize();
  }
}
Tougee commented 2 years ago

No special reasons, we ported this library from Java and adopted this style.

abhay-s-rawat commented 2 years ago

Actually I am thinking to make it more like dart implementation. Is it ok to do so ?

crossle commented 2 years ago

Actually I am thinking to make it more like dart implementation. Is it ok to do so ?

This only for InMemoryPreKeyStore, You can implements your self PreKeyStore.

abhay-s-rawat commented 2 years ago

Actually I am thinking to make it more like dart implementation. Is it ok to do so ?

This only for InMemoryPreKeyStore, You can implements your self PreKeyStore.

Yes I implemented self store but the thing is the abstract class has "Future of PreKeyRecord loadPreKey" definition due to which all extented class must return non nullable objects.

abhay-s-rawat commented 2 years ago

My self store published below, if you plan to change types to nullable rather than throwing errors , please do tell me by opening a issue in (https://github.com/abhay-s-rawat/libsignal_protocol_hive_store). Thanks for your time. https://pub.dev/packages/libsignal_protocol_hive_store

crossle commented 2 years ago

Actually I am thinking to make it more like dart implementation. Is it ok to do so ?

This only for InMemoryPreKeyStore, You can implements your self PreKeyStore.

Yes I implemented self store but the thing is the abstract class has "Future of PreKeyRecord loadPreKey" definition due to which all extented class must return non nullable objects.

Cool, Welcome PR