aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libdino/src/service/file_manager.vala101
-rw-r--r--libdino/src/service/jingle_file_transfers.vala107
-rw-r--r--libdino/src/service/module_manager.vala1
-rw-r--r--main/src/ui/chat_input/view.vala13
-rw-r--r--plugins/gpgme-vala/src/gpgme_helper.vala4
-rw-r--r--plugins/http-files/src/file_sender.vala39
-rw-r--r--plugins/omemo/src/file_transfer/file_decryptor.vala24
-rw-r--r--plugins/omemo/src/file_transfer/file_encryptor.vala28
-rw-r--r--plugins/openpgp/CMakeLists.txt2
-rw-r--r--plugins/openpgp/src/file_transfer/file_decryptor.vala8
-rw-r--r--plugins/openpgp/src/file_transfer/file_encryptor.vala6
-rw-r--r--plugins/openpgp/src/plugin.vala1
-rw-r--r--xmpp-vala/CMakeLists.txt3
-rw-r--r--xmpp-vala/src/module/xep/0166_jingle.vala68
-rw-r--r--xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala19
-rw-r--r--xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala7
-rw-r--r--xmpp-vala/src/module/xep/0391_jingle_encrypted_transports.vala142
17 files changed, 436 insertions, 137 deletions
diff --git a/libdino/src/service/file_manager.vala b/libdino/src/service/file_manager.vala
index e0a417ee..841a6b53 100644
--- a/libdino/src/service/file_manager.vala
+++ b/libdino/src/service/file_manager.vala
@@ -71,45 +71,44 @@ public class FileManager : StreamInteractionModule, Object {
file_meta.size = file_transfer.size;
file_meta.mime_type = file_transfer.mime_type;
- bool encrypted = false;
- foreach (FileEncryptor file_encryptor in file_encryptors) {
- if (file_encryptor.can_encrypt_file(conversation, file_transfer)) {
- file_meta = file_encryptor.encrypt_file(conversation, file_transfer);
- encrypted = true;
- break;
+ FileSender file_sender = null;
+ FileEncryptor file_encryptor = null;
+ foreach (FileSender sender in file_senders) {
+ if (sender.can_send(conversation, file_transfer)) {
+ if (file_transfer.encryption == Encryption.NONE || sender.can_encrypt(conversation, file_transfer)) {
+ file_sender = sender;
+ break;
+ } else {
+ foreach (FileEncryptor encryptor in file_encryptors) {
+ if (encryptor.can_encrypt_file(conversation, file_transfer)) {
+ file_encryptor = encryptor;
+ break;
+ }
+ }
+ if (file_encryptor != null) {
+ file_sender = sender;
+ break;
+ }
+ }
}
}
- if (conversation.encryption != Encryption.NONE && !encrypted) {
- throw new FileSendError.ENCRYPTION_FAILED("File was not encrypted");
- }
- FileSendData file_send_data = null;
- foreach (FileSender file_sender in file_senders) {
- if (file_sender.can_send(conversation, file_transfer)) {
- file_send_data = yield file_sender.prepare_send_file(conversation, file_transfer, file_meta);
- break;
- }
+ if (file_sender == null) {
+ throw new FileSendError.UPLOAD_FAILED("No sender/encryptor combination available");
}
- foreach (FileEncryptor file_encryptor in file_encryptors) {
- if (file_encryptor.can_encrypt_file(conversation, file_transfer)) {
- file_send_data = file_encryptor.preprocess_send_file(conversation, file_transfer, file_send_data, file_meta);
- break;
- }
+ if (file_encryptor != null) {
+ file_meta = file_encryptor.encrypt_file(conversation, file_transfer);
}
- bool sent = false;
- foreach (FileSender file_sender in file_senders) {
- if (file_sender.can_send(conversation, file_transfer)) {
- yield file_sender.send_file(conversation, file_transfer, file_send_data);
- sent = true;
- break;
- }
- }
- if (!sent) {
- throw new FileSendError.UPLOAD_FAILED("File was not sent");
+ FileSendData file_send_data = yield file_sender.prepare_send_file(conversation, file_transfer, file_meta);
+
+ if (file_encryptor != null) {
+ file_send_data = file_encryptor.preprocess_send_file(conversation, file_transfer, file_send_data, file_meta);
}
+ yield file_sender.send_file(conversation, file_transfer, file_send_data, file_meta);
+
conversation.last_active = file_transfer.time;
} catch (Error e) {
warning("Send file error: %s", e.message);
@@ -130,7 +129,9 @@ public class FileManager : StreamInteractionModule, Object {
yield download_file_internal(file_provider, file_transfer, conversation);
}
- public bool is_upload_available(Conversation conversation) {
+ public bool is_upload_available(Conversation? conversation) {
+ if (conversation == null) return false;
+
foreach (FileSender file_sender in file_senders) {
if (file_sender.is_upload_available(conversation)) return true;
}
@@ -230,12 +231,18 @@ public class FileManager : StreamInteractionModule, Object {
try {
// Get meta info
FileReceiveData receive_data = file_provider.get_file_receive_data(file_transfer);
- foreach (FileDecryptor file_decryptor in file_decryptors) {
- if (file_decryptor.can_decrypt_file(conversation, file_transfer, receive_data)) {
- receive_data = file_decryptor.prepare_get_meta_info(conversation, file_transfer, receive_data);
+ FileDecryptor? file_decryptor = null;
+ foreach (FileDecryptor decryptor in file_decryptors) {
+ if (decryptor.can_decrypt_file(conversation, file_transfer, receive_data)) {
+ file_decryptor = decryptor;
break;
}
}
+
+ if (file_decryptor != null) {
+ receive_data = file_decryptor.prepare_get_meta_info(conversation, file_transfer, receive_data);
+ }
+
FileMeta file_meta = yield get_file_meta(file_provider, file_transfer, conversation, receive_data);
@@ -244,34 +251,21 @@ public class FileManager : StreamInteractionModule, Object {
// Download and decrypt file
file_transfer.state = FileTransfer.State.IN_PROGRESS;
- foreach (FileDecryptor file_decryptor in file_decryptors) {
- if (file_decryptor.can_decrypt_file(conversation, file_transfer, receive_data)) {
- file_meta = file_decryptor.prepare_download_file(conversation, file_transfer, receive_data, file_meta);
- break;
- }
+ if (file_decryptor != null) {
+ file_meta = file_decryptor.prepare_download_file(conversation, file_transfer, receive_data, file_meta);
}
input_stream = yield file_provider.download(file_transfer, receive_data, file_meta);
-
- foreach (FileDecryptor file_decryptor in file_decryptors) {
- if (file_decryptor.can_decrypt_file(conversation, file_transfer, receive_data)) {
- input_stream = yield file_decryptor.decrypt_file(input_stream, conversation, file_transfer, receive_data);
- break;
- }
+ if (file_decryptor != null) {
+ input_stream = yield file_decryptor.decrypt_file(input_stream, conversation, file_transfer, receive_data);
}
// Save file
- string filename = Random.next_int().to_string("%x") + "_" + file_meta.file_name;
+ string filename = Random.next_int().to_string("%x") + "_" + file_transfer.file_name;
File file = File.new_for_path(Path.build_filename(get_storage_dir(), filename));
- if (file_transfer.encryption == Encryption.PGP || file.get_path().has_suffix(".pgp")) {
- file = File.new_for_path(file.get_path().substring(0, file.get_path().length - 4));
- }
-
OutputStream os = file.create(FileCreateFlags.REPLACE_DESTINATION);
yield os.splice_async(input_stream, OutputStreamSpliceFlags.CLOSE_SOURCE|OutputStreamSpliceFlags.CLOSE_TARGET);
- file_transfer.size = (int)file_meta.size;
- file_transfer.file_name = file_meta.file_name;
file_transfer.path = file.get_basename();
file_transfer.input_stream = yield file.read_async();
@@ -392,7 +386,8 @@ public interface FileSender : Object {
public abstract bool is_upload_available(Conversation conversation);
public abstract bool can_send(Conversation conversation, FileTransfer file_transfer);
public abstract async FileSendData? prepare_send_file(Conversation conversation, FileTransfer file_transfer, FileMeta file_meta) throws FileSendError;
- public abstract async void send_file(Conversation conversation, FileTransfer file_transfer, FileSendData file_send_data) throws FileSendError;
+ public abstract async void send_file(Conversation conversation, FileTransfer file_transfer, FileSendData file_send_data, FileMeta file_meta) throws FileSendError;
+ public abstract bool can_encrypt(Conversation conversation, FileTransfer file_transfer);
public abstract int get_id();
public abstract float get_priority();
diff --git a/libdino/src/service/jingle_file_transfers.vala b/libdino/src/service/jingle_file_transfers.vala
index 0a93979b..182213bb 100644
--- a/libdino/src/service/jingle_file_transfers.vala
+++ b/libdino/src/service/jingle_file_transfers.vala
@@ -6,6 +6,56 @@ using Dino.Entities;
namespace Dino {
+public interface JingleFileEncryptionHelper : Object {
+ public abstract bool can_transfer(Conversation conversation);
+ public abstract bool can_encrypt(Conversation conversation, FileTransfer file_transfer, Jid? full_jid = null);
+ public abstract string? get_precondition_name(Conversation conversation, FileTransfer file_transfer);
+ public abstract Object? get_precondition_options(Conversation conversation, FileTransfer file_transfer);
+ public abstract FileMeta complete_meta(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta, Xmpp.Xep.JingleFileTransfer.FileTransfer jingle_transfer);
+}
+
+public class JingleFileEncryptionHelperTransferOnly : JingleFileEncryptionHelper, Object {
+ public bool can_transfer(Conversation conversation) {
+ return true;
+ }
+ public bool can_encrypt(Conversation conversation, FileTransfer file_transfer, Jid? full_jid) {
+ return false;
+ }
+ public string? get_precondition_name(Conversation conversation, FileTransfer file_transfer) {
+ return null;
+ }
+ public Object? get_precondition_options(Conversation conversation, FileTransfer file_transfer) {
+ return null;
+ }
+ public FileMeta complete_meta(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta, Xmpp.Xep.JingleFileTransfer.FileTransfer jingle_transfer) {
+ return file_meta;
+ }
+}
+
+public class JingleFileHelperRegistry {
+ private static JingleFileHelperRegistry INSTANCE;
+ public static JingleFileHelperRegistry instance { get {
+ if (INSTANCE == null) {
+ INSTANCE = new JingleFileHelperRegistry();
+ INSTANCE.add_encryption_helper(Encryption.NONE, new JingleFileEncryptionHelperTransferOnly());
+ }
+ return INSTANCE;
+ } }
+
+ internal HashMap<Encryption, JingleFileEncryptionHelper> encryption_helpers = new HashMap<Encryption, JingleFileEncryptionHelper>();
+
+ public void add_encryption_helper(Encryption encryption, JingleFileEncryptionHelper helper) {
+ encryption_helpers[encryption] = helper;
+ }
+
+ public JingleFileEncryptionHelper? get_encryption_helper(Encryption encryption) {
+ if (encryption_helpers.has_key(encryption)) {
+ return encryption_helpers[encryption];
+ }
+ return null;
+ }
+}
+
public class JingleFileProvider : FileProvider, Object {
private StreamInteractor stream_interactor;
@@ -29,7 +79,15 @@ public class JingleFileProvider : FileProvider, Object {
}
public async FileMeta get_meta_info(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) throws FileReceiveError {
- return file_meta;
+ Xmpp.Xep.JingleFileTransfer.FileTransfer? jingle_file_transfer = file_transfers[file_transfer.info];
+ if (jingle_file_transfer == null) {
+ throw new FileReceiveError.DOWNLOAD_FAILED("Transfer data not available anymore");
+ }
+ FileMeta meta = file_meta;
+ foreach (JingleFileEncryptionHelper helper in JingleFileHelperRegistry.instance.encryption_helpers.values) {
+ meta = helper.complete_meta(file_transfer, receive_data, meta, jingle_file_transfer);
+ }
+ return meta;
}
public async InputStream download(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) throws FileReceiveError {
@@ -39,6 +97,9 @@ public class JingleFileProvider : FileProvider, Object {
if (jingle_file_transfer == null) {
throw new FileReceiveError.DOWNLOAD_FAILED("Transfer data not available anymore");
}
+ foreach (JingleFileEncryptionHelper helper in JingleFileHelperRegistry.instance.encryption_helpers.values) {
+ helper.complete_meta(file_transfer, receive_data, file_meta, jingle_file_transfer);
+ }
try {
jingle_file_transfer.accept(stream);
} catch (IOError e) {
@@ -83,6 +144,10 @@ public class JingleFileSender : FileSender, Object {
}
public bool is_upload_available(Conversation conversation) {
+ JingleFileEncryptionHelper? helper = JingleFileHelperRegistry.instance.get_encryption_helper(conversation.encryption);
+ if (helper == null) return false;
+ if (!helper.can_transfer(conversation)) return false;
+
XmppStream? stream = stream_interactor.get_stream(conversation.account);
if (stream == null) return false;
@@ -98,32 +163,46 @@ public class JingleFileSender : FileSender, Object {
}
public bool can_send(Conversation conversation, FileTransfer file_transfer) {
- if (conversation.encryption != Encryption.NONE) return false;
-
- XmppStream? stream = stream_interactor.get_stream(file_transfer.account);
- if (stream == null) return false;
+ // No file specific restrictions apply to Jingle file transfers
+ return is_upload_available(conversation);
+ }
- foreach (Jid full_jid in stream.get_flag(Presence.Flag.IDENTITY).get_resources(conversation.counterpart)) {
- if (stream.get_module(Xep.JingleFileTransfer.Module.IDENTITY).is_available(stream, full_jid)) {
- return true;
- }
- }
- return false;
+ public bool can_encrypt(Conversation conversation, FileTransfer file_transfer) {
+ JingleFileEncryptionHelper? helper = JingleFileHelperRegistry.instance.get_encryption_helper(file_transfer.encryption);
+ if (helper == null) return false;
+ return helper.can_encrypt(conversation, file_transfer);
}
public async FileSendData? prepare_send_file(Conversation conversation, FileTransfer file_transfer, FileMeta file_meta) throws FileSendError {
+ if (file_meta is HttpFileMeta) {
+ throw new FileSendError.UPLOAD_FAILED("Cannot upload http file meta over Jingle");
+ }
return new FileSendData();
}
- public async void send_file(Conversation conversation, FileTransfer file_transfer, FileSendData file_send_data) throws FileSendError {
- // TODO(hrxi) What should happen if `stream == null`?
+ public async void send_file(Conversation conversation, FileTransfer file_transfer, FileSendData file_send_data, FileMeta file_meta) throws FileSendError {
XmppStream? stream = stream_interactor.get_stream(file_transfer.account);
+ if (stream == null) throw new FileSendError.UPLOAD_FAILED("No stream available");
+ JingleFileEncryptionHelper? helper = JingleFileHelperRegistry.instance.get_encryption_helper(file_transfer.encryption);
+ bool must_encrypt = helper != null && helper.can_encrypt(conversation, file_transfer);
foreach (Jid full_jid in stream.get_flag(Presence.Flag.IDENTITY).get_resources(conversation.counterpart)) {
// TODO(hrxi): Prioritization of transports (and resources?).
if (!stream.get_module(Xep.JingleFileTransfer.Module.IDENTITY).is_available(stream, full_jid)) {
continue;
}
- stream.get_module(Xep.JingleFileTransfer.Module.IDENTITY).offer_file_stream.begin(stream, full_jid, file_transfer.input_stream, file_transfer.file_name, file_transfer.size);
+ if (must_encrypt && !helper.can_encrypt(conversation, file_transfer, full_jid)) {
+ continue;
+ }
+ string? precondition_name = null;
+ Object? precondition_options = null;
+ if (must_encrypt) {
+ precondition_name = helper.get_precondition_name(conversation, file_transfer);
+ precondition_options = helper.get_precondition_options(conversation, file_transfer);
+ if (precondition_name == null) {
+ throw new FileSendError.ENCRYPTION_FAILED("Should have created a precondition, but did not");
+ }
+ }
+ yield stream.get_module(Xep.JingleFileTransfer.Module.IDENTITY).offer_file_stream(stream, full_jid, file_transfer.input_stream, file_transfer.server_file_name, file_meta.size, precondition_name, precondition_options);
return;
}
}
diff --git a/libdino/src/service/module_manager.vala b/libdino/src/service/module_manager.vala
index 6a07a146..2b1567a0 100644
--- a/libdino/src/service/module_manager.vala
+++ b/libdino/src/service/module_manager.vala
@@ -84,6 +84,7 @@ public class ModuleManager {
module_map[account].add(new Xep.JingleSocks5Bytestreams.Module());
module_map[account].add(new Xep.JingleInBandBytestreams.Module());
module_map[account].add(new Xep.JingleFileTransfer.Module());
+ module_map[account].add(new Xep.Jet.Module());
initialize_account_modules(account, module_map[account]);
}
}
diff --git a/main/src/ui/chat_input/view.vala b/main/src/ui/chat_input/view.vala
index 74fa044e..24297e6a 100644
--- a/main/src/ui/chat_input/view.vala
+++ b/main/src/ui/chat_input/view.vala
@@ -57,6 +57,7 @@ public class View : Box {
scrolled.vadjustment.notify["upper"].connect_after(on_upper_notify);
encryption_widget.get_style_context().add_class("dino-chatinput-button");
+ encryption_widget.encryption_changed.connect(update_file_transfer_availability);
// Emoji button for emoji picker (recents don't work < 3.22.19, category icons don't work <3.23.2)
if (Gtk.get_major_version() >= 3 && Gtk.get_minor_version() >= 24) {
@@ -83,15 +84,17 @@ public class View : Box {
return this;
}
- public void initialize_for_conversation(Conversation conversation) {
-
+ private void update_file_transfer_availability() {
+ bool upload_available = stream_interactor.get_module(FileManager.IDENTITY).is_upload_available(conversation);
+ file_button.visible = upload_available;
+ file_separator.visible = upload_available;
+ }
+ public void initialize_for_conversation(Conversation conversation) {
if (this.conversation != null) entry_cache[this.conversation] = text_input.buffer.text;
this.conversation = conversation;
- bool upload_available = stream_interactor.get_module(FileManager.IDENTITY).is_upload_available(conversation);
- file_button.visible = upload_available;
- file_separator.visible = upload_available;
+ update_file_transfer_availability();
text_input.buffer.text = "";
if (entry_cache.has_key(conversation)) {
diff --git a/plugins/gpgme-vala/src/gpgme_helper.vala b/plugins/gpgme-vala/src/gpgme_helper.vala
index c0121842..4a6d94fa 100644
--- a/plugins/gpgme-vala/src/gpgme_helper.vala
+++ b/plugins/gpgme-vala/src/gpgme_helper.vala
@@ -163,11 +163,11 @@ private static uint8[] get_uint8_from_data(Data data) {
data.seek(0);
uint8[] buf = new uint8[256];
ssize_t? len = null;
- Array<uint8> res = new Array<uint8>(false, true, 0);
+ ByteArray res = new ByteArray();
do {
len = data.read(buf);
if (len > 0) {
- res.append_vals(buf, (int)len);
+ res.append(buf[0:len]);
}
} while (len > 0);
return res.data;
diff --git a/plugins/http-files/src/file_sender.vala b/plugins/http-files/src/file_sender.vala
index 3ab0d736..65b33eb4 100644
--- a/plugins/http-files/src/file_sender.vala
+++ b/plugins/http-files/src/file_sender.vala
@@ -35,11 +35,11 @@ public class HttpFileSender : FileSender, Object {
return send_data;
}
- public async void send_file(Conversation conversation, FileTransfer file_transfer, FileSendData file_send_data) throws FileSendError {
+ public async void send_file(Conversation conversation, FileTransfer file_transfer, FileSendData file_send_data, FileMeta file_meta) throws FileSendError {
HttpFileSendData? send_data = file_send_data as HttpFileSendData;
if (send_data == null) return;
- yield upload(file_transfer, send_data);
+ yield upload(file_transfer, send_data, file_meta);
file_transfer.info = send_data.url_down; // store the message content temporarily so the message gets filtered out
@@ -62,6 +62,10 @@ public class HttpFileSender : FileSender, Object {
return file_transfer.size < max_file_sizes[conversation.account];
}
+ public bool can_encrypt(Conversation conversation, FileTransfer file_transfer) {
+ return false;
+ }
+
public bool is_upload_available(Conversation conversation) {
lock (max_file_sizes) {
return max_file_sizes.has_key(conversation.account);
@@ -74,24 +78,27 @@ public class HttpFileSender : FileSender, Object {
}
}
- private async void upload(FileTransfer file_transfer, HttpFileSendData file_send_data) throws FileSendError {
+ private static void transfer_more_bytes(InputStream stream, Soup.MessageBody body) {
+ uint8[] bytes = new uint8[4096];
+ ssize_t read = stream.read(bytes);
+ if (read == 0) {
+ body.complete();
+ return;
+ }
+ bytes.length = (int)read;
+ body.append_buffer(new Soup.Buffer.take(bytes));
+ }
+
+ private async void upload(FileTransfer file_transfer, HttpFileSendData file_send_data, FileMeta file_meta) throws FileSendError {
Xmpp.XmppStream? stream = stream_interactor.get_stream(file_transfer.account);
if (stream == null) return;
- uint8[] buf = new uint8[256];
- Array<uint8> data = new Array<uint8>(false, true, 0);
- size_t len = -1;
- do {
- try {
- len = file_transfer.input_stream.read(buf);
- } catch (IOError e) {
- throw new FileSendError.UPLOAD_FAILED("HTTP upload: IOError reading stream: %s".printf(e.message));
- }
- data.append_vals(buf, (uint) len);
- } while(len > 0);
-
Soup.Message message = new Soup.Message("PUT", file_send_data.url_up);
- message.set_request(file_transfer.mime_type, Soup.MemoryUse.COPY, data.data);
+ message.request_headers.set_content_type(file_meta.mime_type, null);
+ message.request_headers.set_content_length(file_meta.size);
+ message.request_body.set_accumulate(false);
+ message.wrote_headers.connect(() => transfer_more_bytes(file_transfer.input_stream, message.request_body));
+ message.wrote_chunk.connect(() => transfer_more_bytes(file_transfer.input_stream, message.request_body));
Soup.Session session = new Soup.Session();
try {
yield session.send_async(message);
diff --git a/plugins/omemo/src/file_transfer/file_decryptor.vala b/plugins/omemo/src/file_transfer/file_decryptor.vala
index bc6f8592..d60ecdc8 100644
--- a/plugins/omemo/src/file_transfer/file_decryptor.vala
+++ b/plugins/omemo/src/file_transfer/file_decryptor.vala
@@ -1,5 +1,6 @@
using Dino.Entities;
+using Crypto;
using Signal;
namespace Dino.Plugins.Omemo {
@@ -56,20 +57,17 @@ public class OmemoFileDecryptor : FileDecryptor, Object {
key = iv_and_key[16:48];
}
- // Read data
- uint8[] buf = new uint8[256];
- Array<uint8> data = new Array<uint8>(false, true, 0);
- size_t len = -1;
- do {
- len = yield encrypted_stream.read_async(buf);
- data.append_vals(buf, (uint) len);
- } while(len > 0);
-
- // Decrypt
- uint8[] cleartext = Signal.aes_decrypt(Cipher.AES_GCM_NOPADDING, key, iv, data.data);
file_transfer.encryption = Encryption.OMEMO;
- return new MemoryInputStream.from_data(cleartext);
- } catch (Error e) {
+ debug("Decrypting file %s from %s", file_transfer.file_name, file_transfer.server_file_name);
+
+ SymmetricCipher cipher = new SymmetricCipher("AES-GCM");
+ cipher.set_key(key);
+ cipher.set_iv(iv);
+ return new ConverterInputStream(encrypted_stream, new SymmetricCipherDecrypter((owned) cipher));
+
+ } catch (Crypto.Error e) {
+ throw new FileReceiveError.DECRYPTION_FAILED("OMEMO file decryption error: %s".printf(e.message));
+ } catch (GLib.Error e) {
throw new FileReceiveError.DECRYPTION_FAILED("OMEMO file decryption error: %s".printf(e.message));
}
}
diff --git a/plugins/omemo/src/file_transfer/file_encryptor.vala b/plugins/omemo/src/file_transfer/file_encryptor.vala
index a5445153..5b4e4d96 100644
--- a/plugins/omemo/src/file_transfer/file_encryptor.vala
+++ b/plugins/omemo/src/file_transfer/file_encryptor.vala
@@ -1,6 +1,7 @@
using Gee;
using Gtk;
+using Crypto;
using Dino.Entities;
using Xmpp;
using Signal;
@@ -22,30 +23,29 @@ public class OmemoFileEncryptor : Dino.FileEncryptor, Object {
var omemo_http_file_meta = new OmemoHttpFileMeta();
try {
- uint8[] buf = new uint8[256];
- Array<uint8> data = new Array<uint8>(false, true, 0);
- size_t len = -1;
- do {
- len = file_transfer.input_stream.read(buf);
- data.append_vals(buf, (uint) len);
- } while(len > 0);
-
//Create a key and use it to encrypt the file
uint8[] iv = new uint8[16];
Plugin.get_context().randomize(iv);
uint8[] key = new uint8[32];
Plugin.get_context().randomize(key);
- uint8[] ciphertext = aes_encrypt(Cipher.AES_GCM_NOPADDING, key, iv, data.data);
+
+ SymmetricCipher cipher = new SymmetricCipher("AES-GCM");
+ cipher.set_key(key);
+ cipher.set_iv(iv);
omemo_http_file_meta.iv = iv;
omemo_http_file_meta.key = key;
- omemo_http_file_meta.size = ciphertext.length;
- omemo_http_file_meta.mime_type = "pgp";
- file_transfer.input_stream = new MemoryInputStream.from_data(ciphertext, GLib.free);
- } catch (Error error) {
- throw new FileSendError.ENCRYPTION_FAILED("HTTP upload: Error encrypting stream: %s".printf(error.message));
+ omemo_http_file_meta.size = file_transfer.size;
+ omemo_http_file_meta.mime_type = "omemo";
+ file_transfer.input_stream = new ConverterInputStream(file_transfer.input_stream, new SymmetricCipherEncrypter((owned) cipher));
+ } catch (Crypto.Error error) {
+ throw new FileSendError.ENCRYPTION_FAILED("OMEMO file encryption error: %s".printf(error.message));
+ } catch (GLib.Error error) {
+ throw new FileSendError.ENCRYPTION_FAILED("OMEMO file encryption error: %s".printf(error.message));
}
+ debug("Encrypting file %s as %s", file_transfer.file_name, file_transfer.server_file_name);
+
return omemo_http_file_meta;
}
diff --git a/plugins/openpgp/CMakeLists.txt b/plugins/openpgp/CMakeLists.txt
index eb50dc71..3f7c1974 100644
--- a/plugins/openpgp/CMakeLists.txt
+++ b/plugins/openpgp/CMakeLists.txt
@@ -53,7 +53,7 @@ GRESOURCES
${OPENPGP_GRESOURCES_XML}
)
-add_definitions(${VALA_CFLAGS} -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\")
+add_definitions(${VALA_CFLAGS} -DG_LOG_DOMAIN="OpenPGP" -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\")
add_library(openpgp SHARED ${OPENPGP_VALA_C} ${OPENPGP_GRESOURCES_TARGET})
add_dependencies(openpgp ${GETTEXT_PACKAGE}-translations)
target_link_libraries(openpgp libdino gpgme-vala ${OPENPGP_PACKAGES})
diff --git a/plugins/openpgp/src/file_transfer/file_decryptor.vala b/plugins/openpgp/src/file_transfer/file_decryptor.vala
index 97eb9f43..455f853d 100644
--- a/plugins/openpgp/src/file_transfer/file_decryptor.vala
+++ b/plugins/openpgp/src/file_transfer/file_decryptor.vala
@@ -19,18 +19,20 @@ public class PgpFileDecryptor : FileDecryptor, Object {
public async InputStream decrypt_file(InputStream encrypted_stream, Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data) throws FileReceiveError {
try {
uint8[] buf = new uint8[256];
- Array<uint8> data = new Array<uint8>(false, true, 0);
+ ByteArray data = new ByteArray();
size_t len = -1;
do {
- len = encrypted_stream.read(buf);
- data.append_vals(buf, (uint) len);
+ len = yield encrypted_stream.read_async(buf);
+ data.append(buf[0:len]);
} while(len > 0);
GPGHelper.DecryptedData clear_data = GPGHelper.decrypt_data(data.data);
file_transfer.encryption = Encryption.PGP;
if (clear_data.filename != null && clear_data.filename != "") {
+ debug("Decrypting file %s from %s", clear_data.filename, file_transfer.file_name);
file_transfer.file_name = clear_data.filename;
} else if (file_transfer.file_name.has_suffix(".pgp")) {
+ debug("Decrypting file %s from %s", file_transfer.file_name.substring(0, file_transfer.file_name.length - 4), file_transfer.file_name);
file_transfer.file_name = file_transfer.file_name.substring(0, file_transfer.file_name.length - 4);
}
return new MemoryInputStream.from_data(clear_data.data, GLib.free);
diff --git a/plugins/openpgp/src/file_transfer/file_encryptor.vala b/plugins/openpgp/src/file_transfer/file_encryptor.vala
index 7d51be60..66e93bd9 100644
--- a/plugins/openpgp/src/file_transfer/file_encryptor.vala
+++ b/plugins/openpgp/src/file_transfer/file_encryptor.vala
@@ -15,17 +15,21 @@ public class PgpFileEncryptor : Dino.FileEncryptor, Object {
}
public FileMeta encrypt_file(Conversation conversation, FileTransfer file_transfer) throws FileSendError {
+ FileMeta file_meta = new FileMeta();
+
try {
GPG.Key[] keys = stream_interactor.get_module(Manager.IDENTITY).get_key_fprs(conversation);
uint8[] enc_content = GPGHelper.encrypt_file(file_transfer.get_file().get_path(), keys, GPG.EncryptFlags.ALWAYS_TRUST, file_transfer.file_name);
file_transfer.input_stream = new MemoryInputStream.from_data(enc_content, GLib.free);
file_transfer.encryption = Encryption.PGP;
file_transfer.server_file_name = Xmpp.random_uuid() + ".pgp";
+ file_meta.size = enc_content.length;
} catch (Error e) {
throw new FileSendError.ENCRYPTION_FAILED("PGP file encryption error: %s".printf(e.message));
}
+ debug("Encrypting file %s as %s", file_transfer.file_name, file_transfer.server_file_name);
- return new FileMeta();
+ return file_meta;
}
public FileSendData? preprocess_send_file(Conversation conversation, FileTransfer file_transfer, FileSendData file_send_data, FileMeta file_meta) {
diff --git a/plugins/openpgp/src/plugin.vala b/plugins/openpgp/src/plugin.vala
index b4581f31..324b8652 100644
--- a/plugins/openpgp/src/plugin.vala
+++ b/plugins/openpgp/src/plugin.vala
@@ -31,6 +31,7 @@ public class Plugin : Plugins.RootInterface, Object {
Manager.start(app.stream_interactor, db);
app.stream_interactor.get_module(FileManager.IDENTITY).add_file_encryptor(new PgpFileEncryptor(app.stream_interactor));
app.stream_interactor.get_module(FileManager.IDENTITY).add_file_decryptor(new PgpFileDecryptor());
+ JingleFileHelperRegistry.instance.add_encryption_helper(Encryption.PGP, new JingleFileEncryptionHelperTransferOnly());
internationalize(GETTEXT_PACKAGE, app.search_path_generator.get_locale_path(GETTEXT_PACKAGE, LOCALE_INSTALL_DIR));
}
diff --git a/xmpp-vala/CMakeLists.txt b/xmpp-vala/CMakeLists.txt
index e0f01723..b945f9d0 100644
--- a/xmpp-vala/CMakeLists.txt
+++ b/xmpp-vala/CMakeLists.txt
@@ -78,6 +78,7 @@ SOURCES
"src/module/xep/0363_http_file_upload.vala"
"src/module/xep/0368_srv_records_tls.vala"
"src/module/xep/0380_explicit_encryption.vala"
+ "src/module/xep/0391_jingle_encrypted_transports.vala"
"src/module/xep/pixbuf_storage.vala"
"src/util.vala"
@@ -95,7 +96,7 @@ DEPENDS
${CMAKE_BINARY_DIR}/exports/xmpp-vala.deps
)
-add_definitions(${VALA_CFLAGS})
+add_definitions(${VALA_CFLAGS} -DG_LOG_DOMAIN="xmpp-vala")
add_library(xmpp-vala SHARED ${ENGINE_VALA_C})
add_dependencies(xmpp-vala xmpp-vala-vapi)
target_link_libraries(xmpp-vala ${ENGINE_PACKAGES})
diff --git a/xmpp-vala/src/module/xep/0166_jingle.vala b/xmpp-vala/src/module/xep/0166_jingle.vala
index 86396f30..85663929 100644
--- a/xmpp-vala/src/module/xep/0166_jingle.vala
+++ b/xmpp-vala/src/module/xep/0166_jingle.vala
@@ -43,6 +43,7 @@ public errordomain Error {
BAD_REQUEST,
INVALID_PARAMETERS,
UNSUPPORTED_TRANSPORT,
+ UNSUPPORTED_SECURITY,
NO_SHARED_PROTOCOLS,
TRANSPORT_ERROR,
}
@@ -69,6 +70,7 @@ class ContentNode {
public string name;
public StanzaNode? description;
public StanzaNode? transport;
+ public StanzaNode? security;
}
ContentNode get_single_content_node(StanzaNode jingle) throws IqError {
@@ -94,6 +96,7 @@ ContentNode get_single_content_node(StanzaNode jingle) throws IqError {
string? name = content.get_attribute("name");
StanzaNode? description = get_single_node_anyns(content, "description");
StanzaNode? transport = get_single_node_anyns(content, "transport");
+ StanzaNode? security = get_single_node_anyns(content, "security");
if (name == null || creator == null) {
throw new IqError.BAD_REQUEST("missing name or creator");
}
@@ -102,7 +105,8 @@ ContentNode get_single_content_node(StanzaNode jingle) throws IqError {
creator=creator,
name=name,
description=description,
- transport=transport
+ transport=transport,
+ security=security
};
}
@@ -112,6 +116,7 @@ public class Module : XmppStreamModule, Iq.Handler {
private HashMap<string, ContentType> content_types = new HashMap<string, ContentType>();
private HashMap<string, Transport> transports = new HashMap<string, Transport>();
+ private HashMap<string, SecurityPrecondition> security_preconditions = new HashMap<string, SecurityPrecondition>();
private XmppStream? current_stream = null;
@@ -163,6 +168,16 @@ public class Module : XmppStreamModule, Iq.Handler {
}
return result;
}
+ public void register_security_precondition(SecurityPrecondition precondition) {
+ security_preconditions[precondition.security_ns_uri()] = precondition;
+ }
+ public SecurityPrecondition? get_security_precondition(string? ns_uri) {
+ if (ns_uri == null) return null;
+ if (!security_preconditions.has_key(ns_uri)) {
+ return null;
+ }
+ return security_preconditions[ns_uri];
+ }
private bool is_jingle_available(XmppStream stream, Jid full_jid) {
bool? has_jingle = stream.get_flag(ServiceDiscovery.Flag.IDENTITY).has_entity_feature(full_jid, NS_URI);
@@ -173,7 +188,7 @@ public class Module : XmppStreamModule, Iq.Handler {
return is_jingle_available(stream, full_jid) && select_transport(stream, type, full_jid, Set.empty()) != null;
}
- public Session create_session(XmppStream stream, TransportType type, Jid receiver_full_jid, Senders senders, string content_name, StanzaNode description) throws Error {
+ public Session create_session(XmppStream stream, TransportType type, Jid receiver_full_jid, Senders senders, string content_name, StanzaNode description, string? precondition_name = null, Object? precondation_options = null) throws Error {
if (!is_jingle_available(stream, receiver_full_jid)) {
throw new Error.NO_SHARED_PROTOCOLS("No Jingle support");
}
@@ -181,18 +196,26 @@ public class Module : XmppStreamModule, Iq.Handler {
if (transport == null) {
throw new Error.NO_SHARED_PROTOCOLS("No suitable transports");
}
+ SecurityPrecondition? precondition = get_security_precondition(precondition_name);
+ if (precondition_name != null && precondition == null) {
+ throw new Error.UNSUPPORTED_SECURITY("No suitable security precondiiton found");
+ }
Jid? my_jid = stream.get_flag(Bind.Flag.IDENTITY).my_jid;
if (my_jid == null) {
throw new Error.GENERAL("Couldn't determine own JID");
}
TransportParameters transport_params = transport.create_transport_parameters(stream, my_jid, receiver_full_jid);
- Session session = new Session.initiate_sent(random_uuid(), type, transport_params, my_jid, receiver_full_jid, content_name, send_terminate_and_remove_session);
+ SecurityParameters? security_params = precondition != null ? precondition.create_security_parameters(stream, my_jid, receiver_full_jid, precondation_options) : null;
+ Session session = new Session.initiate_sent(random_uuid(), type, transport_params, security_params, my_jid, receiver_full_jid, content_name, send_terminate_and_remove_session);
StanzaNode content = new StanzaNode.build("content", NS_URI)
.put_attribute("creator", "initiator")
.put_attribute("name", content_name)
.put_attribute("senders", senders.to_string())
.put_node(description)
.put_node(transport_params.to_transport_stanza_node());
+ if (security_params != null) {
+ content.put_node(security_params.to_security_stanza_node(stream, my_jid, receiver_full_jid));
+ }
StanzaNode jingle = new StanzaNode.build("jingle", NS_URI)
.add_self_xmlns()
.put_attribute("action", "session-initiate")
@@ -233,8 +256,17 @@ public class Module : XmppStreamModule, Iq.Handler {
}
ContentParameters content_params = content_type.parse_content_parameters(content.description);
+ SecurityPrecondition? precondition = content.security != null ? get_security_precondition(content.security.ns_uri) : null;
+ SecurityParameters? security_params = null;
+ if (precondition != null) {
+ debug("Using precondition %s", precondition.security_ns_uri());
+ security_params = precondition.parse_security_parameters(stream, my_jid, iq.from, content.security);
+ } else if (content.security != null) {
+ throw new IqError.NOT_IMPLEMENTED("unknown security precondition");
+ }
+
TransportType type = content_type.content_type_transport_type();
- Session session = new Session.initiate_received(sid, type, transport_params, my_jid, iq.from, content.name, send_terminate_and_remove_session);
+ Session session = new Session.initiate_received(sid, type, transport_params, security_params, my_jid, iq.from, content.name, send_terminate_and_remove_session);
stream.get_flag(Flag.IDENTITY).add_session(session);
stream.get_module(Iq.Module.IDENTITY).send_iq(stream, new Iq.Stanza.result(iq));
@@ -328,7 +360,7 @@ public interface Transport : Object {
public abstract bool is_transport_available(XmppStream stream, Jid full_jid);
public abstract TransportType transport_type();
public abstract int transport_priority();
- public abstract TransportParameters create_transport_parameters(XmppStream stream, Jid local_full_jid, Jid peer_full_jid);
+ public abstract TransportParameters create_transport_parameters(XmppStream stream, Jid local_full_jid, Jid peer_full_jid) throws Error;
public abstract TransportParameters parse_transport_parameters(XmppStream stream, Jid local_full_jid, Jid peer_full_jid, StanzaNode transport) throws IqError;
}
@@ -375,6 +407,17 @@ public interface ContentParameters : Object {
public abstract void on_session_initiate(XmppStream stream, Session session);
}
+public interface SecurityPrecondition : Object {
+ public abstract string security_ns_uri();
+ public abstract SecurityParameters? create_security_parameters(XmppStream stream, Jid local_full_jid, Jid peer_full_jid, Object options) throws Jingle.Error;
+ public abstract SecurityParameters? parse_security_parameters(XmppStream stream, Jid local_full_jid, Jid peer_full_jid, StanzaNode security) throws IqError;
+}
+
+public interface SecurityParameters : Object {
+ public abstract string security_ns_uri();
+ public abstract StanzaNode to_security_stanza_node(XmppStream stream, Jid local_full_jid, Jid peer_full_jid);
+ public abstract IOStream wrap_stream(IOStream stream);
+}
public class Session {
// INITIATE_SENT -> CONNECTING -> [REPLACING_TRANSPORT -> CONNECTING ->]... ACTIVE -> ENDED
@@ -398,6 +441,7 @@ public class Session {
public Jid peer_full_jid { get; private set; }
public Role content_creator { get; private set; }
public string content_name { get; private set; }
+ public SecurityParameters? security { get; private set; }
private Connection connection;
public IOStream conn { get { return connection; } }
@@ -410,7 +454,7 @@ public class Session {
SessionTerminate session_terminate_handler;
- public Session.initiate_sent(string sid, TransportType type, TransportParameters transport, Jid local_full_jid, Jid peer_full_jid, string content_name, owned SessionTerminate session_terminate_handler) {
+ public Session.initiate_sent(string sid, TransportType type, TransportParameters transport, SecurityParameters? security, Jid local_full_jid, Jid peer_full_jid, string content_name, owned SessionTerminate session_terminate_handler) {
this.state = State.INITIATE_SENT;
this.role = Role.INITIATOR;
this.sid = sid;
@@ -422,12 +466,13 @@ public class Session {
this.tried_transport_methods = new HashSet<string>();
this.tried_transport_methods.add(transport.transport_ns_uri());
this.transport = transport;
+ this.security = security;
this.connection = new Connection(this);
this.session_terminate_handler = (owned)session_terminate_handler;
this.terminate_on_connection_close = true;
}
- public Session.initiate_received(string sid, TransportType type, TransportParameters? transport, Jid local_full_jid, Jid peer_full_jid, string content_name, owned SessionTerminate session_terminate_handler) {
+ public Session.initiate_received(string sid, TransportType type, TransportParameters? transport, SecurityParameters? security, Jid local_full_jid, Jid peer_full_jid, string content_name, owned SessionTerminate session_terminate_handler) {
this.state = State.INITIATE_RECEIVED;
this.role = Role.RESPONDER;
this.sid = sid;
@@ -437,6 +482,7 @@ public class Session {
this.content_creator = Role.INITIATOR;
this.content_name = content_name;
this.transport = transport;
+ this.security = security;
this.tried_transport_methods = new HashSet<string>();
if (transport != null) {
this.tried_transport_methods.add(transport.transport_ns_uri());
@@ -557,7 +603,12 @@ public class Session {
state = State.ACTIVE;
transport = null;
tried_transport_methods.clear();
- connection.set_inner(conn);
+ if (security != null) {
+ connection.set_inner(security.wrap_stream(conn));
+ } else {
+ connection.set_inner(conn);
+ }
+
} else {
if (role == Role.INITIATOR) {
select_new_transport(stream);
@@ -913,6 +964,7 @@ public class Connection : IOStream {
return true;
}
public async bool close_read_async(int io_priority = GLib.Priority.DEFAULT, Cancellable? cancellable = null) throws IOError {
+ debug("Closing Jingle input stream");
yield wait_and_check_for_errors(io_priority, cancellable);
if (read_closed) {
return true;
diff --git a/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala b/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala
index 951ea7b7..7780d973 100644
--- a/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala
+++ b/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala
@@ -48,18 +48,24 @@ public class Module : Jingle.ContentType, XmppStreamModule {
return stream.get_module(Jingle.Module.IDENTITY).is_available(stream, Jingle.TransportType.STREAMING, full_jid);
}
- public async void offer_file_stream(XmppStream stream, Jid receiver_full_jid, InputStream input_stream, string basename, int64 size) throws IOError {
+ public async void offer_file_stream(XmppStream stream, Jid receiver_full_jid, InputStream input_stream, string basename, int64 size, string? precondition_name = null, Object? precondition_options = null) throws IOError {
+ StanzaNode file_node;
StanzaNode description = new StanzaNode.build("description", NS_URI)
.add_self_xmlns()
- .put_node(new StanzaNode.build("file", NS_URI)
- .put_node(new StanzaNode.build("name", NS_URI).put_node(new StanzaNode.text(basename)))
- .put_node(new StanzaNode.build("size", NS_URI).put_node(new StanzaNode.text(size.to_string()))));
+ .put_node(file_node = new StanzaNode.build("file", NS_URI)
+ .put_node(new StanzaNode.build("name", NS_URI).put_node(new StanzaNode.text(basename))));
// TODO(hrxi): Add the mandatory hash field
+ if (size > 0) {
+ file_node.put_node(new StanzaNode.build("size", NS_URI).put_node(new StanzaNode.text(size.to_string())));
+ } else {
+ warning("Sending file %s without size, likely going to cause problems down the road...", basename);
+ }
+
Jingle.Session session;
try {
session = stream.get_module(Jingle.Module.IDENTITY)
- .create_session(stream, Jingle.TransportType.STREAMING, receiver_full_jid, Jingle.Senders.INITIATOR, "a-file-offer", description); // TODO(hrxi): Why "a-file-offer"?
+ .create_session(stream, Jingle.TransportType.STREAMING, receiver_full_jid, Jingle.Senders.INITIATOR, "a-file-offer", description, precondition_name, precondition_options); // TODO(hrxi): Why "a-file-offer"?
} catch (Jingle.Error e) {
throw new IOError.FAILED(@"couldn't create Jingle session: $(e.message)");
}
@@ -172,13 +178,14 @@ public class FileTransfer : Object {
public Jid peer { get { return session.peer_full_jid; } }
public string? file_name { get { return parameters.name; } }
public int64 size { get { return parameters.size; } }
+ public Jingle.SecurityParameters? security { get { return session.security; } }
public InputStream? stream { get; private set; }
public FileTransfer(Jingle.Session session, Parameters parameters) {
this.session = session;
this.parameters = parameters;
- this.stream = new FileTransferInputStream(session.conn.input_stream, parameters.size);
+ this.stream = new FileTransferInputStream(session.conn.input_stream, size);
}
public void accept(XmppStream stream) throws IOError {
diff --git a/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala b/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala
index a5eb1250..991ea141 100644
--- a/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala
+++ b/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala
@@ -287,6 +287,7 @@ class Parameters : Jingle.TransportParameters, Object {
}
remote_sent_selected_candidate = true;
remote_selected_candidate = candidate;
+ debug("Remote selected candidate %s", candidate.cid);
try_completing_negotiation();
}
private void handle_activated(string cid) throws Jingle.IqError {
@@ -353,6 +354,7 @@ class Parameters : Jingle.TransportParameters, Object {
}
}
public async void wait_for_remote_activation(Candidate candidate, SocketConnection conn) {
+ debug("Waiting for remote activation of %s", candidate.cid);
waiting_for_activation_cid = candidate.cid;
waiting_for_activation_callback = wait_for_remote_activation.callback;
yield;
@@ -368,6 +370,7 @@ class Parameters : Jingle.TransportParameters, Object {
}
}
public async void connect_to_local_candidate(Candidate candidate) {
+ debug("Connecting to candidate %s", candidate.cid);
try {
SocketConnection conn = yield connect_to_socks5(candidate, local_dstaddr);
@@ -420,6 +423,7 @@ class Parameters : Jingle.TransportParameters, Object {
SocketClient socket_client = new SocketClient() { timeout=3 };
string address = @"[$(candidate.host)]:$(candidate.port)";
+ debug("Connecting to SOCKS5 server at %s", address);
size_t written;
size_t read;
@@ -500,6 +504,7 @@ class Parameters : Jingle.TransportParameters, Object {
local_determined_selected_candidate = true;
local_selected_candidate = candidate;
local_selected_candidate_conn = conn;
+ debug("Selected candidate %s", candidate.cid);
session.send_transport_info(stream, new StanzaNode.build("transport", NS_URI)
.add_self_xmlns()
.put_attribute("sid", sid)
@@ -522,6 +527,8 @@ class Parameters : Jingle.TransportParameters, Object {
.put_attribute("sid", sid)
.put_node(new StanzaNode.build("candidate-error", NS_URI))
);
+ // Try remote candidates
+ try_completing_negotiation();
}
public void create_transport_connection(XmppStream stream, Jingle.Session session) {
this.session = session;
diff --git a/xmpp-vala/src/module/xep/0391_jingle_encrypted_transports.vala b/xmpp-vala/src/module/xep/0391_jingle_encrypted_transports.vala
new file mode 100644
index 00000000..e2b1326b
--- /dev/null
+++ b/xmpp-vala/src/module/xep/0391_jingle_encrypted_transports.vala
@@ -0,0 +1,142 @@
+using Gee;
+using Xmpp.Xep.Jingle;
+
+namespace Xmpp.Xep.Jet {
+public const string NS_URI = "urn:xmpp:jingle:jet:0";
+
+public class Module : XmppStreamModule, SecurityPrecondition {
+ public static Xmpp.ModuleIdentity<Module> IDENTITY = new Xmpp.ModuleIdentity<Module>(NS_URI, "0391_jet");
+ private HashMap<string, EnvelopEncoding> envelop_encodings = new HashMap<string, EnvelopEncoding>();
+ private HashMap<string, Cipher> ciphers = new HashMap<string, Cipher>();
+
+ public override void attach(XmppStream stream) {
+ stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, NS_URI);
+ stream.get_module(Jingle.Module.IDENTITY).register_security_precondition(this);
+ }
+
+ public override void detach(XmppStream stream) {
+ }
+
+ public bool is_available(XmppStream stream, Jid full_jid) {
+ bool? has_feature = stream.get_flag(ServiceDiscovery.Flag.IDENTITY).has_entity_feature(full_jid, NS_URI);
+ return has_feature != null && (!)has_feature;
+ }
+
+ public void register_envelop_encoding(EnvelopEncoding encoding) {
+ envelop_encodings[encoding.get_type_uri()] = encoding;
+ }
+
+ public void register_cipher(Cipher cipher) {
+ ciphers[cipher.get_cipher_uri()] = cipher;
+ }
+
+ public string security_ns_uri() {
+ return NS_URI;
+ }
+
+ public Jingle.SecurityParameters? create_security_parameters(XmppStream stream, Jid local_full_jid, Jid peer_full_jid, Object options) throws Jingle.Error requires (options is Options) {
+ Options jet_options = (Options) options;
+ string cipher = jet_options.cipher_uri;
+ string type = jet_options.type_uri;
+ if (!envelop_encodings.has_key(type) || !ciphers.has_key(cipher)) {
+ throw new IqError.NOT_IMPLEMENTED("JET cipher or type unknown");
+ }
+ EnvelopEncoding encoding = envelop_encodings[type];
+ return new SecurityParameters(ciphers[cipher], encoding, ciphers[cipher].generate_random_secret(), jet_options);
+ }
+
+ public Jingle.SecurityParameters? parse_security_parameters(XmppStream stream, Jid local_full_jid, Jid peer_full_jid, StanzaNode security) throws IqError {
+ string? cipher = security.get_attribute("cipher");
+ string? type = security.get_attribute("type");
+ if (cipher == null || type == null) {
+ throw new IqError.BAD_REQUEST("No cipher or type specified for JET");
+ }
+ if (!envelop_encodings.has_key(type) || !ciphers.has_key(cipher)) {
+ throw new IqError.NOT_IMPLEMENTED("JET cipher or type unknown");
+ }
+ EnvelopEncoding encoding = envelop_encodings[type];
+ TransportSecret secret = encoding.decode_envolop(stream, local_full_jid, peer_full_jid, security);
+ return new SecurityParameters(ciphers[cipher], encoding, secret);
+ }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return IDENTITY.id; }
+}
+
+public class Options : Object {
+ public string type_uri { get; private set; }
+ public string cipher_uri { get; private set; }
+
+ public Options(string type_uri, string cipher_uri) {
+ this.type_uri = type_uri;
+ this.cipher_uri = cipher_uri;
+ }
+}
+
+public class SecurityParameters : Jingle.SecurityParameters, Object {
+ public Cipher cipher { get; private set; }
+ public EnvelopEncoding encoding { get; private set; }
+ public TransportSecret secret { get; private set; }
+ public Options? options { get; private set; }
+
+ public SecurityParameters(Cipher cipher, EnvelopEncoding encoding, TransportSecret secret, Options? options = null) {
+ this.cipher = cipher;
+ this.encoding = encoding;
+ this.secret = secret;
+ this.options = options;
+ }
+
+ public string security_ns_uri() {
+ return NS_URI;
+ }
+ public IOStream wrap_stream(IOStream stream) {
+ debug("Wrapping stream into encrypted stream for %s/%s", encoding.get_type_uri(), cipher.get_cipher_uri());
+ return new EncryptedStream(cipher, secret, stream);
+ }
+ public StanzaNode to_security_stanza_node(XmppStream stream, Jid local_full_jid, Jid peer_full_jid) {
+ StanzaNode security = new StanzaNode.build("security", NS_URI)
+ .add_self_xmlns()
+ .put_attribute("cipher", cipher.get_cipher_uri())
+ .put_attribute("type", encoding.get_type_uri());
+ encoding.encode_envelop(stream, local_full_jid, peer_full_jid, this, security);
+ return security;
+ }
+}
+
+public interface Cipher : Object {
+ public abstract string get_cipher_uri();
+ public abstract TransportSecret generate_random_secret();
+ public abstract InputStream wrap_input_stream(InputStream input, TransportSecret secret);
+ public abstract OutputStream wrap_output_stream(OutputStream output, TransportSecret secret);
+}
+
+private class EncryptedStream : IOStream {
+ private IOStream stream;
+ private InputStream input;
+ private OutputStream output;
+ public override InputStream input_stream { get { return input; } }
+ public override OutputStream output_stream { get { return output; } }
+
+ public EncryptedStream(Cipher cipher, TransportSecret secret, IOStream stream) {
+ this.stream = stream;
+ input = cipher.wrap_input_stream(stream.input_stream, secret);
+ output = cipher.wrap_output_stream(stream.output_stream, secret);
+ }
+}
+
+public class TransportSecret {
+ public uint8[] transport_key { get; private set; }
+ public uint8[] initialization_vector { get; private set; }
+ public TransportSecret(uint8[] transport_key, uint8[] initialization_vector) {
+ this.transport_key = transport_key;
+ this.initialization_vector = initialization_vector;
+ }
+}
+
+public interface EnvelopEncoding : Object {
+ public abstract string get_type_uri();
+ public abstract TransportSecret decode_envolop(XmppStream stream, Jid local_full_jid, Jid peer_full_jid, StanzaNode security) throws IqError;
+ public abstract void encode_envelop(XmppStream stream, Jid local_full_jid, Jid peer_full_jid, SecurityParameters security_params, StanzaNode security);
+}
+
+} \ No newline at end of file