git » sdk » commit f7d396d

OMEMO: Enable client-wide encryption policy support

author Matthew Wild
2025-04-22 19:22:03 UTC
committer Matthew Wild
2025-04-22 19:22:03 UTC
parent ea9795bac6adf0a820a4767ef18daa660165b004

OMEMO: Enable client-wide encryption policy support

With the default policy, this fixes encryption to recipients with no published
keys.

Note that there is no API to set a custom encryption policy yet. The default
is opportunistic.

doc/OMEMO.md +0 -1
snikket/Client.hx +7 -0
snikket/OMEMO.hx +32 -3

diff --git a/doc/OMEMO.md b/doc/OMEMO.md
index e25d16f..edaa116 100644
--- a/doc/OMEMO.md
+++ b/doc/OMEMO.md
@@ -15,6 +15,5 @@ compile with the NO_OMEMO flag.
 - Persistence: only IndexedDB backend is currently implemented
 - Encryption status reported by the API can be forged by sender
 - Consumed prekeys are not removed and replaced
-- Messages to recipients with no published keys fail to send
 - Outgoing messages are not encrypted to the sending account's other devices
 - No support for group chats
diff --git a/snikket/Client.hx b/snikket/Client.hx
index 3ca56b3..8b7e998 100644
--- a/snikket/Client.hx
+++ b/snikket/Client.hx
@@ -11,6 +11,7 @@ import snikket.ChatMessage;
 import snikket.Message;
 import snikket.EventEmitter;
 import snikket.EventHandler;
+import snikket.EncryptionPolicy;
 #if !NO_OMEMO
 import snikket.OMEMO;
 #end
@@ -94,6 +95,12 @@ class Client extends EventEmitter {
 	private var fastMechanism: Null<String> = null;
 	private var token: Null<String> = null;
 	private final pendingCaps: Map<String, Array<(Null<Caps>)->Chat>> = [];
+	@:allow(snikket)
+	private final encryptionPolicy:EncryptionPolicy = {
+		allowUnencryptedOutgoing: true,
+		allowUnencryptedIncoming: true,
+		preferEncryptedOutgoing: true,
+	};
 
 #if !NO_OMEMO
 	@:allow(snikket)
diff --git a/snikket/OMEMO.hx b/snikket/OMEMO.hx
index bb00538..bc6568a 100644
--- a/snikket/OMEMO.hx
+++ b/snikket/OMEMO.hx
@@ -923,9 +923,9 @@ class OMEMO {
 				promRecipientDevices.then((recipientDevices) -> {
 					promEncryptedMessage.then((encryptionResult) -> {
 						buildOMEMOHeader(encryptionResult, deviceId, recipient.asString(), recipientDevices).then(resolve, reject);
-					});
-				});
-			});
+					}, reject);
+				}, reject);
+			}, reject);
 		});
 
 		final promStanza = promHeader.then((header) -> {
@@ -935,6 +935,35 @@ class OMEMO {
 			newStanza.textTag("encryption", "", { xmlns: "urn:xmpp:eme:0", namespace: "eu.siacs.conversations.axolotl" });
 			newStanza.textTag("body", "I sent you an OMEMO encrypted message but your client doesn’t seem to support that. Find more information on https://conversations.im/omemo");
 			return newStanza;
+		}, (failureReason) -> {
+			final noRecipientSupport = failureReason == "no-devices";
+			var allowUnencrypted:Bool = client.encryptionPolicy.allowUnencryptedOutgoing;
+
+			var errMsg:String;
+			if(noRecipientSupport) {
+				errMsg = "Encryption failed because no recipient devices could be found";
+			} else {
+				errMsg = "Encryption failed due to internal error: " + failureReason;
+				// Since this failure is not expected, we'll only allow the stanza
+				// through if the policy does not prefer encrypted communications. If
+				// encrypted communication *is* preferred, we need a good excuse to
+				// send unencrypted (such as no recipient support), but no such excuse
+				// is found here.
+				allowUnencrypted = allowUnencrypted && !client.encryptionPolicy.preferEncryptedOutgoing;
+			}
+
+			if(!allowUnencrypted) {
+				// Policy forbids outgoing unencrypted messages or some unexpected
+				// error occurred (the latter is not a reason to override preferences)
+				// FIXME: We need to report this to the UI somehow
+				throw "Unable to send message: " + errMsg;
+			}
+
+			trace("OMEMO: Skipping encryption (permitted by policy): " + errMsg);
+
+			// Encryption failed, but policy says this is ok.
+			// Just pass through the original stanza to be sent.
+			return stanza;
 		});
 
 		return promStanza;