├── .github └── FUNDING.yml ├── .gitignore ├── .gitlint ├── .pubignore ├── .woodpecker.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── example └── omemo_dart_example.dart ├── flake.lock ├── flake.nix ├── lib ├── omemo_dart.dart └── src │ ├── common │ └── constants.dart │ ├── crypto.dart │ ├── double_ratchet │ ├── double_ratchet.dart │ └── kdf.dart │ ├── errors.dart │ ├── helpers.dart │ ├── keys.dart │ ├── omemo │ ├── bundle.dart │ ├── decryption_result.dart │ ├── device.dart │ ├── encrypted_key.dart │ ├── encryption_result.dart │ ├── errors.dart │ ├── fingerprint.dart │ ├── omemo.dart │ ├── queue.dart │ ├── ratchet_data.dart │ ├── ratchet_map_key.dart │ └── stanza.dart │ ├── protobuf │ ├── .gitkeep │ ├── schema.pb.dart │ ├── schema.pbenum.dart │ ├── schema.pbjson.dart │ └── schema.pbserver.dart │ ├── trust │ ├── always.dart │ ├── base.dart │ ├── btbv.dart │ └── never.dart │ └── x3dh │ └── x3dh.dart ├── omemo_dart.doap ├── protobuf └── schema.proto ├── pubspec.yaml └── test ├── double_ratchet_test.dart ├── omemo_test.dart ├── queue_test.dart ├── trust_test.dart └── x3dh_test.dart /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: papatutuwawa 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub. 2 | .dart_tool/ 3 | .packages 4 | 5 | # Conventional directory for build outputs. 6 | build/ 7 | 8 | # Omit committing pubspec.lock for library packages; see 9 | # https://dart.dev/guides/libraries/private-files#pubspeclock. 10 | pubspec.lock 11 | 12 | # NixOS 13 | .direnv 14 | .envrc 15 | -------------------------------------------------------------------------------- /.gitlint: -------------------------------------------------------------------------------- 1 | [general] 2 | ignore=B5,B6,B7,B8 3 | [title-max-length] 4 | line-length=72 5 | [title-trailing-punctuation] 6 | [title-hard-tab] 7 | [title-match-regex] 8 | regex=^(feat|fix|test|release|chore|security|docs|refactor|style|ci):.*$ 9 | [body-trailing-whitespace] 10 | [body-first-line-empty] 11 | -------------------------------------------------------------------------------- /.pubignore: -------------------------------------------------------------------------------- 1 | lib/protobuf 2 | -------------------------------------------------------------------------------- /.woodpecker.yml: -------------------------------------------------------------------------------- 1 | pipeline: 2 | analysis: 3 | image: dart:3.0.7 4 | commands: 5 | # Proxy requests to pub.dev using pubcached 6 | - PUB_HOSTED_URL=http://172.17.0.1:8000 dart pub get 7 | - dart analyze --fatal-infos --fatal-warnings 8 | - dart test 9 | when: 10 | path: 11 | includes: ['lib/**', 'test/**'] 12 | notify: 13 | image: git.polynom.me/papatutuwawa/woodpecker-xmpp 14 | settings: 15 | xmpp_is_muc: 1 16 | xmpp_tls: 1 17 | xmpp_recipient: moxxy-build@muc.moxxy.org 18 | xmpp_alias: 2Bot 19 | secrets: [ xmpp_jid, xmpp_password, xmpp_server ] 20 | when: 21 | status: 22 | - failure 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.0 2 | 3 | - Initial version 4 | - Implement the Double Ratchet, X3DH and OMEMO specific bits 5 | - Add a Blind-Trust-Before-Verification TrustManager 6 | - Supported OMEMO version: 0.8.3 7 | 8 | ## 0.1.3 9 | 10 | - Fix bug with the Double Ratchet causing only the initial message to be decryptable 11 | - Expose `getDeviceMap` as a developer usable function 12 | ## 0.2.0 13 | 14 | - Add convenience functions `getDeviceId` and `getDeviceBundle` 15 | - Creating a new ratchet with an id for which we already have a ratchet will now overwrite the old ratchet 16 | - Ratchet now carry an "acknowledged" attribute 17 | 18 | ## 0.2.1 19 | 20 | - Add `isRatchetAcknowledged` 21 | - Ratchets that are created due to accepting a kex are now unacknowledged 22 | 23 | ## 0.3.0 24 | 25 | - Implement enabling and disabling ratchets via the TrustManager interface 26 | - Fix deserialization of the various objects 27 | - Remove the BTBV TrustManager's loadState method. Just use the constructor 28 | - Allow removing all ratchets for a given Jid 29 | - If an error occurs while decrypting the message, the ratchet will now be reset to its prior state 30 | - Fix a bug within the Varint encoding function. This should fix some occasional UnknownSignedPrekeyExceptions 31 | - Remove OmemoSessionManager's toJson and fromJson. Use toJsonWithoutSessions and fromJsonWithoutSessions. Restoring sessions is not out-of-scope for that function 32 | 33 | ## 0.3.1 34 | 35 | - Fix a bug that caused the device's id to change when replacing a OPK 36 | - Every decryption failure now causes the ratchet to be restored to a pre-decryption state 37 | - Add method to get the device's fingerprint 38 | 39 | ## 0.4.0 40 | 41 | - Deprecate `OmemoSessionManager`. Use `OmemoManager` instead. 42 | - Implement queued access to the ratchets inside the `OmemoManager`. 43 | - Implement heartbeat messages. 44 | - [BREAKING] Rename `Device` to `OmemoDevice`. 45 | 46 | ## 0.4.1 47 | 48 | - Fix fetching the current device and building a ratchet session with it when encrypting for our own JID 49 | 50 | ## 0.4.2 51 | 52 | - Fix removeAllRatchets not removing, well, all ratchets. In fact, it did not remove any ratchet. 53 | 54 | ## 0.4.3 55 | 56 | - Fix bug that causes ratchets to be unable to decrypt anything after receiving a heartbeat with a completely new session 57 | 58 | ## 0.5.0 59 | 60 | This version is a complete rework of omemo_dart! 61 | 62 | - Removed events from `OmemoManager` 63 | - Removed `OmemoSessionManager` 64 | - Removed serialization/deserialization code 65 | - Replace exceptions with errors inside a result type 66 | - Ratchets and trust data is now loaded and cached on demand 67 | - Accessing the trust manager must happen via `withTrustManager` 68 | - Overriding the base implementations is replaced by providing callback functions 69 | 70 | ## 0.5.1 71 | 72 | - Remove `added` and `replaced` from the data passed to the `CommitRatchetsCallback` 73 | - Added a list of newly added and replaced ratchets to the encryption and decryption results. This is useful for displaying messages like "Contact added a new device" 74 | 75 | ## 0.6.0 76 | 77 | - Bump dependencies to fix running with never version of Dart 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Alexander "PapaTutuWawa" 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # omemo_dart 2 | 3 | [![status-badge](https://ci.polynom.me/api/badges/16/status.svg)](https://ci.polynom.me/repos/16) 4 | 5 | `omemo_dart` is a Dart library to help developers of Dart/Flutter XMPP clients to implement 6 | [OMEMO](https://xmpp.org/extensions/xep-0384.html) in its newest version - currently 0.8.3. 7 | 8 | The library provides an implementation of the [X3DH](https://signal.org/docs/specifications/x3dh/) 9 | key exchange, the [Double Ratchet](https://signal.org/docs/specifications/doubleratchet/) with 10 | the OMEMO 0.8.3 specific `ENCRYPT`, `DECRYPT` and `KDF_*` functions and a very high-level 11 | `OmemoSessionManager` that manages all Double Ratchet sessions and provides a clean and simple 12 | interface for encrypting a message for all known Ratchet sessions we have with a user. 13 | 14 | This library also has no dependency on any XMPP library. `omemo_dart` instead defines an 15 | intermediary format for the required data that you, the user, will need to transform to and from 16 | the stanza format of your preferred XMPP library yourself. 17 | 18 | ## Important Notes 19 | 20 | - **Please note that this library has not been audited for its security! Use at your own risk!** 21 | - This library is not tested with other implementations of OMEMO 0.8.3 as I do not know of any client implementing spec compliant OMEMO 0.8.3. It does, however, work with itself. 22 | 23 | ## Usage 24 | 25 | Include `omemo_dart` in your `pubspec.yaml` like this: 26 | 27 | ```yaml 28 | # [...] 29 | 30 | dependencies: 31 | omemo_dart: 32 | hosted: https://git.polynom.me/api/packages/PapaTutuWawa/pub 33 | version: ^0.5.0 34 | # [...] 35 | 36 | # [...] 37 | ``` 38 | 39 | ### Example 40 | 41 | This repository includes a documented ["example"](./example/omemo_dart_example.dart) that explains the basic usage of the library while 42 | leaving out the XMPP specific bits. For a more functional and integrated example, see the `omemo_client.dart` example from 43 | [moxxmpp](https://codeberg.org/moxxy/moxxmpp). 44 | 45 | ### Persistence 46 | 47 | 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. 48 | In order to allow persistence, your application needs to keep track of the following: 49 | 50 | - The `OmemoDevice` assigned to the `OmemoManager` 51 | - `JID -> [int]`: The device list for each JID 52 | - `(JID, device) -> Ratchet`: The actual ratchet 53 | 54 | If you also use the `BlindTrustBeforeVerificationTrustManager`, you additionally need to keep track of: 55 | 56 | - `(JID, device) -> (int, bool)`: The trust level and the enablement state 57 | 58 | ## Contributing 59 | 60 | When submitting a PR, please run the linter using `dart analyze` and make sure that all 61 | tests still pass using `dart test`. 62 | 63 | To ensure uniform commit message formatting, please also use `gitlint` to lint your commit 64 | messages' formatting. 65 | 66 | ## License 67 | 68 | Licensed under the MIT license. 69 | 70 | See `LICENSE`. 71 | 72 | ## Support 73 | 74 | If you like what I do and you want to support me, feel free to donate to me on Ko-Fi. 75 | 76 | [](https://ko-fi.com/papatutuwawa) 77 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:very_good_analysis/analysis_options.yaml 2 | linter: 3 | rules: 4 | public_member_api_docs: false 5 | lines_longer_than_80_chars: false 6 | use_setters_to_change_properties: false 7 | avoid_positional_boolean_parameters: false 8 | avoid_bool_literals_in_conditional_expressions: false 9 | 10 | analyzer: 11 | exclude: 12 | - "lib/src/protobuf/*.dart" 13 | - "example/omemo_dart_example.dart" 14 | -------------------------------------------------------------------------------- /example/omemo_dart_example.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:omemo_dart/omemo_dart.dart'; 3 | 4 | /// This example aims to demonstrate how omemo_dart is used. Since omemo_dart is not 5 | /// dependent on any XMPP library, you need to convert stanzas to the appropriate 6 | /// intermediary format and back. 7 | void main() async { 8 | const aliceJid = 'alice@some.server'; 9 | const bobJid = 'bob@other.serve'; 10 | 11 | // You are Alice and want to begin using OMEMO, so you first create an OmemoManager. 12 | final aliceManager = OmemoManager( 13 | // Generate Alice's OMEMO device bundle. We can specify how many One-time Prekeys we want, but 14 | // per default, omemo_dart generates 100 (recommended by XEP-0384). 15 | await OmemoDevice.generateNewDevice(aliceJid), 16 | // The trust manager we want to use. In this case, we use the provided one that 17 | // implements "Blind Trust Before Verification". To make things simpler, we keep 18 | // no persistent data and can thus use the MemoryBTBVTrustManager. If we wanted to keep 19 | // the state, we would have to override BlindTrustBeforeVerificationTrustManager. 20 | BlindTrustBeforeVerificationTrustManager(), 21 | // This function is called whenever we need to send an OMEMO heartbeat to [recipient]. 22 | // [result] is the encryted data to include. This needs to be wired into your XMPP library's 23 | // OMEMO implementation. 24 | // For simplicity, we use an empty function and imagine it works. 25 | (result, recipient) async => {}, 26 | // This function is called whenever we need to fetch the device list for [jid]. 27 | // This needs to be wired into your XMPP library's OMEMO implementation. 28 | // For simplicity, we use an empty function and imagine it works. 29 | (jid) async => [], 30 | // This function is called whenever we need to fetch the device bundle with id [id] from [jid]. 31 | // This needs to be wired into your XMPP library's OMEMO implementation. 32 | // For simplicity, we use an empty function and imagine it works. 33 | (jid, id) async => null, 34 | // This function is called whenever we need to subscribe to [jid]'s device list PubSub node. 35 | // This needs to be wired into your XMPP library's OMEMO implementation. 36 | // For simplicity, we use an empty function and imagine it works. 37 | (jid) async {}, 38 | // This function is called whenever our own device bundle has to be republished to our PEP node. 39 | // This needs to be wired into your XMPP library's OMEMO implementation. 40 | // For simplicity, we use an empty function and imagine it works. 41 | (device) async {}, 42 | ); 43 | 44 | // Bob, on his side, also creates an [OmemoManager] similar to Alice. 45 | final bobManager = OmemoManager( 46 | await OmemoDevice.generateNewDevice(bobJid), 47 | BlindTrustBeforeVerificationTrustManager(), 48 | (result, recipient) async => {}, 49 | (jid) async => [], 50 | (jid, id) async => null, 51 | (jid) async {}, 52 | (device) async {}, 53 | ); 54 | 55 | // Alice prepares to send the message to Bob, so she builds the message stanza and 56 | // collects all the children of the stanza that should be encrypted into a string. 57 | // Note that this leaves out the wrapping stanza, i.e. if we want to send a 58 | // we only include the 's children. 59 | const aliceMessageStanzaBody = ''' 60 | Hello Bob, it's me, Alice! 61 | 62 | '''; 63 | 64 | // Since OMEMO 0.8.3 mandates usage of XEP-0420: Stanza Content Encryption, we have to 65 | // wrap our acual payload - aliceMessageStanzaBody - into an SCE envelope. Note that 66 | // the rpad element must contain a random string. See XEP-0420 for recommendations. 67 | // OMEMO makes the