1using GPG; 2 3using Xmpp; 4 5namespace Dino.Plugins.OpenPgp { 6 private const string NS_URI = "jabber:x"; 7 private const string NS_URI_ENCRYPTED = NS_URI + ":encrypted"; 8 private const string NS_URI_SIGNED = NS_URI + ":signed"; 9 10 public class Module : XmppStreamModule { 11 public static Xmpp.ModuleIdentity<Module> IDENTITY = new Xmpp.ModuleIdentity<Module>(NS_URI, "0027_current_pgp_usage"); 12 13 public signal void received_jid_key_id(XmppStream stream, Jid jid, string key_id); 14 15 private string? signed_status = null; 16 private Key? own_key = null; 17 private ReceivedPipelineDecryptListener received_pipeline_decrypt_listener = new ReceivedPipelineDecryptListener(); 18 19 public Module(string? own_key_id = null) { 20 set_private_key_id(own_key_id); 21 } 22 23 public void set_private_key_id(string? own_key_id) { 24 if (own_key_id != null) { 25 try { 26 own_key = GPGHelper.get_private_key(own_key_id); 27 if (own_key == null) warning("Can't get PGP private key"); 28 } catch (Error e) { } 29 if (own_key != null) { 30 signed_status = gpg_sign("", own_key); 31 } 32 } 33 } 34 35 public bool encrypt(MessageStanza message, GPG.Key[] keys) { 36 string? enc_body = gpg_encrypt(message.body, keys); 37 if (enc_body != null) { 38 message.stanza.put_node(new StanzaNode.build("x", NS_URI_ENCRYPTED).add_self_xmlns().put_node(new StanzaNode.text(enc_body))); 39 message.body = "[This message is OpenPGP encrypted (see XEP-0027)]"; 40 Xep.ExplicitEncryption.add_encryption_tag_to_message(message, NS_URI_ENCRYPTED); 41 return true; 42 } 43 return false; 44 } 45 46 public override void attach(XmppStream stream) { 47 stream.get_module(Presence.Module.IDENTITY).received_presence.connect(on_received_presence); 48 stream.get_module(Presence.Module.IDENTITY).pre_send_presence_stanza.connect(on_pre_send_presence_stanza); 49 stream.get_module(MessageModule.IDENTITY).received_pipeline.connect(received_pipeline_decrypt_listener); 50 stream.add_flag(new Flag()); 51 } 52 53 public override void detach(XmppStream stream) { 54 stream.get_module(Presence.Module.IDENTITY).received_presence.disconnect(on_received_presence); 55 stream.get_module(Presence.Module.IDENTITY).pre_send_presence_stanza.disconnect(on_pre_send_presence_stanza); 56 stream.get_module(MessageModule.IDENTITY).received_pipeline.disconnect(received_pipeline_decrypt_listener); 57 } 58 59 public static void require(XmppStream stream) { 60 if (stream.get_module(IDENTITY) == null) stream.add_module(new Module()); 61 } 62 63 public override string get_ns() { return NS_URI; } 64 public override string get_id() { return IDENTITY.id; } 65 66 private void on_received_presence(XmppStream stream, Presence.Stanza presence) { 67 StanzaNode x_node = presence.stanza.get_subnode("x", NS_URI_SIGNED); 68 if (x_node == null) return; 69 string? sig = x_node.get_string_content(); 70 if (sig == null) return; 71 new Thread<void*> (null, () => { 72 string signed_data = presence.status == null ? "" : presence.status; 73 string? key_id = get_sign_key(sig, signed_data); 74 if (key_id != null) { 75 stream.get_flag(Flag.IDENTITY).set_key_id(presence.from, key_id); 76 Idle.add(() => { 77 received_jid_key_id(stream, presence.from, key_id); 78 return false; 79 }); 80 } 81 return null; 82 }); 83 } 84 85 private void on_pre_send_presence_stanza(XmppStream stream, Presence.Stanza presence) { 86 if (presence.type_ == Presence.Stanza.TYPE_AVAILABLE && signed_status != null) { 87 presence.stanza.put_node(new StanzaNode.build("x", NS_URI_SIGNED).add_self_xmlns().put_node(new StanzaNode.text(signed_status))); 88 } 89 } 90 91 private static string? gpg_encrypt(string plain, GPG.Key[] keys) { 92 string encr; 93 try { 94 encr = GPGHelper.encrypt_armor(plain, keys, GPG.EncryptFlags.ALWAYS_TRUST); 95 } catch (Error e) { 96 return null; 97 } 98 int encryption_start = encr.index_of("\n\n") + 2; 99 return encr.substring(encryption_start, encr.length - "\n-----END PGP MESSAGE-----".length - encryption_start); 100 } 101 102 private static string? get_sign_key(string sig, string signed_text) { 103 string armor = "-----BEGIN PGP MESSAGE-----\n\n" + sig + "\n-----END PGP MESSAGE-----"; 104 string? sign_key = null; 105 try { 106 sign_key = GPGHelper.get_sign_key(armor, signed_text); 107 } catch (Error e) { } 108 return sign_key; 109 } 110 111 private static string? gpg_sign(string str, Key key) { 112 string signed; 113 try { 114 signed = GPGHelper.sign(str, GPG.SigMode.CLEAR, key); 115 } catch (Error e) { 116 return null; 117 } 118 int signature_start = signed.index_of("-----BEGIN PGP SIGNATURE-----"); 119 signature_start = signed.index_of("\n\n", signature_start) + 2; 120 return signed.substring(signature_start, signed.length - "\n-----END PGP SIGNATURE-----".length - signature_start); 121 } 122 } 123 124 public class MessageFlag : Xmpp.MessageFlag { 125 public const string id = "pgp"; 126 127 public bool decrypted = false; 128 129 public static MessageFlag? get_flag(MessageStanza message) { 130 return (MessageFlag) message.get_flag(NS_URI, id); 131 } 132 133 public override string get_ns() { return NS_URI; } 134 public override string get_id() { return id; } 135 } 136 137public class ReceivedPipelineDecryptListener : StanzaListener<MessageStanza> { 138 139 private const string[] after_actions_const = {"MODIFY_BODY"}; 140 141 public override string action_group { get { return "ENCRYPT_BODY"; } } 142 public override string[] after_actions { get { return after_actions_const; } } 143 144 public override async bool run(XmppStream stream, MessageStanza message) { 145 string? encrypted = get_cyphertext(message); 146 if (encrypted != null) { 147 MessageFlag flag = new MessageFlag(); 148 message.add_flag(flag); 149 string? decrypted = yield gpg_decrypt(encrypted); 150 if (decrypted != null) { 151 flag.decrypted = true; 152 message.body = decrypted; 153 } 154 } 155 return false; 156 } 157 158 private static async string? gpg_decrypt(string enc) { 159 SourceFunc callback = gpg_decrypt.callback; 160 string? res = null; 161 new Thread<void*> (null, () => { 162 string armor = "-----BEGIN PGP MESSAGE-----\n\n" + enc + "\n-----END PGP MESSAGE-----"; 163 try { 164 res = GPGHelper.decrypt(armor); 165 } catch (Error e) { 166 res = null; 167 } 168 Idle.add((owned) callback); 169 return null; 170 }); 171 yield; 172 return res; 173 } 174 175 private string? get_cyphertext(MessageStanza message) { 176 StanzaNode? x_node = message.stanza.get_subnode("x", NS_URI_ENCRYPTED); 177 return x_node == null ? null : x_node.get_string_content(); 178 } 179} 180 181} 182