comparison src/browser/sat_browser/otrjs_wrapper.py @ 522:0de69fec24e9

browser and server sides: OTR plugin, first draft
author souliane <souliane@mailoo.org>
date Tue, 02 Sep 2014 21:28:42 +0200
parents
children 5add182e7dd5
comparison
equal deleted inserted replaced
521:69bffcf37ce3 522:0de69fec24e9
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 # Libervia wrapper for otr.js
5 # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org)
6 # Copyright (C) 2013, 2014 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 class context(object):
47
48 # Pre-declare these attributes to avoid the pylint "undefined variable" errors
49 STATUS_SEND_QUERY = None
50 STATUS_AKE_INIT = None
51 STATUS_AKE_SUCCESS = None
52 STATUS_END_OTR = None
53 STATE_PLAINTEXT = None
54 STATE_ENCRYPTED = None
55 STATE_FINISHED = None
56 OTR_VERSION_2 = None
57 OTR_VERSION_3 = None
58
59 JS("""
60 $cls_definition['STATUS_SEND_QUERY'] = OTR.CONST.STATUS_SEND_QUERY;
61 $cls_definition['STATUS_AKE_INIT'] = OTR.CONST.STATUS_AKE_INIT;
62 $cls_definition['STATUS_AKE_SUCCESS'] = OTR.CONST.STATUS_AKE_SUCCESS;
63 $cls_definition['STATUS_END_OTR'] = OTR.CONST.STATUS_END_OTR;
64 $cls_definition['STATE_PLAINTEXT'] = OTR.CONST.MSGSTATE_PLAINTEXT;
65 $cls_definition['STATE_ENCRYPTED'] = OTR.CONST.MSGSTATE_ENCRYPTED;
66 $cls_definition['STATE_FINISHED'] = OTR.CONST.MSGSTATE_FINISHED;
67 $cls_definition['OTR_VERSION_2'] = OTR.CONST.OTR_VERSION_2;
68 $cls_definition['OTR_VERSION_3'] = OTR.CONST.OTR_VERSION_3;
69 """)
70
71 class UnencryptedMessage(Exception):
72 pass
73
74 class Context(object):
75
76 def __init__(self, account, peername):
77 self.user = account
78 self.peer = peername
79 self.trustName = self.peer
80 options = {'fragment_size': 140,
81 'send_interval': 200,
82 'priv': account.getPrivkey(), # this would generate the account key if it hasn't been done yet
83 'debug': False,
84 }
85 JS("""self.otr = new OTR(options);""")
86
87 for policy in ('ALLOW_V2', 'ALLOW_V3', 'REQUIRE_ENCRYPTION'):
88 setattr(self.otr, policy, self.getPolicy(policy))
89
90 self.otr.on('ui', self.receiveMessageCb)
91 self.otr.on('io', self.sendMessageCb)
92 self.otr.on('error', self.messageErrorCb)
93 self.otr.on('status', lambda status: self.setStateCb(self.otr.msgstate, status))
94 self.otr.on('smp', self.smpAuthCb)
95
96 @property
97 def state(self):
98 return self.otr.msgstate
99
100 @state.setter
101 def state(self, state):
102 self.otr.msgstate = state
103
104 def getCurrentKey(self):
105 return self.otr.their_priv_pk
106
107 def setTrust(self, fingerprint, trustLevel):
108 self.user.setTrust(self.trustName, fingerprint, trustLevel)
109
110 def setCurrentTrust(self, trustLevel):
111 self.setTrust(self.otr.their_priv_pk.fingerprint(), trustLevel)
112
113 def getTrust(self, fingerprint, default=None):
114 return self.user.getTrust(self.trustName, fingerprint, default)
115
116 def getCurrentTrust(self):
117 # XXX: the docstring of potr for the return value of this method is incorrect
118 if self.otr.their_priv_pk is None:
119 return None
120 return self.getTrust(self.otr.their_priv_pk.fingerprint(), None)
121
122 def getUsedVersion(self):
123 """Return the otr version that is beeing used"""
124 # this method doesn't exist in potr, it has been added for convenience
125 try:
126 return self.otr.ake.otr_version
127 except AttributeError:
128 return None
129
130 def disconnect(self):
131 self.otr.endOtr()
132
133 def receiveMessage(self, msg):
134 """Received a message, ask otr.js to (try to) decrypt it"""
135 self.otr.receiveMsg(msg)
136
137 def sendMessage(self, msg):
138 """Ask otr.js to encrypt a message for sending"""
139 self.otr.sendMsg(msg)
140
141 def sendQueryMessage(self):
142 """Start or refresh an encryption communication"""
143 # otr.js offers this method, with potr you have to build the query message yourself
144 self.otr.sendQueryMsg()
145
146 def inject(self, msg, appdata=None):
147 return self.sendMessageCb(msg, appdata)
148
149 def getPolicy(self, key):
150 raise NotImplementedError
151
152 def smpAuthSecret(self, secret, question=None):
153 return self.otr.smpSecret(secret, question)
154
155 def smpAuthAbort(self, act=None):
156 # XXX: dirty hack to let the triggered method know who aborted the
157 # authentication. We need it to display the proper feedback and,
158 # if the buddy aborted, set the conversation 'unverified'.
159 self.otr.sm.init()
160 JS("""self.otr.sm.sendMsg(OTR.HLP.packTLV(6, ''))""")
161 self.smpAuthCb('abort', '', act)
162
163 def sendMessageCb(self, msg, meta):
164 """Actually send the message after it's been encrypted"""
165 raise NotImplementedError
166
167 def receiveMessageCb(self, msg, encrypted):
168 """Display the message after it's been eventually decrypted"""
169 raise NotImplementedError
170
171 def messageErrorCb(self, error):
172 """Message error callback"""
173 raise NotImplementedError
174
175 def setStateCb(self, newstate):
176 raise NotImplementedError
177
178 def smpAuthCb(self, newstate):
179 raise NotImplementedError
180
181 class Account(object):
182
183 def __init__(self, host):
184 self.host = host
185 self.privkey = None
186 self.trusts = {}
187
188 def getPrivkey(self):
189 # the return value must have a method serializePrivateKey()
190 # if the key is not saved yet, call savePrivkey to generate it
191 if self.privkey is None:
192 self.privkey = self.loadPrivkey()
193 if self.privkey is None:
194 JS("""self.privkey = new DSA();""")
195 self.savePrivkey()
196 return self.privkey
197
198 def setTrust(self, key, fingerprint, trustLevel):
199 if key not in self.trusts:
200 self.trusts[key] = {}
201 self.trusts[key][fingerprint] = trustLevel
202 self.saveTrusts()
203
204 def getTrust(self, key, fingerprint, default=None):
205 if key not in self.trusts:
206 return default
207 return self.trusts[key].get(fingerprint, default)
208
209 def loadPrivkey(self):
210 raise NotImplementedError
211
212 def savePrivkey(self):
213 raise NotImplementedError
214
215 def saveTrusts(self):
216 raise NotImplementedError
217
218
219 class crypt(object):
220
221 class PK(object):
222
223 def parsePrivateKey(self, key):
224 JS("""return DSA.parsePrivate(key);""")
225
226
227 # serialazePrivateKey is the method name in potr
228 JS("""DSA.serializePrivateKey = DSA.packPrivate;""")