| author | Matthew Wild
<mwild1@gmail.com> 2025-05-09 09:48:52 UTC |
| committer | Stephen Paul Weber
<singpolyma@singpolyma.net> 2025-09-29 13:43:04 UTC |
| parent | 63389c56375e8459d83625b2da62bf6dbfa21803 |
| doc/OMEMO.md | +13 | -7 |
| snikket/OMEMO.hx | +38 | -19 |
diff --git a/doc/OMEMO.md b/doc/OMEMO.md index 232867d..48412bd 100644 --- a/doc/OMEMO.md +++ b/doc/OMEMO.md @@ -9,10 +9,16 @@ compile with the NO_OMEMO flag. ## TODO / known issues -- No caching of remote contact devices -- No API to control encryption of outgoing messages -- No API to determine cryptographic identity of message sender -- Persistence: only IndexedDB backend is currently implemented -- Encryption status reported by the API can be forged by sender -- Outgoing messages are not encrypted to the sending account's other devices -- No support for group chats +- [x] One-to-one bidirectional OMEMO +- [x] Healing of broken sessions +- [x] Remove and replace consumed prekeys +- [x] Allow non-OMEMO messages to recipients with no published keys when policy allows +- [x] Encrypt outgoing messages to the sending account's other devices +- [x] Persistence: IndexedDB (for web) +- [ ] Use cache for remote contact devices +- [ ] Persistence: SQLite backend (for native) +- [ ] API to control encryption of outgoing messages +- [ ] API to determine cryptographic identity of message sender +- [ ] Fix that encryption status reported by the API can be forged by sender +- [ ] Group chat support + diff --git a/snikket/OMEMO.hx b/snikket/OMEMO.hx index 5113b3f..d31c786 100644 --- a/snikket/OMEMO.hx +++ b/snikket/OMEMO.hx @@ -1093,31 +1093,50 @@ class OMEMO { return sessionCipher.encrypt(keyWithTag); } + // Convert a key from a string of raw bytes to base64 + private static function b64EncodeKey(keyStr:String) { + #if js + // Haxe cannot natively convert this string to a byte array. It only supports two + // encodings - 'UTF8' and 'RawNative'. The former wrongly tries to interpret + // the binary data as UTF-8 sequences, and the latter translates each character + // to a pair of bytes (since JS uses UTF-16). + return Browser.window.btoa(keyStr); + #else + return Base64.encode(Bytes.ofString(keyStr, RawNative)); + #end + } + + private function encryptForDevice(sid:Int, jid:String, rid:Int, encryptionResult:OMEMOEncryptionResult):Promise<OMEMOPayloadKey> { + final promSessionCipher = getSessionCipher(sid, jid, rid); + return promSessionCipher.then((sessionCipher) -> { + return encryptPayloadKeyForSession(encryptionResult, sessionCipher).then((encryptedKey) -> { + final payloadKey:OMEMOPayloadKey = { + rid: rid, + prekey: encryptedKey.type == 3, + encodedKey: b64EncodeKey(encryptedKey.body), + }; + return payloadKey; + }); + }); + } + private function buildOMEMOHeader(encryptionResult:OMEMOEncryptionResult, sid:Int, jid:String, deviceList:Array<Int>):Promise<Stanza> { final promKeys = [ for(rid in deviceList) { - final promSessionCipher = getSessionCipher(sid, jid, rid); - promSessionCipher.then((sessionCipher) -> { - return encryptPayloadKeyForSession(encryptionResult, sessionCipher).then((encryptedKey) -> { - final payloadKey:OMEMOPayloadKey = { - rid: rid, - prekey: encryptedKey.type == 3, -#if js - // Haxe cannot natively convert this string to a byte array. It only supports two - // encodings - 'UTF8' and 'RawNative'. The former wrongly tries to interpret - // the binary data as UTF-8 sequences, and the latter translates each character - // to a pair of bytes (since JS uses UTF-16). - encodedKey: Browser.window.btoa(encryptedKey.body) -#else - encodedKey: Base64.encode(Bytes.ofString(encryptedKey.body, RawNative)) -#end - }; - return payloadKey; - }); - }); + encryptForDevice(sid, jid, rid, encryptionResult); } ]; + // We've included keys for our contact's devices, now we need + // to include any of our own devices, so they can read the outgoing + // message also. + for(rid in this.deviceList) { + // Don't encrypt to our own device (we already have the original message locally) + if(sid != rid) { + promKeys.push(encryptForDevice(sid, this.client.accountId(), rid, encryptionResult)); + } + } + final promHeader = new Promise((resolve, reject) -> { PromiseTools.all(promKeys).then((recipientKeys) -> { final header:OMEMOPayload = {