PGP encrypted file transfers
This commit is contained in:
parent
8b43df8ec3
commit
9ea16b6d85
|
@ -25,6 +25,11 @@ public class FileTransfer : Object {
|
|||
public OutputStream output_stream { get; set; }
|
||||
|
||||
public string file_name { get; set; }
|
||||
private string? server_file_name_ = null;
|
||||
public string server_file_name {
|
||||
get { return server_file_name_ ?? file_name; }
|
||||
set { server_file_name_ = value; }
|
||||
}
|
||||
public string path { get; set; }
|
||||
public string mime_type { get; set; }
|
||||
public int size { get; set; }
|
||||
|
@ -90,6 +95,10 @@ public class FileTransfer : Object {
|
|||
notify.connect(on_update);
|
||||
}
|
||||
|
||||
public string get_uri() {
|
||||
return Path.build_filename(Dino.get_storage_dir(), "files", path);
|
||||
}
|
||||
|
||||
private void on_update(Object o, ParamSpec sp) {
|
||||
Qlite.UpdateBuilder update_builder = db.file_transfer.update().with(db.file_transfer.id, "=", id);
|
||||
switch (sp.name) {
|
||||
|
|
|
@ -16,6 +16,8 @@ public class FileManager : StreamInteractionModule, Object {
|
|||
private StreamInteractor stream_interactor;
|
||||
private Database db;
|
||||
private Gee.List<FileSender> file_senders = new ArrayList<FileSender>();
|
||||
private Gee.List<IncommingFileProcessor> incomming_processors = new ArrayList<IncommingFileProcessor>();
|
||||
private Gee.List<OutgoingFileProcessor> outgoing_processors = new ArrayList<OutgoingFileProcessor>();
|
||||
|
||||
public static void start(StreamInteractor stream_interactor, Database db) {
|
||||
FileManager m = new FileManager(stream_interactor, db);
|
||||
|
@ -46,12 +48,19 @@ public class FileManager : StreamInteractionModule, Object {
|
|||
file_transfer.encryption = Encryption.NONE;
|
||||
file_transfer.file_name = file_info.get_display_name();
|
||||
file_transfer.input_stream = file.read();
|
||||
|
||||
file_transfer.mime_type = file_info.get_content_type();
|
||||
file_transfer.size = (int)file_info.get_size();
|
||||
save_file(file_transfer);
|
||||
|
||||
file_transfer.persist(db);
|
||||
|
||||
foreach (OutgoingFileProcessor processor in outgoing_processors) {
|
||||
if (processor.can_process(conversation, file_transfer)) {
|
||||
processor.process(conversation, file_transfer);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (FileSender file_sender in file_senders) {
|
||||
if (file_sender.can_send(conversation, file_transfer)) {
|
||||
file_sender.send_file(conversation, file_transfer);
|
||||
|
@ -88,11 +97,7 @@ public class FileManager : StreamInteractionModule, Object {
|
|||
}
|
||||
|
||||
public void add_provider(FileProvider file_provider) {
|
||||
file_provider.file_incoming.connect((file_transfer) => {
|
||||
save_file(file_transfer);
|
||||
file_transfer.persist(db);
|
||||
received_file(file_transfer);
|
||||
});
|
||||
file_provider.file_incoming.connect(handle_incomming_file);
|
||||
}
|
||||
|
||||
public void add_sender(FileSender file_sender) {
|
||||
|
@ -102,6 +107,30 @@ public class FileManager : StreamInteractionModule, Object {
|
|||
});
|
||||
}
|
||||
|
||||
public void add_incomming_processor(IncommingFileProcessor processor) {
|
||||
incomming_processors.add(processor);
|
||||
}
|
||||
|
||||
public void add_outgoing_processor(OutgoingFileProcessor processor) {
|
||||
outgoing_processors.add(processor);
|
||||
}
|
||||
|
||||
private void handle_incomming_file(FileTransfer file_transfer) {
|
||||
foreach (IncommingFileProcessor processor in incomming_processors) {
|
||||
if (processor.can_process(file_transfer)) {
|
||||
processor.process(file_transfer);
|
||||
}
|
||||
}
|
||||
save_file(file_transfer);
|
||||
|
||||
File file = File.new_for_path(file_transfer.get_uri());
|
||||
FileInfo file_info = file.query_info("*", FileQueryInfoFlags.NONE);
|
||||
file_transfer.mime_type = file_info.get_content_type();
|
||||
|
||||
file_transfer.persist(db);
|
||||
received_file(file_transfer);
|
||||
}
|
||||
|
||||
private void save_file(FileTransfer file_transfer) {
|
||||
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));
|
||||
|
@ -132,12 +161,12 @@ public interface FileSender : Object {
|
|||
|
||||
public interface IncommingFileProcessor : Object {
|
||||
public abstract bool can_process(FileTransfer file_transfer);
|
||||
public abstract FileTransfer process(FileTransfer file_transfer);
|
||||
public abstract void process(FileTransfer file_transfer);
|
||||
}
|
||||
|
||||
public interface OutgoingFileProcessor : Object {
|
||||
public abstract bool can_process(FileTransfer file_transfer);
|
||||
public abstract FileTransfer process(FileTransfer file_transfer);
|
||||
public abstract bool can_process(Conversation conversation, FileTransfer file_transfer);
|
||||
public abstract void process(Conversation conversation, FileTransfer file_transfer);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ class FilePopulator : Plugins.ConversationItemPopulator, Object {
|
|||
public void populate_between_widgets(Conversation conversation, DateTime from, DateTime to) { }
|
||||
|
||||
private void insert_file(FileTransfer transfer) {
|
||||
if (transfer.mime_type.has_prefix("image")) {
|
||||
if (transfer.mime_type != null && transfer.mime_type.has_prefix("image")) {
|
||||
item_collection.insert_item(new ImageItem(stream_interactor, transfer));
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ public class ImageItem : Plugins.MetaConversationItem {
|
|||
|
||||
public override Object get_widget(Plugins.WidgetType widget_type) {
|
||||
Image image = new Image() { halign=Align.START, visible = true };
|
||||
Gdk.Pixbuf pixbuf = new Gdk.Pixbuf.from_stream(file_transfer.input_stream);
|
||||
Gdk.Pixbuf pixbuf = new Gdk.Pixbuf.from_file(file_transfer.get_uri());
|
||||
|
||||
int max_scaled_height = MAX_HEIGHT * image.scale_factor;
|
||||
if (pixbuf.height > max_scaled_height) {
|
||||
|
|
|
@ -19,6 +19,20 @@ public static string encrypt_armor(string plain, Key[] keys, EncryptFlags flags)
|
|||
}
|
||||
}
|
||||
|
||||
public static uint8[] encrypt_file(string uri, Key[] keys, EncryptFlags flags) throws GLib.Error {
|
||||
global_mutex.lock();
|
||||
try {
|
||||
initialize();
|
||||
Data plain_data = Data.create_from_file(uri);
|
||||
Context context = Context.create();
|
||||
context.set_armor(true);
|
||||
Data enc_data = context.op_encrypt(keys, flags, plain_data);
|
||||
return get_uint8_from_data(enc_data);
|
||||
} finally {
|
||||
global_mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public static string decrypt(string encr) throws GLib.Error {
|
||||
global_mutex.lock();
|
||||
try {
|
||||
|
@ -32,6 +46,19 @@ public static string decrypt(string encr) throws GLib.Error {
|
|||
}
|
||||
}
|
||||
|
||||
public static uint8[] decrypt_data(uint8[] data) throws GLib.Error {
|
||||
global_mutex.lock();
|
||||
try {
|
||||
initialize();
|
||||
Data enc_data = Data.create_from_memory(data, false);
|
||||
Context context = Context.create();
|
||||
Data dec_data = context.op_decrypt(enc_data);
|
||||
return get_uint8_from_data(dec_data);
|
||||
} finally {
|
||||
global_mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public static string sign(string plain, SigMode mode, Key? key = null) throws GLib.Error {
|
||||
global_mutex.lock();
|
||||
try {
|
||||
|
@ -125,6 +152,20 @@ private static string get_string_from_data(Data data) {
|
|||
return res;
|
||||
}
|
||||
|
||||
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);
|
||||
do {
|
||||
len = data.read(buf);
|
||||
if (len > 0) {
|
||||
res.append_vals(buf, (int)len);
|
||||
}
|
||||
} while (len > 0);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
private static void initialize() {
|
||||
if (!initialized) {
|
||||
check_version();
|
||||
|
@ -132,4 +173,4 @@ private static void initialize() {
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -464,7 +464,13 @@ namespace GPG {
|
|||
}
|
||||
|
||||
[CCode (cname = "gpgme_data_new_from_file")]
|
||||
public static GPGError.Error create_from_file(out Data d, string filename, int copy = 1);
|
||||
public static GPGError.Error new_from_file(out Data d, string filename, int copy = 1);
|
||||
|
||||
public static Data create_from_file(string filename, int copy = 1) {
|
||||
Data data;
|
||||
throw_if_error(new_from_file(out data, filename, copy));
|
||||
return data;
|
||||
}
|
||||
|
||||
[CCode (cname = "gpgme_data_release_and_get_mem")]
|
||||
public string release_and_get_mem(out size_t len);
|
||||
|
|
|
@ -47,7 +47,7 @@ public class FileProvider : Dino.FileProvider, Object {
|
|||
if (name == "Content-Type") content_type = val;
|
||||
if (name == "Content-Length") content_length = val;
|
||||
});
|
||||
if (content_type != null && content_type.has_prefix("image") && content_length != null && int.parse(content_length) < 5000000) {
|
||||
if (/*content_type != null && content_type.has_prefix("image") &&*/ content_length != null && int.parse(content_length) < 5000000) {
|
||||
Soup.Request request = session.request (message.body);
|
||||
FileTransfer file_transfer = new FileTransfer();
|
||||
file_transfer.account = conversation.account;
|
||||
|
|
|
@ -23,9 +23,7 @@ public class Manager : StreamInteractionModule, FileSender, Object {
|
|||
public void send_file(Conversation conversation, FileTransfer file_transfer) {
|
||||
Xmpp.Core.XmppStream? stream = stream_interactor.get_stream(file_transfer.account);
|
||||
if (stream != null) {
|
||||
file_transfer.provider = 0;
|
||||
uploading(file_transfer);
|
||||
stream_interactor.module_manager.get_module(file_transfer.account, UploadStreamModule.IDENTITY).upload(stream, Path.build_filename(FileManager.get_storage_dir(), file_transfer.path),
|
||||
stream_interactor.module_manager.get_module(file_transfer.account, UploadStreamModule.IDENTITY).upload(stream, file_transfer.input_stream, file_transfer.server_file_name, file_transfer.size, file_transfer.mime_type,
|
||||
(stream, url_down) => {
|
||||
uploaded(file_transfer, url_down);
|
||||
stream_interactor.get_module(MessageProcessor.IDENTITY).send_message(url_down, conversation);
|
||||
|
|
|
@ -14,16 +14,19 @@ public class UploadStreamModule : XmppStreamModule {
|
|||
|
||||
public delegate void OnUploadOk(XmppStream stream, string url_down);
|
||||
public delegate void OnError(XmppStream stream, string error);
|
||||
public void upload(XmppStream stream, string file_uri, owned OnUploadOk listener, owned OnError error_listener) {
|
||||
File file = File.new_for_path(file_uri);
|
||||
FileInfo file_info = file.query_info("*", FileQueryInfoFlags.NONE);
|
||||
request_slot(stream, file.get_basename(), (int)file_info.get_size(), file_info.get_content_type(),
|
||||
public void upload(XmppStream stream, InputStream input_stream, string file_name, int file_size, string file_content_type, owned OnUploadOk listener, owned OnError error_listener) {
|
||||
request_slot(stream, file_name, file_size, file_content_type,
|
||||
(stream, url_down, url_up) => {
|
||||
uint8[] data;
|
||||
FileUtils.get_data(file_uri, out data);
|
||||
uint8[] buf = new uint8[256];
|
||||
Array<uint8> data = new Array<uint8>(false, true, 0);
|
||||
size_t len = -1;
|
||||
do {
|
||||
len = input_stream.read(buf);
|
||||
data.append_vals(buf, (uint) len);
|
||||
} while(len > 0);
|
||||
|
||||
Soup.Message message = new Soup.Message("PUT", url_up);
|
||||
message.set_request(file_info.get_content_type(), Soup.MemoryUse.COPY, data);
|
||||
message.set_request(file_content_type, Soup.MemoryUse.COPY, data.data);
|
||||
Soup.Session session = new Soup.Session();
|
||||
session.send_async.begin(message, null, (obj, res) => {
|
||||
try {
|
||||
|
|
|
@ -33,7 +33,9 @@ SOURCES
|
|||
src/contact_details_provider.vala
|
||||
src/database.vala
|
||||
src/encryption_list_entry.vala
|
||||
src/in_file_processor.vala
|
||||
src/manager.vala
|
||||
src/out_file_processor.vala
|
||||
src/plugin.vala
|
||||
src/register_plugin.vala
|
||||
src/stream_flag.vala
|
||||
|
|
28
plugins/openpgp/src/in_file_processor.vala
Normal file
28
plugins/openpgp/src/in_file_processor.vala
Normal file
|
@ -0,0 +1,28 @@
|
|||
using Dino.Entities;
|
||||
|
||||
namespace Dino.Plugins.OpenPgp {
|
||||
|
||||
public class InFileProcessor : IncommingFileProcessor, Object {
|
||||
public bool can_process(FileTransfer file_transfer) {
|
||||
return file_transfer.file_name.has_suffix("pgp") || file_transfer.mime_type == "application/pgp-encrypted";
|
||||
}
|
||||
|
||||
public void process(FileTransfer file_transfer) {
|
||||
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);
|
||||
|
||||
uint8[] clear_data = GPGHelper.decrypt_data(data.data);
|
||||
file_transfer.input_stream = new MemoryInputStream.from_data(clear_data, GLib.free);
|
||||
file_transfer.encryption = Encryption.PGP;
|
||||
if (file_transfer.file_name.has_suffix(".pgp")) {
|
||||
file_transfer.file_name = file_transfer.file_name.substring(0, file_transfer.file_name.length - 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -30,6 +30,39 @@ public class Manager : StreamInteractionModule, Object {
|
|||
stream_interactor.get_module(MessageProcessor.IDENTITY).pre_message_send.connect(check_encypt);
|
||||
}
|
||||
|
||||
public GPG.Key[] get_key_fprs(Conversation conversation) {
|
||||
Gee.List<string> keys = new Gee.ArrayList<string>();
|
||||
keys.add(db.get_account_key(conversation.account));
|
||||
if (conversation.type_ == Conversation.Type.GROUPCHAT) {
|
||||
Gee.List<Jid> muc_jids = new Gee.ArrayList<Jid>();
|
||||
Gee.List<Jid>? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_occupants(conversation.counterpart, conversation.account);
|
||||
if (occupants != null) muc_jids.add_all(occupants);
|
||||
Gee.List<Jid>? offline_members = stream_interactor.get_module(MucManager.IDENTITY).get_offline_members(conversation.counterpart, conversation.account);
|
||||
if (occupants != null) muc_jids.add_all(offline_members);
|
||||
|
||||
foreach (Jid jid in muc_jids) {
|
||||
string? key_id = stream_interactor.get_module(Manager.IDENTITY).get_key_id(conversation.account, jid);
|
||||
if (key_id != null && GPGHelper.get_keylist(key_id).size > 0 && !keys.contains(key_id)) {
|
||||
keys.add(key_id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
string? key_id = get_key_id(conversation.account, conversation.counterpart);
|
||||
if (key_id != null) {
|
||||
keys.add(key_id);
|
||||
}
|
||||
}
|
||||
GPG.Key[] gpgkeys = new GPG.Key[keys.size];
|
||||
for (int i = 0; i < keys.size; i++) {
|
||||
try {
|
||||
GPG.Key key = GPGHelper.get_public_key(keys[i]);
|
||||
if (key != null) gpgkeys[i] = key;
|
||||
} catch (Error e) {}
|
||||
}
|
||||
|
||||
return gpgkeys;
|
||||
}
|
||||
|
||||
private void on_pre_message_received(Entities.Message message, Xmpp.Message.Stanza message_stanza, Conversation conversation) {
|
||||
if (MessageFlag.get_flag(message_stanza) != null && MessageFlag.get_flag(message_stanza).decrypted) {
|
||||
message.encryption = Encryption.PGP;
|
||||
|
@ -38,45 +71,13 @@ public class Manager : StreamInteractionModule, Object {
|
|||
|
||||
private void check_encypt(Entities.Message message, Xmpp.Message.Stanza message_stanza, Conversation conversation) {
|
||||
if (message.encryption == Encryption.PGP) {
|
||||
bool encrypted = false;
|
||||
if (conversation.type_ == Conversation.Type.CHAT) {
|
||||
encrypted = encrypt_for_chat(message, message_stanza, conversation);
|
||||
} else if (conversation.type_ == Conversation.Type.GROUPCHAT) {
|
||||
encrypted = encrypt_for_groupchat(message, message_stanza, conversation);
|
||||
}
|
||||
if (!encrypted) message.marked = Entities.Message.Marked.WONTSEND;
|
||||
}
|
||||
}
|
||||
|
||||
private bool encrypt_for_chat(Entities.Message message, Xmpp.Message.Stanza message_stanza, Conversation conversation) {
|
||||
Core.XmppStream? stream = stream_interactor.get_stream(conversation.account);
|
||||
if (stream == null) return false;
|
||||
|
||||
string? key_id = get_key_id(conversation.account, message.counterpart);
|
||||
if (key_id != null) {
|
||||
return stream.get_module(Module.IDENTITY).encrypt(message_stanza, new Gee.ArrayList<string>.wrap(new string[]{key_id}));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool encrypt_for_groupchat(Entities.Message message, Xmpp.Message.Stanza message_stanza, Conversation conversation) {
|
||||
Core.XmppStream? stream = stream_interactor.get_stream(conversation.account);
|
||||
if (stream == null) return false;
|
||||
|
||||
Gee.List<Jid> muc_jids = new Gee.ArrayList<Jid>();
|
||||
Gee.List<Jid>? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_occupants(conversation.counterpart, conversation.account);
|
||||
if (occupants != null) muc_jids.add_all(occupants);
|
||||
Gee.List<Jid>? offline_members = stream_interactor.get_module(MucManager.IDENTITY).get_offline_members(conversation.counterpart, conversation.account);
|
||||
if (occupants != null) muc_jids.add_all(offline_members);
|
||||
|
||||
Gee.List<string> keys = new Gee.ArrayList<string>();
|
||||
foreach (Jid jid in muc_jids) {
|
||||
string? key_id = stream_interactor.get_module(Manager.IDENTITY).get_key_id(conversation.account, jid);
|
||||
if (key_id != null && GPGHelper.get_keylist(key_id).size > 0 && !keys.contains(key_id)) {
|
||||
keys.add(key_id);
|
||||
GPG.Key[] keys = get_key_fprs(conversation);
|
||||
Core.XmppStream? stream = stream_interactor.get_stream(conversation.account);
|
||||
if (stream != null) {
|
||||
bool encrypted = stream.get_module(Module.IDENTITY).encrypt(message_stanza, keys);
|
||||
if (!encrypted) message.marked = Entities.Message.Marked.WONTSEND;
|
||||
}
|
||||
}
|
||||
return stream.get_module(Module.IDENTITY).encrypt(message_stanza, keys);
|
||||
}
|
||||
|
||||
public string? get_key_id(Account account, Jid jid) {
|
||||
|
|
27
plugins/openpgp/src/out_file_processor.vala
Normal file
27
plugins/openpgp/src/out_file_processor.vala
Normal file
|
@ -0,0 +1,27 @@
|
|||
using Dino.Entities;
|
||||
|
||||
namespace Dino.Plugins.OpenPgp {
|
||||
|
||||
public class OutFileProcessor : OutgoingFileProcessor, Object {
|
||||
|
||||
StreamInteractor stream_interactor;
|
||||
|
||||
public OutFileProcessor(StreamInteractor stream_interactor) {
|
||||
this.stream_interactor = stream_interactor;
|
||||
}
|
||||
|
||||
public bool can_process(Conversation conversation, FileTransfer file_transfer) {
|
||||
return conversation.encryption == Encryption.PGP;
|
||||
}
|
||||
|
||||
public void process(Conversation conversation, FileTransfer file_transfer) {
|
||||
string uri = file_transfer.get_uri();
|
||||
GPG.Key[] keys = stream_interactor.get_module(Manager.IDENTITY).get_key_fprs(conversation);
|
||||
uint8[] enc_content = GPGHelper.encrypt_file(uri, keys, GPG.EncryptFlags.ALWAYS_TRUST);
|
||||
file_transfer.input_stream = new MemoryInputStream.from_data(enc_content, GLib.free);
|
||||
file_transfer.encryption = Encryption.PGP;
|
||||
file_transfer.server_file_name = file_transfer.server_file_name + ".pgp";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -29,6 +29,8 @@ public class Plugin : Plugins.RootInterface, Object {
|
|||
app.stream_interactor.module_manager.initialize_account_modules.connect(on_initialize_account_modules);
|
||||
|
||||
Manager.start(app.stream_interactor, db);
|
||||
app.stream_interactor.get_module(FileManager.IDENTITY).add_outgoing_processor(new OutFileProcessor(app.stream_interactor));
|
||||
app.stream_interactor.get_module(FileManager.IDENTITY).add_incomming_processor(new InFileProcessor());
|
||||
|
||||
internationalize(GETTEXT_PACKAGE, app.search_path_generator.get_locale_path(GETTEXT_PACKAGE, LOCALE_INSTALL_DIR));
|
||||
}
|
||||
|
|
|
@ -33,11 +33,8 @@ namespace Dino.Plugins.OpenPgp {
|
|||
}
|
||||
}
|
||||
|
||||
public bool encrypt(Message.Stanza message, Gee.List<string> fprs) {
|
||||
string[] encrypt_to = new string[fprs.size + 1];
|
||||
for (int i = 0; i < fprs.size; i++) encrypt_to[i] = fprs[i];
|
||||
encrypt_to[encrypt_to.length - 1] = own_key.fpr;
|
||||
string? enc_body = gpg_encrypt(message.body, encrypt_to);
|
||||
public bool encrypt(Message.Stanza message, GPG.Key[] keys) {
|
||||
string? enc_body = gpg_encrypt(message.body, keys);
|
||||
if (enc_body != null) {
|
||||
message.stanza.put_node(new StanzaNode.build("x", NS_URI_ENCRYPTED).add_self_xmlns().put_node(new StanzaNode.text(enc_body)));
|
||||
message.body = "[This message is OpenPGP encrypted (see XEP-0027)]";
|
||||
|
@ -105,13 +102,9 @@ namespace Dino.Plugins.OpenPgp {
|
|||
}
|
||||
}
|
||||
|
||||
private static string? gpg_encrypt(string plain, string[] key_ids) {
|
||||
GPG.Key[] keys = new GPG.Key[key_ids.length];
|
||||
private static string? gpg_encrypt(string plain, GPG.Key[] keys) {
|
||||
string encr;
|
||||
try {
|
||||
for (int i = 0; i < key_ids.length; i++) {
|
||||
keys[i] = GPGHelper.get_public_key(key_ids[i]);
|
||||
}
|
||||
encr = GPGHelper.encrypt_armor(plain, keys, GPG.EncryptFlags.ALWAYS_TRUST);
|
||||
} catch (Error e) {
|
||||
return null;
|
||||
|
|
Loading…
Reference in a new issue