Mercurial > libervia-web
diff browser/sat_browser/otrjs_wrapper.py @ 1124:28e3eb3bb217
files reorganisation and installation rework:
- files have been reorganised to follow other SàT projects and usual Python organisation (no more "/src" directory)
- VERSION file is now used, as for other SàT projects
- replace the overcomplicated setup.py be a more sane one. Pyjamas part is not compiled anymore by setup.py, it must be done separatly
- removed check for data_dir if it's empty
- installation tested working in virtual env
- libervia launching script is now in bin/libervia
author | Goffi <goffi@goffi.org> |
---|---|
date | Sat, 25 Aug 2018 17:59:48 +0200 |
parents | src/browser/sat_browser/otrjs_wrapper.py@f2170536ba23 |
children | 2af117bfe6cc |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/browser/sat_browser/otrjs_wrapper.py Sat Aug 25 17:59:48 2018 +0200 @@ -0,0 +1,273 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Libervia wrapper for otr.js +# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) +# Copyright (C) 2013-2016 Adrien Cossa (souliane@mailoo.org) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +"""This file is a wrapper for otr.js. It partially reproduces the usage +(modules, classes and attributes names) and behavior of potr, so you +can easily adapt some code based on potr to Pyjamas applications. + +potr is released under the GNU LESSER GENERAL PUBLIC LICENSE Version 3 + - https://github.com/python-otr/pure-python-otr/blob/master/LICENSE + +otr.js is released under the Mozilla Public Licence Version 2.0 + - https://github.com/arlolra/otr/blob/master/license +""" + +from __pyjamas__ import JS + +# should you re-use this class outside SàT, you can import __pyjamas__.console as log instead +from sat.core.log import getLogger +log = getLogger(__name__) + + +# XXX: pyjamas can't probably import more than one JS file, it messes the order. +# XXX: pyjamas needs the file to be in the compilation directory - no submodule. +# XXX: pyjamas needs the imported file to end with a empty line or semi-column. +# FIXME: fix these bugs upstream in Pyjamas +import otr.min.js + + +def isSupported(): + JS("""return (typeof OTR !== 'undefined');""") + + +if not isSupported(): + # see https://developer.mozilla.org/en-US/docs/Web/API/window.crypto.getRandomValues#Browser_Compatibility + log.error('Your browser is not implementing CSPRNG: OTR has been disabled.') + raise ImportError('CSPRNG is not supported by your browser') + + +class context(object): + + # Pre-declare these attributes to avoid the pylint "undefined variable" errors + STATUS_SEND_QUERY = None + STATUS_AKE_INIT = None + STATUS_AKE_SUCCESS = None + STATUS_END_OTR = None + STATE_PLAINTEXT = None + STATE_ENCRYPTED = None + STATE_FINISHED = None + OTR_TAG = None + OTR_VERSION_2 = None + OTR_VERSION_3 = None + WHITESPACE_TAG = None + WHITESPACE_TAG_V2 = None + WHITESPACE_TAG_V3 = None + + JS(""" + $cls_definition['STATUS_SEND_QUERY'] = OTR.CONST.STATUS_SEND_QUERY; + $cls_definition['STATUS_AKE_INIT'] = OTR.CONST.STATUS_AKE_INIT; + $cls_definition['STATUS_AKE_SUCCESS'] = OTR.CONST.STATUS_AKE_SUCCESS; + $cls_definition['STATUS_END_OTR'] = OTR.CONST.STATUS_END_OTR; + $cls_definition['STATE_PLAINTEXT'] = OTR.CONST.MSGSTATE_PLAINTEXT; + $cls_definition['STATE_ENCRYPTED'] = OTR.CONST.MSGSTATE_ENCRYPTED; + $cls_definition['STATE_FINISHED'] = OTR.CONST.MSGSTATE_FINISHED; + $cls_definition['OTR_TAG'] = OTR.CONST.OTR_TAG; + $cls_definition['OTR_VERSION_2'] = OTR.CONST.OTR_VERSION_2; + $cls_definition['OTR_VERSION_3'] = OTR.CONST.OTR_VERSION_3; + $cls_definition['WHITESPACE_TAG'] = OTR.CONST.WHITESPACE_TAG; + $cls_definition['WHITESPACE_TAG_V2'] = OTR.CONST.WHITESPACE_TAG_V2; + $cls_definition['WHITESPACE_TAG_V3'] = OTR.CONST.WHITESPACE_TAG_V3; + """) + + class UnencryptedMessage(Exception): + pass + + class Context(object): + + def __init__(self, account, peername): + self.user = account + self.peer = peername + self.trustName = self.peer + options = {'fragment_size': 140, + 'send_interval': 200, + 'priv': account.getPrivkey(), # this would generate the account key if it hasn't been done yet + 'debug': False, + } + JS("""self.otr = new OTR(options);""") + + for policy in ('ALLOW_V2', 'ALLOW_V3', 'REQUIRE_ENCRYPTION'): + setattr(self.otr, policy, self.getPolicy(policy)) + + self.otr.on('ui', self.receiveMessageCb) + self.otr.on('io', self.sendMessageCb) + self.otr.on('error', self.messageErrorCb) + self.otr.on('status', lambda status: self.setStateCb(self.otr.msgstate, status)) + self.otr.on('smp', self.smpAuthCb) + + @property + def state(self): + return self.otr.msgstate + + @state.setter + def state(self, state): + self.otr.msgstate = state + + def getCurrentKey(self): + return self.otr.their_priv_pk + + def setTrust(self, fingerprint, trustLevel): + self.user.setTrust(self.trustName, fingerprint, trustLevel) + + def setCurrentTrust(self, trustLevel): + self.setTrust(self.otr.their_priv_pk.fingerprint(), trustLevel) + + def getTrust(self, fingerprint, default=None): + return self.user.getTrust(self.trustName, fingerprint, default) + + def getCurrentTrust(self): + # XXX: the docstring of potr for the return value of this method is incorrect + if self.otr.their_priv_pk is None: + return None + return self.getTrust(self.otr.their_priv_pk.fingerprint(), None) + + def getUsedVersion(self): + """Return the otr version that is beeing used""" + # this method doesn't exist in potr, it has been added for convenience + try: + return self.otr.ake.otr_version + except AttributeError: + return None + + def disconnect(self): + self.otr.endOtr() + + def finish(self): + """Finish the session - avoid to send any message and the user has to manually disconnect""" + # it means TLV of type 1 (two first bytes), message length 0 (2 last bytes) + self.otr.handleTLVs('\x00\x01\x00\x00') + + def receiveMessage(self, msg): + """Received a message, ask otr.js to (try to) decrypt it""" + self.otr.receiveMsg(msg) + + def sendMessage(self, msg): + """Ask otr.js to encrypt a message for sending""" + self.otr.sendMsg(msg) + + def sendQueryMessage(self): + """Start or refresh an encryption communication""" + # otr.js offers this method, with potr you have to build the query message yourself + self.otr.sendQueryMsg() + + def inject(self, msg, appdata=None): + return self.sendMessageCb(msg, appdata) + + def getPolicy(self, key): + raise NotImplementedError + + def smpAuthSecret(self, secret, question=None): + return self.otr.smpSecret(secret, question) + + def smpAuthAbort(self, act=None): + # XXX: dirty hack to let the triggered method know who aborted the + # authentication. We need it to display the proper feedback and, + # if the correspondent aborted, set the conversation 'unverified'. + self.otr.sm.init() + JS("""self.otr.sm.sendMsg(OTR.HLP.packTLV(6, ''))""") + self.smpAuthCb('abort', '', act) + + def sendMessageCb(self, msg, meta): + """Actually send the message after it's been encrypted""" + raise NotImplementedError + + def receiveMessageCb(self, msg, encrypted): + """Display the message after it's been eventually decrypted""" + raise NotImplementedError + + def messageErrorCb(self, error): + """Message error callback""" + raise NotImplementedError + + def setStateCb(self, newstate): + raise NotImplementedError + + def smpAuthCb(self, newstate): + raise NotImplementedError + + class Account(object): + + def __init__(self, host): + self.host = host + self.privkey = None + self.trusts = {} + + def getPrivkey(self): + # the return value must have a method serializePrivateKey() + # if the key is not saved yet, call savePrivkey to generate it + if self.privkey is None: + self.privkey = self.loadPrivkey() + if self.privkey is None: + JS("""self.privkey = new DSA();""") + self.savePrivkey() + return self.privkey + + def setTrust(self, key, fingerprint, trustLevel): + if key not in self.trusts: + self.trusts[key] = {} + self.trusts[key][fingerprint] = trustLevel + self.saveTrusts() + + def getTrust(self, key, fingerprint, default=None): + if key not in self.trusts: + return default + return self.trusts[key].get(fingerprint, default) + + def loadPrivkey(self): + raise NotImplementedError + + def savePrivkey(self): + raise NotImplementedError + + def saveTrusts(self): + raise NotImplementedError + + +class crypt(object): + + class PK(object): + + def parsePrivateKey(self, key): + JS("""return DSA.parsePrivate(key);""") + + +class proto(object): + + @classmethod + def checkForOTR(cls, body): + """Helper method to check if the message contains OTR starting tag or whitespace + + @return: + - context.OTR_TAG if the message starts with it + - context.WHITESPACE_TAG if the message contains OTR whitespaces + - None otherwise + """ + if body.startswith(context.OTR_TAG): + return context.OTR_TAG + index = body.find(context.WHITESPACE_TAG) + if index < 0: + return False + tags = [body[i:i + 8] for i in range(index, len(body), 8)] + if [True for tag in tags if tag in (context.WHITESPACE_TAG_V2, context.WHITESPACE_TAG_V3)]: + return context.WHITESPACE_TAG + return None + + +# serialazePrivateKey is the method name in potr +JS("""DSA.serializePrivateKey = DSA.packPrivate;""")