1# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com> 2# 3# This file is part of nbxmpp. 4# 5# This program is free software; you can redistribute it and/or 6# modify it under the terms of the GNU General Public License 7# as published by the Free Software Foundation; either version 3 8# of the License, or (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program; If not, see <http://www.gnu.org/licenses/>. 17 18import time 19import random 20import string 21 22from nbxmpp.namespaces import Namespace 23from nbxmpp.protocol import NodeProcessed 24from nbxmpp.protocol import Node 25from nbxmpp.protocol import StanzaMalformed 26from nbxmpp.protocol import JID 27from nbxmpp.util import b64decode 28from nbxmpp.util import b64encode 29from nbxmpp.structs import StanzaHandler 30from nbxmpp.structs import PGPKeyMetadata 31from nbxmpp.structs import PGPPublicKey 32from nbxmpp.errors import MalformedStanzaError 33from nbxmpp.task import iq_request_task 34from nbxmpp.modules.date_and_time import parse_datetime 35from nbxmpp.modules.base import BaseModule 36from nbxmpp.modules.util import finalize 37from nbxmpp.modules.util import raise_if_error 38 39 40class OpenPGP(BaseModule): 41 42 _depends = { 43 'publish': 'PubSub', 44 'request_items': 'PubSub', 45 } 46 47 def __init__(self, client): 48 BaseModule.__init__(self, client) 49 50 self._client = client 51 self.handlers = [ 52 StanzaHandler(name='message', 53 callback=self._process_pubsub_openpgp, 54 ns=Namespace.PUBSUB_EVENT, 55 priority=16), 56 StanzaHandler(name='message', 57 callback=self._process_openpgp_message, 58 ns=Namespace.OPENPGP, 59 priority=7), 60 ] 61 62 def _process_openpgp_message(self, _client, stanza, properties): 63 openpgp = stanza.getTag('openpgp', namespace=Namespace.OPENPGP) 64 if openpgp is None: 65 self._log.warning('No openpgp node found') 66 self._log.warning(stanza) 67 return 68 69 data = openpgp.getData() 70 if not data: 71 self._log.warning('No data in openpgp node found') 72 self._log.warning(stanza) 73 return 74 75 self._log.info('Encrypted message received') 76 try: 77 properties.openpgp = b64decode(data, return_type=bytes) 78 except Exception: 79 self._log.warning('b64decode failed') 80 self._log.warning(stanza) 81 return 82 83 def _process_pubsub_openpgp(self, _client, stanza, properties): 84 """ 85 <item> 86 <public-keys-list xmlns='urn:xmpp:openpgp:0'> 87 <pubkey-metadata 88 v4-fingerprint='1357B01865B2503C18453D208CAC2A9678548E35' 89 date='2018-03-01T15:26:12Z' 90 /> 91 <pubkey-metadata 92 v4-fingerprint='67819B343B2AB70DED9320872C6464AF2A8E4C02' 93 date='1953-05-16T12:00:00Z' 94 /> 95 </public-keys-list> 96 </item> 97 """ 98 99 if not properties.is_pubsub_event: 100 return 101 102 if properties.pubsub_event.node != Namespace.OPENPGP_PK: 103 return 104 105 item = properties.pubsub_event.item 106 if item is None: 107 # Retract, Deleted or Purged 108 return 109 110 try: 111 data = _parse_keylist(properties.jid, item) 112 except ValueError as error: 113 self._log.warning(error) 114 self._log.warning(stanza) 115 raise NodeProcessed 116 117 if data is None: 118 self._log.info('Received PGP keylist: %s - no keys set', 119 properties.jid) 120 return 121 122 pubsub_event = properties.pubsub_event._replace(data=data) 123 self._log.info('Received PGP keylist: %s - %s', properties.jid, data) 124 125 properties.pubsub_event = pubsub_event 126 127 @iq_request_task 128 def set_keylist(self, keylist, public=True): 129 task = yield 130 131 access_model = 'open' if public else 'presence' 132 133 options = { 134 'pubsub#persist_items': 'true', 135 'pubsub#access_model': access_model, 136 } 137 138 self._log.info('Set keylist: %s', keylist) 139 140 result = yield self.publish(Namespace.OPENPGP_PK, 141 _make_keylist(keylist), 142 id_='current', 143 options=options, 144 force_node_options=True) 145 146 yield finalize(task, result) 147 148 @iq_request_task 149 def set_public_key(self, key, fingerprint, date, public=True): 150 task = yield 151 152 self._log.info('Set public key') 153 154 access_model = 'open' if public else 'presence' 155 156 options = { 157 'pubsub#persist_items': 'true', 158 'pubsub#access_model': access_model, 159 } 160 161 result = yield self.publish(f'{Namespace.OPENPGP_PK}:{fingerprint}', 162 _make_public_key(key, date), 163 id_='current', 164 options=options, 165 force_node_options=True) 166 167 yield finalize(task, result) 168 169 @iq_request_task 170 def request_public_key(self, jid, fingerprint): 171 task = yield 172 173 self._log.info('Request public key from: %s %s', jid, fingerprint) 174 175 items = yield self.request_items( 176 f'{Namespace.OPENPGP_PK}:{fingerprint}', 177 max_items=1, 178 jid=jid) 179 180 raise_if_error(items) 181 182 if not items: 183 yield task.set_result(None) 184 185 try: 186 key = _parse_public_key(jid, items[0]) 187 except ValueError as error: 188 raise MalformedStanzaError(str(error), items) 189 190 yield key 191 192 @iq_request_task 193 def request_keylist(self, jid=None): 194 task = yield 195 196 self._log.info('Request keylist from: %s', jid) 197 198 items = yield self.request_items( 199 Namespace.OPENPGP_PK, 200 max_items=1, 201 jid=jid) 202 203 raise_if_error(items) 204 205 if not items: 206 yield task.set_result(None) 207 208 try: 209 keylist = _parse_keylist(jid, items[0]) 210 except ValueError as error: 211 raise MalformedStanzaError(str(error), items) 212 213 self._log.info('Received keylist: %s', keylist) 214 yield keylist 215 216 @iq_request_task 217 def request_secret_key(self): 218 task = yield 219 220 self._log.info('Request secret key') 221 222 items = yield self.request_items( 223 Namespace.OPENPGP_SK, 224 max_items=1) 225 226 raise_if_error(items) 227 228 if not items: 229 yield task.set_result(None) 230 231 try: 232 secret_key = _parse_secret_key(items[0]) 233 except ValueError as error: 234 raise MalformedStanzaError(str(error), items) 235 236 yield secret_key 237 238 @iq_request_task 239 def set_secret_key(self, secret_key): 240 task = yield 241 242 self._log.info('Set public key') 243 244 options = { 245 'pubsub#persist_items': 'true', 246 'pubsub#access_model': 'whitelist', 247 } 248 249 self._log.info('Set secret key') 250 251 result = yield self.publish(Namespace.OPENPGP_SK, 252 _make_secret_key(secret_key), 253 id_='current', 254 options=options, 255 force_node_options=True) 256 257 yield finalize(task, result) 258 259 260def parse_signcrypt(stanza): 261 ''' 262 <signcrypt xmlns='urn:xmpp:openpgp:0'> 263 <to jid='juliet@example.org'/> 264 <time stamp='2014-07-10T17:06:00+02:00'/> 265 <rpad> 266 f0rm1l4n4-mT8y33j!Y%fRSrcd^ZE4Q7VDt1L%WEgR!kv 267 </rpad> 268 <payload> 269 <body xmlns='jabber:client'> 270 This is a secret message. 271 </body> 272 </payload> 273 </signcrypt> 274 ''' 275 if (stanza.getName() != 'signcrypt' or 276 stanza.getNamespace() != Namespace.OPENPGP): 277 raise StanzaMalformed('Invalid signcrypt node') 278 279 to_nodes = stanza.getTags('to') 280 if not to_nodes: 281 raise StanzaMalformed('missing to nodes') 282 283 recipients = [] 284 for to_node in to_nodes: 285 jid = to_node.getAttr('jid') 286 try: 287 recipients.append(JID.from_string(jid)) 288 except Exception as error: 289 raise StanzaMalformed('Invalid jid: %s %s' % (jid, error)) 290 291 timestamp = stanza.getTagAttr('time', 'stamp') 292 if timestamp is None: 293 raise StanzaMalformed('Invalid timestamp') 294 295 payload = stanza.getTag('payload') 296 if payload is None or payload.getChildren() is None: 297 raise StanzaMalformed('Invalid payload node') 298 return payload.getChildren(), recipients, timestamp 299 300 301def create_signcrypt_node(stanza, recipients, not_encrypted_nodes): 302 ''' 303 <signcrypt xmlns='urn:xmpp:openpgp:0'> 304 <to jid='juliet@example.org'/> 305 <time stamp='2014-07-10T17:06:00+02:00'/> 306 <rpad> 307 f0rm1l4n4-mT8y33j!Y%fRSrcd^ZE4Q7VDt1L%WEgR!kv 308 </rpad> 309 <payload> 310 <body xmlns='jabber:client'> 311 This is a secret message. 312 </body> 313 </payload> 314 </signcrypt> 315 ''' 316 encrypted_nodes = [] 317 child_nodes = list(stanza.getChildren()) 318 for node in child_nodes: 319 if (node.getName(), node.getNamespace()) not in not_encrypted_nodes: 320 if not node.getNamespace(): 321 node.setNamespace(Namespace.CLIENT) 322 encrypted_nodes.append(node) 323 stanza.delChild(node) 324 325 signcrypt = Node('signcrypt', attrs={'xmlns': Namespace.OPENPGP}) 326 for recipient in recipients: 327 signcrypt.addChild('to', attrs={'jid': str(recipient)}) 328 329 timestamp = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) 330 signcrypt.addChild('time', attrs={'stamp': timestamp}) 331 332 signcrypt.addChild('rpad').addData(get_rpad()) 333 334 payload = signcrypt.addChild('payload') 335 336 for node in encrypted_nodes: 337 payload.addChild(node=node) 338 339 return signcrypt 340 341 342def get_rpad(): 343 rpad_range = random.randint(30, 50) 344 return ''.join( 345 random.choice(string.ascii_letters) for _ in range(rpad_range)) 346 347 348def create_message_stanza(stanza, encrypted_payload, with_fallback_text): 349 b64encoded_payload = b64encode(encrypted_payload) 350 351 openpgp_node = Node('openpgp', attrs={'xmlns': Namespace.OPENPGP}) 352 openpgp_node.addData(b64encoded_payload) 353 stanza.addChild(node=openpgp_node) 354 355 eme_node = Node('encryption', attrs={'xmlns': Namespace.EME, 356 'namespace': Namespace.OPENPGP}) 357 stanza.addChild(node=eme_node) 358 359 if with_fallback_text: 360 stanza.setBody( 361 '[This message is *encrypted* with OpenPGP (See :XEP:`0373`]') 362 363 364def _make_keylist(keylist): 365 item = Node('public-keys-list', {'xmlns': Namespace.OPENPGP}) 366 if keylist is not None: 367 for key in keylist: 368 date = time.strftime('%Y-%m-%dT%H:%M:%SZ', 369 time.gmtime(key.date)) 370 attrs = {'v4-fingerprint': key.fingerprint, 371 'date': date} 372 item.addChild('pubkey-metadata', attrs=attrs) 373 return item 374 375 376def _make_public_key(key, date): 377 date = time.strftime( 378 '%Y-%m-%dT%H:%M:%SZ', time.gmtime(date)) 379 item = Node('pubkey', attrs={'xmlns': Namespace.OPENPGP, 380 'date': date}) 381 data = item.addChild('data') 382 data.addData(b64encode(key)) 383 return item 384 385 386def _make_secret_key(secret_key): 387 item = Node('secretkey', {'xmlns': Namespace.OPENPGP}) 388 if secret_key is not None: 389 item.setData(b64encode(secret_key)) 390 return item 391 392 393def _parse_public_key(jid, item): 394 pub_key = item.getTag('pubkey', namespace=Namespace.OPENPGP) 395 if pub_key is None: 396 raise ValueError('pubkey node missing') 397 398 date = parse_datetime(pub_key.getAttr('date'), epoch=True) 399 400 data = pub_key.getTag('data') 401 if data is None: 402 raise ValueError('data node missing') 403 404 try: 405 key = b64decode(data.getData(), return_type=bytes) 406 except Exception as error: 407 raise ValueError(f'decoding error: {error}') 408 409 return PGPPublicKey(jid, key, date) 410 411 412def _parse_keylist(jid, item): 413 keylist_node = item.getTag('public-keys-list', 414 namespace=Namespace.OPENPGP) 415 if keylist_node is None: 416 raise ValueError('public-keys-list node missing') 417 418 metadata = keylist_node.getTags('pubkey-metadata') 419 if not metadata: 420 return None 421 422 data = [] 423 for key in metadata: 424 fingerprint = key.getAttr('v4-fingerprint') 425 date = key.getAttr('date') 426 if fingerprint is None or date is None: 427 raise ValueError('Invalid metadata node') 428 429 timestamp = parse_datetime(date, epoch=True) 430 if timestamp is None: 431 raise ValueError('Invalid date timestamp: %s' % date) 432 433 data.append(PGPKeyMetadata(jid, fingerprint, timestamp)) 434 return data 435 436 437def _parse_secret_key(item): 438 sec_key = item.getTag('secretkey', namespace=Namespace.OPENPGP) 439 if sec_key is None: 440 raise ValueError('secretkey node missing') 441 442 data = sec_key.getData() 443 if not data: 444 raise ValueError('secretkey data missing') 445 446 try: 447 key = b64decode(data, return_type=bytes) 448 except Exception as error: 449 raise ValueError(f'decoding error: {error}') 450 451 return key 452