diff --git a/CHANGELOG.md b/CHANGELOG.md
index 210eb12..74a727d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -54,3 +54,14 @@
## 0.4.3
- Fix bug that causes ratchets to be unable to decrypt anything after receiving a heartbeat with a completely new session
+
+## 0.5.0
+
+This version is a complete rework of omemo_dart!
+
+- Removed events from `OmemoManager`
+- Removed `OmemoSessionManager`
+- Removed serialization/deserialization code
+- Replace exceptions with errors inside a result type
+- Ratchets and trust data is now loaded and cached on demand
+- Accessing the trust manager must happen via `withTrustManager`
\ No newline at end of file
diff --git a/README.md b/README.md
index 38631fd..65529ef 100644
--- a/README.md
+++ b/README.md
@@ -28,22 +28,32 @@ Include `omemo_dart` in your `pubspec.yaml` like this:
dependencies:
omemo_dart:
hosted: https://git.polynom.me/api/packages/PapaTutuWawa/pub
- version: ^0.4.3
+ version: ^0.5.0
# [...]
# [...]
```
-## Contributing
+### Example
-Due to issues with `protobuf`, `omemo_dart` reimplements the Protobuf encoding for the required
-OMEMO messages. As such, `protobuf` is only a dependency for testing that the serialisation and
-deserialisation of the custom implementation. In order to run tests, you need the Protbuf
-compiler. After that, making sure that
-the [Dart Protobuf compiler addon](https://pub.dev/packages/protoc_plugin) and the
-Protobuf compiler itself is in your PATH,
-run `protoc -I=./protobuf/ --dart_out=lib/protobuf/ ./protobuf/schema.proto` in the
-repository's root to generate the real Protobuf bindings.
+This repository includes a documented ["example"](./example/omemo_dart_example.dart) that explains the basic usage of the library while
+leaving out the XMPP specific bits. For a more functional and integrated example, see the `omemo_client.dart` example from
+[moxxmpp](https://codeberg.org/moxxy/moxxmpp).
+
+### Persistence
+
+By default, `omemo_dart` uses in-memory implementations for everything. For a real-world application, this is unsuitable as OMEMO devices would be constantly added.
+In order to allow persistence, your application needs to keep track of the following:
+
+- The `OmemoDevice` assigned to the `OmemoManager`
+- `JID -> [int]`: The device list for each JID
+- `(JID, device) -> Ratchet`: The actual ratchet
+
+If you also use the `BlindTrustBeforeVerificationTrustManager`, you additionally need to keep track of:
+
+- `(JID, device) -> (int, bool)`: The trust level and the enablement state
+
+## Contributing
When submitting a PR, please run the linter using `dart analyze` and make sure that all
tests still pass using `dart test`.
diff --git a/analysis_options.yaml b/analysis_options.yaml
index beb23d6..b5d1d46 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -9,8 +9,5 @@ linter:
analyzer:
exclude:
- - "lib/protobuf/*.dart"
- # TODO: Remove once OmemoSessionManager is gone
- - "test/omemo_test.dart"
+ - "lib/src/protobuf/*.dart"
- "example/omemo_dart_example.dart"
- - "test/serialisation_test.dart"
diff --git a/example/omemo_dart_example.dart b/example/omemo_dart_example.dart
index 405d465..2a9d741 100644
--- a/example/omemo_dart_example.dart
+++ b/example/omemo_dart_example.dart
@@ -35,6 +35,10 @@ void main() async {
// This needs to be wired into your XMPP library's OMEMO implementation.
// For simplicity, we use an empty function and imagine it works.
(jid) async {},
+ // This function is called whenever our own device bundle has to be republished to our PEP node.
+ // This needs to be wired into your XMPP library's OMEMO implementation.
+ // For simplicity, we use an empty function and imagine it works.
+ (device) async {},
);
// Alice now wants to chat with Bob at his bare Jid "bob@other.server". To make things
@@ -42,7 +46,7 @@ void main() async {
// request it using PEP and then convert the device bundle into a OmemoBundle object.
final bobManager = OmemoManager(
await OmemoDevice.generateNewDevice(bobJid),
- MemoryBTBVTrustManager(),
+ BlindTrustBeforeVerificationTrustManager(),
(result, recipient) async => {},
(jid) async => [],
(jid, id) async => null,
@@ -145,6 +149,11 @@ void main() async {
/// The text of the element, if it exists. If not, then the message might be
/// a hearbeat, where no payload is sent. In that case, use null.
payload,
+
+ /// Since we did not receive this message due to a catch-up mechanism, like MAM, we
+ /// set this to false. If we, however, did use a catch-up mechanism, we must set this
+ /// to true to prevent the OPKs from being replaced.
+ false,
),
);
diff --git a/lib/omemo_dart.dart b/lib/omemo_dart.dart
index f0f9677..acc9b7a 100644
--- a/lib/omemo_dart.dart
+++ b/lib/omemo_dart.dart
@@ -8,9 +8,10 @@ export 'src/omemo/bundle.dart';
export 'src/omemo/device.dart';
export 'src/omemo/encrypted_key.dart';
export 'src/omemo/encryption_result.dart';
-export 'src/omemo/events.dart';
+export 'src/omemo/errors.dart';
export 'src/omemo/fingerprint.dart';
-export 'src/omemo/omemomanager.dart';
+export 'src/omemo/omemo.dart';
+export 'src/omemo/ratchet_data.dart';
export 'src/omemo/ratchet_map_key.dart';
export 'src/omemo/stanza.dart';
export 'src/trust/base.dart';
diff --git a/lib/src/common/constants.dart b/lib/src/common/constants.dart
new file mode 100644
index 0000000..8b0fcbd
--- /dev/null
+++ b/lib/src/common/constants.dart
@@ -0,0 +1,11 @@
+/// The overarching assumption is that we use Ed25519 keys for the identity keys
+const omemoX3DHInfoString = 'OMEMO X3DH';
+
+/// The info used for when encrypting the AES key for the actual payload.
+const omemoPayloadInfoString = 'OMEMO Payload';
+
+/// Info string for ENCRYPT
+const encryptHkdfInfoString = 'OMEMO Message Key Material';
+
+/// Amount of messages we may skip per session
+const maxSkip = 1000;
diff --git a/lib/src/crypto.dart b/lib/src/crypto.dart
index ce421c2..7010b00 100644
--- a/lib/src/crypto.dart
+++ b/lib/src/crypto.dart
@@ -1,5 +1,7 @@
import 'dart:convert';
import 'package:cryptography/cryptography.dart';
+import 'package:moxlib/moxlib.dart';
+import 'package:omemo_dart/src/errors.dart';
import 'package:omemo_dart/src/keys.dart';
/// Performs X25519 with [kp] and [pk]. If [identityKey] is set, then
@@ -92,7 +94,7 @@ Future> aes256CbcEncrypt(
/// A small helper function to make AES-256-CBC easier. Decrypt [ciphertext] using [key] as
/// the encryption key and [iv] as the IV. Returns the ciphertext.
-Future> aes256CbcDecrypt(
+Future>> aes256CbcDecrypt(
List ciphertext,
List key,
List iv,
@@ -100,13 +102,19 @@ Future> aes256CbcDecrypt(
final algorithm = AesCbc.with256bits(
macAlgorithm: MacAlgorithm.empty,
);
- return algorithm.decrypt(
- NoMacSecretBox(
- ciphertext,
- nonce: iv,
- ),
- secretKey: SecretKey(key),
- );
+ try {
+ return Result(
+ await algorithm.decrypt(
+ NoMacSecretBox(
+ ciphertext,
+ nonce: iv,
+ ),
+ secretKey: SecretKey(key),
+ ),
+ );
+ } catch (ex) {
+ return Result(MalformedCiphertextError(ex));
+ }
}
/// OMEMO often uses the output of a HMAC-SHA-256 truncated to its first 16 bytes.
diff --git a/lib/src/double_ratchet/crypto.dart b/lib/src/double_ratchet/crypto.dart
deleted file mode 100644
index 42033ed..0000000
--- a/lib/src/double_ratchet/crypto.dart
+++ /dev/null
@@ -1,60 +0,0 @@
-import 'package:omemo_dart/src/crypto.dart';
-import 'package:omemo_dart/src/errors.dart';
-import 'package:omemo_dart/src/helpers.dart';
-import 'package:omemo_dart/src/protobuf/omemo_authenticated_message.dart';
-import 'package:omemo_dart/src/protobuf/omemo_message.dart';
-
-/// Info string for ENCRYPT
-const encryptHkdfInfoString = 'OMEMO Message Key Material';
-
-/// Signals ENCRYPT function as specified by OMEMO 0.8.3.
-/// Encrypt [plaintext] using the message key [mk], given associated_data [associatedData]
-/// and the AD output from the X3DH [sessionAd].
-Future> encrypt(
- List mk,
- List plaintext,
- List associatedData,
- List sessionAd,
-) async {
- // Generate encryption, authentication key and IV
- final keys = await deriveEncryptionKeys(mk, encryptHkdfInfoString);
- final ciphertext =
- await aes256CbcEncrypt(plaintext, keys.encryptionKey, keys.iv);
-
- final header =
- OmemoMessage.fromBuffer(associatedData.sublist(sessionAd.length))
- ..ciphertext = ciphertext;
- final headerBytes = header.writeToBuffer();
- final hmacInput = concat([sessionAd, headerBytes]);
- final hmacResult = await truncatedHmac(hmacInput, keys.authenticationKey);
- final message = OmemoAuthenticatedMessage()
- ..mac = hmacResult
- ..message = headerBytes;
- return message.writeToBuffer();
-}
-
-/// Signals DECRYPT function as specified by OMEMO 0.8.3.
-/// Decrypt [ciphertext] with the message key [mk], given the associated_data [associatedData]
-/// and the AD output from the X3DH.
-Future> decrypt(
- List mk,
- List ciphertext,
- List associatedData,
- List sessionAd,
-) async {
- // Generate encryption, authentication key and IV
- final keys = await deriveEncryptionKeys(mk, encryptHkdfInfoString);
-
- // Assumption ciphertext is a OMEMOAuthenticatedMessage
- final message = OmemoAuthenticatedMessage.fromBuffer(ciphertext);
- final header = OmemoMessage.fromBuffer(message.message!);
-
- final hmacInput = concat([sessionAd, header.writeToBuffer()]);
- final hmacResult = await truncatedHmac(hmacInput, keys.authenticationKey);
-
- if (!listsEqual(hmacResult, message.mac!)) {
- throw InvalidMessageHMACException();
- }
-
- return aes256CbcDecrypt(header.ciphertext!, keys.encryptionKey, keys.iv);
-}
diff --git a/lib/src/double_ratchet/double_ratchet.dart b/lib/src/double_ratchet/double_ratchet.dart
index b997d4a..2a3a204 100644
--- a/lib/src/double_ratchet/double_ratchet.dart
+++ b/lib/src/double_ratchet/double_ratchet.dart
@@ -1,47 +1,24 @@
-import 'dart:convert';
import 'package:cryptography/cryptography.dart';
import 'package:hex/hex.dart';
import 'package:meta/meta.dart';
+import 'package:moxlib/moxlib.dart';
+import 'package:omemo_dart/src/common/constants.dart';
import 'package:omemo_dart/src/crypto.dart';
-import 'package:omemo_dart/src/double_ratchet/crypto.dart';
import 'package:omemo_dart/src/double_ratchet/kdf.dart';
import 'package:omemo_dart/src/errors.dart';
import 'package:omemo_dart/src/helpers.dart';
import 'package:omemo_dart/src/keys.dart';
-import 'package:omemo_dart/src/protobuf/omemo_message.dart';
-
-/// Amount of messages we may skip per session
-const maxSkip = 1000;
-
-class RatchetStep {
- const RatchetStep(this.header, this.ciphertext);
- final OmemoMessage header;
- final List ciphertext;
-}
+import 'package:omemo_dart/src/protobuf/schema.pb.dart';
@immutable
class SkippedKey {
const SkippedKey(this.dh, this.n);
- factory SkippedKey.fromJson(Map data) {
- return SkippedKey(
- OmemoPublicKey.fromBytes(
- base64.decode(data['public']! as String),
- KeyPairType.x25519,
- ),
- data['n']! as int,
- );
- }
-
+ /// The DH public key for which we skipped a message key.
final OmemoPublicKey dh;
- final int n;
- Future