aboutsummaryrefslogtreecommitdiff
path: root/main/src/plugins/omemo/jingle/jet_omemo.vala
diff options
context:
space:
mode:
authorMarvin W <git@larma.de>2026-10-04 14:42:16 +0200
committerMarvin W <git@larma.de>2026-10-23 16:59:59 +0200
commitdba577f39c489b9db29960f2bf5cc78ac4f29d7b (patch)
treea8354c9fc328c1442d5e8c5c2adec4b428c92f44 /main/src/plugins/omemo/jingle/jet_omemo.vala
parent2413442905e31f34cb0f77c9167003b3725760fe (diff)
Move OMEMO, OpenPGP and RTP plugins into main/libdino
Diffstat (limited to 'main/src/plugins/omemo/jingle/jet_omemo.vala')
-rw-r--r--main/src/plugins/omemo/jingle/jet_omemo.vala115
1 files changed, 115 insertions, 0 deletions
diff --git a/main/src/plugins/omemo/jingle/jet_omemo.vala b/main/src/plugins/omemo/jingle/jet_omemo.vala
new file mode 100644
index 00000000..07d03f64
--- /dev/null
+++ b/main/src/plugins/omemo/jingle/jet_omemo.vala
@@ -0,0 +1,115 @@
+using Dino.Crypto;
+using Dino.Crypto.Omemo;
+using Dino;
+using Dino.Entities;
+using Gee;
+using Omemo;
+using Xmpp;
+using Xmpp.Xep;
+
+namespace Dino.Plugins.JetOmemo {
+
+private const string NS_URI = "urn:xmpp:jingle:jet-omemo:0";
+private const string AES_128_GCM_URI = "urn:xmpp:ciphers:aes-128-gcm-nopadding";
+
+public class Module : XmppStreamModule, Jet.EnvelopEncoding {
+ public static Xmpp.ModuleIdentity<Module> IDENTITY = new Xmpp.ModuleIdentity<Module>(NS_URI, "0396_jet_omemo");
+ const uint KEY_SIZE = 16;
+ const uint IV_SIZE = 12;
+
+ public override void attach(XmppStream stream) {
+ if (stream.get_module(Jet.Module.IDENTITY) != null) {
+ stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, NS_URI);
+ stream.get_module(Jet.Module.IDENTITY).register_envelop_encoding(this);
+ stream.get_module(Jet.Module.IDENTITY).register_cipher(new AesGcmCipher(KEY_SIZE, IV_SIZE, AES_128_GCM_URI));
+ }
+ }
+
+ public override void detach(XmppStream stream) {
+ stream.get_module(ServiceDiscovery.Module.IDENTITY).remove_feature(stream, NS_URI);
+ }
+
+ public async bool is_available(XmppStream stream, Jid full_jid) {
+ bool? has_feature = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).has_entity_feature(stream, full_jid, NS_URI);
+ if (has_feature == null || !(!)has_feature) {
+ return false;
+ }
+ return yield stream.get_module(Xep.Jet.Module.IDENTITY).is_available(stream, full_jid);
+ }
+
+ public string get_type_uri() {
+ return Omemo.NS_URI;
+ }
+
+ public Jet.TransportSecret decode_envolop(XmppStream stream, Jid local_full_jid, Jid peer_full_jid, StanzaNode security) throws Jingle.IqError {
+ StanzaNode? encrypted = security.get_subnode("encrypted", Omemo.NS_URI);
+ if (encrypted == null) throw new Jingle.IqError.BAD_REQUEST("Invalid JET-OMEMO envelop: missing encrypted element");
+
+ Xep.Omemo.OmemoDecryptor decryptor = stream.get_module(Xep.Omemo.OmemoDecryptor.IDENTITY);
+
+ Xmpp.Xep.Omemo.ParsedData? data = decryptor.parse_node(encrypted);
+ if (data == null) throw new Jingle.IqError.BAD_REQUEST("Invalid JET-OMEMO envelop: bad encrypted element");
+
+ foreach (Bytes encr_key in data.our_potential_encrypted_keys.keys) {
+ data.is_prekey = data.our_potential_encrypted_keys[encr_key];
+ data.encrypted_key = encr_key.get_data();
+
+ try {
+ uint8[] key = decryptor.decrypt_key(data, peer_full_jid.bare_jid);
+ return new Jet.TransportSecret(key, data.iv);
+ } catch (GLib.Error e) {
+ debug("Decrypting JET key from %s/%d failed: %s", peer_full_jid.bare_jid.to_string(), data.sid, e.message);
+ }
+ }
+ throw new Jingle.IqError.NOT_ACCEPTABLE("Not encrypted for targeted device");
+ }
+
+ public void encode_envelop(XmppStream stream, Jid local_full_jid, Jid peer_full_jid, Jet.SecurityParameters security_params, StanzaNode security) {
+ Store store = stream.get_module(Omemo.StreamModule.IDENTITY).store;
+
+ var encryption_data = new Xep.Omemo.EncryptionData(store.local_registration_id);
+ encryption_data.iv = security_params.secret.initialization_vector;
+ encryption_data.keytag = security_params.secret.transport_key;
+ Xep.Omemo.OmemoEncryptor encryptor = stream.get_module(Xep.Omemo.OmemoEncryptor.IDENTITY);
+ encryptor.encrypt_key_to_recipient(stream, encryption_data, peer_full_jid.bare_jid);
+
+ security.put_node(encryption_data.get_encrypted_node());
+ }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return IDENTITY.id; }
+}
+
+public class AesGcmCipher : Jet.Cipher, Object {
+ private uint key_size;
+ private uint default_iv_size;
+ private string uri;
+ public AesGcmCipher(uint key_size, uint default_iv_size, string uri) {
+ this.key_size = key_size;
+ this.default_iv_size = default_iv_size;
+ this.uri = uri;
+ }
+ public string get_cipher_uri() {
+ return uri;
+ }
+ public Jet.TransportSecret generate_random_secret() {
+ uint8[] iv = new uint8[default_iv_size];
+ Crypto.randomize(iv);
+ uint8[] key = new uint8[key_size];
+ Crypto.randomize(key);
+ return new Jet.TransportSecret(key, iv);
+ }
+ public InputStream wrap_input_stream(InputStream input, Jet.TransportSecret secret) requires (secret.transport_key.length == key_size) {
+ SymmetricCipherDecryption cipher = create_symmetric_cipher_decryption("AES-GCM");
+ cipher.set_key(secret.transport_key);
+ cipher.set_iv(secret.initialization_vector);
+ return new ConverterInputStream(input, new SymmetricCipherDecrypter((owned) cipher, 16));
+ }
+ public OutputStream wrap_output_stream(OutputStream output, Jet.TransportSecret secret) requires (secret.transport_key.length == key_size) {
+ SymmetricCipherEncryption cipher = create_symmetric_cipher_encryption("AES-GCM");
+ cipher.set_key(secret.transport_key);
+ cipher.set_iv(secret.initialization_vector);
+ return new ConverterOutputStream(output, new SymmetricCipherEncrypter((owned) cipher, 16));
+ }
+}
+}