Add JET support
This commit is contained in:
parent
87d64524c8
commit
e899668213
|
@ -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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_sender == null) {
|
||||
throw new FileSendError.UPLOAD_FAILED("No sender/encryptor combination available");
|
||||
}
|
||||
|
||||
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 (file_encryptor != null) {
|
||||
file_meta = file_encryptor.encrypt_file(conversation, file_transfer);
|
||||
}
|
||||
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();
|
||||
|
|
|
@ -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;
|
||||
// No file specific restrictions apply to Jingle file transfers
|
||||
return is_upload_available(conversation);
|
||||
}
|
||||
|
||||
XmppStream? stream = stream_interactor.get_stream(file_transfer.account);
|
||||
if (stream == null) return false;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
||||
|
||||
if (this.conversation != null) entry_cache[this.conversation] = text_input.buffer.text;
|
||||
this.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;
|
||||
|
||||
update_file_transfer_availability();
|
||||
|
||||
text_input.buffer.text = "";
|
||||
if (entry_cache.has_key(conversation)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
142
xmpp-vala/src/module/xep/0391_jingle_encrypted_transports.vala
Normal file
142
xmpp-vala/src/module/xep/0391_jingle_encrypted_transports.vala
Normal file
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue