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;""")