comparison 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
comparison
equal deleted inserted replaced
1123:63a4b8fe9782 1124:28e3eb3bb217
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 # Libervia wrapper for otr.js
5 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
6 # Copyright (C) 2013-2016 Adrien Cossa (souliane@mailoo.org)
7
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Affero General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Affero General Public License for more details.
17
18 # You should have received a copy of the GNU Affero General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20
21 """This file is a wrapper for otr.js. It partially reproduces the usage
22 (modules, classes and attributes names) and behavior of potr, so you
23 can easily adapt some code based on potr to Pyjamas applications.
24
25 potr is released under the GNU LESSER GENERAL PUBLIC LICENSE Version 3
26 - https://github.com/python-otr/pure-python-otr/blob/master/LICENSE
27
28 otr.js is released under the Mozilla Public Licence Version 2.0
29 - https://github.com/arlolra/otr/blob/master/license
30 """
31
32 from __pyjamas__ import JS
33
34 # should you re-use this class outside SàT, you can import __pyjamas__.console as log instead
35 from sat.core.log import getLogger
36 log = getLogger(__name__)
37
38
39 # XXX: pyjamas can't probably import more than one JS file, it messes the order.
40 # XXX: pyjamas needs the file to be in the compilation directory - no submodule.
41 # XXX: pyjamas needs the imported file to end with a empty line or semi-column.
42 # FIXME: fix these bugs upstream in Pyjamas
43 import otr.min.js
44
45
46 def isSupported():
47 JS("""return (typeof OTR !== 'undefined');""")
48
49
50 if not isSupported():
51 # see https://developer.mozilla.org/en-US/docs/Web/API/window.crypto.getRandomValues#Browser_Compatibility
52 log.error('Your browser is not implementing CSPRNG: OTR has been disabled.')
53 raise ImportError('CSPRNG is not supported by your browser')
54
55
56 class context(object):
57
58 # Pre-declare these attributes to avoid the pylint "undefined variable" errors
59 STATUS_SEND_QUERY = None
60 STATUS_AKE_INIT = None
61 STATUS_AKE_SUCCESS = None
62 STATUS_END_OTR = None
63 STATE_PLAINTEXT = None
64 STATE_ENCRYPTED = None
65 STATE_FINISHED = None
66 OTR_TAG = None
67 OTR_VERSION_2 = None
68 OTR_VERSION_3 = None
69 WHITESPACE_TAG = None
70 WHITESPACE_TAG_V2 = None
71 WHITESPACE_TAG_V3 = None
72
73 JS("""
74 $cls_definition['STATUS_SEND_QUERY'] = OTR.CONST.STATUS_SEND_QUERY;
75 $cls_definition['STATUS_AKE_INIT'] = OTR.CONST.STATUS_AKE_INIT;
76 $cls_definition['STATUS_AKE_SUCCESS'] = OTR.CONST.STATUS_AKE_SUCCESS;
77 $cls_definition['STATUS_END_OTR'] = OTR.CONST.STATUS_END_OTR;
78 $cls_definition['STATE_PLAINTEXT'] = OTR.CONST.MSGSTATE_PLAINTEXT;
79 $cls_definition['STATE_ENCRYPTED'] = OTR.CONST.MSGSTATE_ENCRYPTED;
80 $cls_definition['STATE_FINISHED'] = OTR.CONST.MSGSTATE_FINISHED;
81 $cls_definition['OTR_TAG'] = OTR.CONST.OTR_TAG;
82 $cls_definition['OTR_VERSION_2'] = OTR.CONST.OTR_VERSION_2;
83 $cls_definition['OTR_VERSION_3'] = OTR.CONST.OTR_VERSION_3;
84 $cls_definition['WHITESPACE_TAG'] = OTR.CONST.WHITESPACE_TAG;
85 $cls_definition['WHITESPACE_TAG_V2'] = OTR.CONST.WHITESPACE_TAG_V2;
86 $cls_definition['WHITESPACE_TAG_V3'] = OTR.CONST.WHITESPACE_TAG_V3;
87 """)
88
89 class UnencryptedMessage(Exception):
90 pass
91
92 class Context(object):
93
94 def __init__(self, account, peername):
95 self.user = account
96 self.peer = peername
97 self.trustName = self.peer
98 options = {'fragment_size': 140,
99 'send_interval': 200,
100 'priv': account.getPrivkey(), # this would generate the account key if it hasn't been done yet
101 'debug': False,
102 }
103 JS("""self.otr = new OTR(options);""")
104
105 for policy in ('ALLOW_V2', 'ALLOW_V3', 'REQUIRE_ENCRYPTION'):
106 setattr(self.otr, policy, self.getPolicy(policy))
107
108 self.otr.on('ui', self.receiveMessageCb)
109 self.otr.on('io', self.sendMessageCb)
110 self.otr.on('error', self.messageErrorCb)
111 self.otr.on('status', lambda status: self.setStateCb(self.otr.msgstate, status))
112 self.otr.on('smp', self.smpAuthCb)
113
114 @property
115 def state(self):
116 return self.otr.msgstate
117
118 @state.setter
119 def state(self, state):
120 self.otr.msgstate = state
121
122 def getCurrentKey(self):
123 return self.otr.their_priv_pk
124
125 def setTrust(self, fingerprint, trustLevel):
126 self.user.setTrust(self.trustName, fingerprint, trustLevel)
127
128 def setCurrentTrust(self, trustLevel):
129 self.setTrust(self.otr.their_priv_pk.fingerprint(), trustLevel)
130
131 def getTrust(self, fingerprint, default=None):
132 return self.user.getTrust(self.trustName, fingerprint, default)
133
134 def getCurrentTrust(self):
135 # XXX: the docstring of potr for the return value of this method is incorrect
136 if self.otr.their_priv_pk is None:
137 return None
138 return self.getTrust(self.otr.their_priv_pk.fingerprint(), None)
139
140 def getUsedVersion(self):
141 """Return the otr version that is beeing used"""
142 # this method doesn't exist in potr, it has been added for convenience
143 try:
144 return self.otr.ake.otr_version
145 except AttributeError:
146 return None
147
148 def disconnect(self):
149 self.otr.endOtr()
150
151 def finish(self):
152 """Finish the session - avoid to send any message and the user has to manually disconnect"""
153 # it means TLV of type 1 (two first bytes), message length 0 (2 last bytes)
154 self.otr.handleTLVs('\x00\x01\x00\x00')
155
156 def receiveMessage(self, msg):
157 """Received a message, ask otr.js to (try to) decrypt it"""
158 self.otr.receiveMsg(msg)
159
160 def sendMessage(self, msg):
161 """Ask otr.js to encrypt a message for sending"""
162 self.otr.sendMsg(msg)
163
164 def sendQueryMessage(self):
165 """Start or refresh an encryption communication"""
166 # otr.js offers this method, with potr you have to build the query message yourself
167 self.otr.sendQueryMsg()
168
169 def inject(self, msg, appdata=None):
170 return self.sendMessageCb(msg, appdata)
171
172 def getPolicy(self, key):
173 raise NotImplementedError
174
175 def smpAuthSecret(self, secret, question=None):
176 return self.otr.smpSecret(secret, question)
177
178 def smpAuthAbort(self, act=None):
179 # XXX: dirty hack to let the triggered method know who aborted the
180 # authentication. We need it to display the proper feedback and,
181 # if the correspondent aborted, set the conversation 'unverified'.
182 self.otr.sm.init()
183 JS("""self.otr.sm.sendMsg(OTR.HLP.packTLV(6, ''))""")
184 self.smpAuthCb('abort', '', act)
185
186 def sendMessageCb(self, msg, meta):
187 """Actually send the message after it's been encrypted"""
188 raise NotImplementedError
189
190 def receiveMessageCb(self, msg, encrypted):
191 """Display the message after it's been eventually decrypted"""
192 raise NotImplementedError
193
194 def messageErrorCb(self, error):
195 """Message error callback"""
196 raise NotImplementedError
197
198 def setStateCb(self, newstate):
199 raise NotImplementedError
200
201 def smpAuthCb(self, newstate):
202 raise NotImplementedError
203
204 class Account(object):
205
206 def __init__(self, host):
207 self.host = host
208 self.privkey = None
209 self.trusts = {}
210
211 def getPrivkey(self):
212 # the return value must have a method serializePrivateKey()
213 # if the key is not saved yet, call savePrivkey to generate it
214 if self.privkey is None:
215 self.privkey = self.loadPrivkey()
216 if self.privkey is None:
217 JS("""self.privkey = new DSA();""")
218 self.savePrivkey()
219 return self.privkey
220
221 def setTrust(self, key, fingerprint, trustLevel):
222 if key not in self.trusts:
223 self.trusts[key] = {}
224 self.trusts[key][fingerprint] = trustLevel
225 self.saveTrusts()
226
227 def getTrust(self, key, fingerprint, default=None):
228 if key not in self.trusts:
229 return default
230 return self.trusts[key].get(fingerprint, default)
231
232 def loadPrivkey(self):
233 raise NotImplementedError
234
235 def savePrivkey(self):
236 raise NotImplementedError
237
238 def saveTrusts(self):
239 raise NotImplementedError
240
241
242 class crypt(object):
243
244 class PK(object):
245
246 def parsePrivateKey(self, key):
247 JS("""return DSA.parsePrivate(key);""")
248
249
250 class proto(object):
251
252 @classmethod
253 def checkForOTR(cls, body):
254 """Helper method to check if the message contains OTR starting tag or whitespace
255
256 @return:
257 - context.OTR_TAG if the message starts with it
258 - context.WHITESPACE_TAG if the message contains OTR whitespaces
259 - None otherwise
260 """
261 if body.startswith(context.OTR_TAG):
262 return context.OTR_TAG
263 index = body.find(context.WHITESPACE_TAG)
264 if index < 0:
265 return False
266 tags = [body[i:i + 8] for i in range(index, len(body), 8)]
267 if [True for tag in tags if tag in (context.WHITESPACE_TAG_V2, context.WHITESPACE_TAG_V3)]:
268 return context.WHITESPACE_TAG
269 return None
270
271
272 # serialazePrivateKey is the method name in potr
273 JS("""DSA.serializePrivateKey = DSA.packPrivate;""")