instrumentation: add next-share/
[cs-p2p-next.git] / instrumentation / next-share / BaseLib / Core / Overlay / permid.py
1 # Written by Arno Bakker
2 # see LICENSE.txt for license information
3
4 import sys
5 from BaseLib.Core.Utilities.Crypto import sha
6 from base64 import encodestring
7 from copy import deepcopy
8 import traceback,os
9
10 from M2Crypto import Rand,EC
11 from BaseLib.Core.BitTornado.bencode import bencode, bdecode
12 from BaseLib.Core.BitTornado.BT1.MessageID import *
13
14 DEBUG = False
15
16 # Internal constants
17 keypair_ecc_curve = EC.NID_sect233k1
18 num_random_bits = 1024*8 # bits
19
20 # Protocol states
21 STATE_INITIAL = 0
22 STATE_AWAIT_R1 = 1
23 STATE_AWAIT_R2 = 2
24 STATE_AUTHENTICATED = 3
25 STATE_FAILED = 4
26
27 # Exported functions
28 def init():
29     Rand.rand_seed(os.urandom(num_random_bits/8))
30
31 def exit():
32     pass
33
34 def generate_keypair():
35     ec_keypair=EC.gen_params(keypair_ecc_curve)
36     ec_keypair.gen_key()
37     return ec_keypair
38
39 def read_keypair(keypairfilename):
40     return EC.load_key(keypairfilename)
41
42 def read_pub_key(pubfilename):
43     return EC.load_pub_key(pubfilename)
44
45 def save_keypair(keypair,keypairfilename):
46     keypair.save_key(keypairfilename, None)    
47
48 def save_pub_key(keypair,pubkeyfilename):
49     keypair.save_pub_key(pubkeyfilename)    
50
51
52 # def show_permid(permid):
53 # See Tribler/utilities.py
54
55 def permid_for_user(permid):
56     # Full BASE64-encoded 
57     return encodestring(permid).replace("\n","")
58
59 # For convenience
60 def sign_data(plaintext,ec_keypair):
61     digest = sha(plaintext).digest()
62     return ec_keypair.sign_dsa_asn1(digest)
63
64 def verify_data(plaintext,permid,blob):
65     pubkey = EC.pub_key_from_der(permid)
66     digest = sha(plaintext).digest()
67     return pubkey.verify_dsa_asn1(digest,blob)
68
69 def verify_data_pubkeyobj(plaintext,pubkey,blob):
70     digest = sha(plaintext).digest()
71     return pubkey.verify_dsa_asn1(digest,blob)
72
73
74 # Internal functions
75
76 #
77 # The following methods and ChallengeResponse class implement a
78 # Challenge/Response identification protocol, notably the
79 # ISO/IEC 9798-3 protocol, as described in $10.3.3 (ii) (2) of the 
80 # ``Handbook of Applied Cryptography''by  Alfred J. Menezes et al.
81 #
82
83 def generate_challenge():
84     randomB = Rand.rand_bytes(num_random_bits/8)
85     return [randomB,bencode(randomB)]
86
87 def check_challenge(cdata):
88     try:
89         randomB = bdecode(cdata)
90     except:
91         return None
92     if len(randomB) != num_random_bits/8:
93         return None
94     else:
95         return randomB
96
97 def generate_response1(randomB,peeridB,keypairA):
98     randomA = Rand.rand_bytes(num_random_bits/8)
99     response1 = {}
100     response1['certA'] = str(keypairA.pub().get_der())
101     response1['rA'] = randomA
102     response1['B'] = peeridB
103     response1['SA'] = sign_response(randomA,randomB,peeridB,keypairA)
104     return [randomA,bencode(response1)]
105
106 def check_response1(rdata1,randomB,peeridB):
107     try:
108         response1 = bdecode(rdata1)
109     except:
110         return [None,None]
111     if response1['B'] != peeridB:
112         return [None,None]
113     pubA_der = response1['certA']
114     pubA = EC.pub_key_from_der(pubA_der)
115     sigA = response1['SA']
116     randomA = response1['rA']
117     if verify_response(randomA,randomB,peeridB,pubA,sigA):
118         return [randomA,pubA]
119     else:
120         return [None,None]
121     
122 def generate_response2(randomA,peeridA,randomB,keypairB):
123     response2 = {}
124     response2['certB'] = str(keypairB.pub().get_der())
125     response2['A'] = peeridA
126     response2['SB'] = sign_response(randomB,randomA,peeridA,keypairB)
127     return bencode(response2)
128
129 def check_response2(rdata2,randomA,peeridA,randomB,peeridB):
130     try:
131         response2 = bdecode(rdata2)
132     except:
133         return None
134     if response2['A'] != peeridA:
135         return None
136     pubB_der = response2['certB']
137     pubB = EC.pub_key_from_der(pubB_der)
138     sigB = response2['SB']
139     if verify_response(randomB,randomA,peeridA,pubB,sigB):
140         return pubB
141     else:
142         return None
143
144 def sign_response(randomA,randomB,peeridB,keypairA):
145     list = [ randomA, randomB, peeridB ]
146     blist = bencode(list)
147     digest = sha(blist).digest()
148     blob = keypairA.sign_dsa_asn1(digest)
149     return blob
150
151 def verify_response(randomA,randomB,peeridB,pubA,sigA):
152     list = [ randomA, randomB, peeridB ]
153     blist = bencode(list)
154     digest = sha(blist).digest()
155     return pubA.verify_dsa_asn1(digest,sigA)
156     
157
158 # External functions
159
160 def create_torrent_signature(metainfo,keypairfilename):
161     keypair = EC.load_key(keypairfilename)
162     bmetainfo = bencode(metainfo)
163     digester = sha(bmetainfo[:])
164     digest = digester.digest()
165     sigstr = keypair.sign_dsa_asn1(digest)
166     metainfo['signature'] = sigstr
167     metainfo['signer'] = str(keypair.pub().get_der())
168     
169 def verify_torrent_signature(metainfo):
170     r = deepcopy(metainfo)
171     signature = r['signature']
172     signer = r['signer']
173     del r['signature']
174     del r['signer']
175     bmetainfo = bencode(r)
176     digester = sha(bmetainfo[:])
177     digest = digester.digest()
178     return do_verify_torrent_signature(digest,signature,signer)
179
180
181 # Internal
182
183 def do_verify_torrent_signature(digest,sigstr,permid):
184     if permid is None:
185         return False
186     try:
187         ecpub = EC.pub_key_from_der(permid)
188         if ecpub is None:
189             return False
190         intret = ecpub.verify_dsa_asn1(digest,sigstr)
191         return intret == 1
192     except Exception, e:
193         print >> sys.stderr,"permid: Exception in verify_torrent_signature:",str(e) 
194         return False
195
196
197 # Exported classes
198 class PermIDException(Exception): pass
199
200 class ChallengeResponse:
201     """ Exchange Challenge/Response via Overlay Swarm """
202
203     def __init__(self, my_keypair, my_id, secure_overlay):
204         self.my_keypair = my_keypair
205         self.permid = str(my_keypair.pub().get_der())
206         self.my_id = my_id
207         self.secure_overlay = secure_overlay
208
209         self.my_random = None
210         self.peer_id = None
211         self.peer_random = None
212         self.peer_pub = None
213         self.state = STATE_INITIAL
214         # Calculate message limits:
215         [dummy_random,cdata] = generate_challenge()
216         [dummy_random1,rdata1] = generate_response1(dummy_random,my_id,self.my_keypair)
217         rdata2 = generate_response2(dummy_random,my_id,dummy_random,self.my_keypair)
218         self.minchal = 1+len(cdata) # 1+ = message type
219         self.minr1 = 1+len(rdata1) - 1 # Arno: hack, also here, just to be on the safe side
220         self.minr2 = 1+len(rdata2) - 1 # Arno: hack, sometimes the official minimum is too big
221
222     def starting_party(self,locally_initiated):
223         if self.state == STATE_INITIAL and locally_initiated:
224             self.state = STATE_AWAIT_R1
225             return True
226         else:
227             return False
228
229     def create_challenge(self):
230         [self.my_random,cdata] = generate_challenge()
231         return cdata
232
233     def got_challenge_event(self,cdata,peer_id):
234         if self.state != STATE_INITIAL:
235             self.state = STATE_FAILED
236             if DEBUG:
237                 print >> sys.stderr, "Got unexpected CHALLENGE message"
238             raise PermIDException
239         self.peer_random = check_challenge(cdata)
240         if self.peer_random is None:
241             self.state = STATE_FAILED
242             if DEBUG:
243                 print >> sys.stderr,"Got bad CHALLENGE message"
244             raise PermIDException
245         self.peer_id = peer_id
246         [self.my_random,rdata1] = generate_response1(self.peer_random,peer_id,self.my_keypair)
247         self.state = STATE_AWAIT_R2
248         return rdata1
249
250     def got_response1_event(self,rdata1,peer_id):
251         if self.state != STATE_AWAIT_R1:
252             self.state = STATE_FAILED
253             if DEBUG:
254                 print >> sys.stderr,"Got unexpected RESPONSE1 message"
255             raise PermIDException
256         [randomA,peer_pub] = check_response1(rdata1,self.my_random,self.my_id)
257         
258         if randomA is None or peer_pub is None:
259             self.state = STATE_FAILED
260             if DEBUG:
261                 print >> sys.stderr,"Got bad RESPONSE1 message"
262             raise PermIDException
263         
264         # avoid being connected by myself
265         peer_permid = str(peer_pub.get_der())
266         if self.permid == peer_permid:
267             self.state = STATE_FAILED
268             if DEBUG:
269                 print >> sys.stderr,"Got the same Permid as myself"
270             raise PermIDException
271         
272         self.peer_id = peer_id
273         self.peer_random = randomA
274         self.peer_pub = peer_pub
275         self.set_peer_authenticated()
276         rdata2 = generate_response2(self.peer_random,self.peer_id,self.my_random,self.my_keypair)
277         return rdata2
278
279     def got_response2_event(self,rdata2):
280         if self.state != STATE_AWAIT_R2:
281             self.state = STATE_FAILED
282             if DEBUG:
283                 print >> sys.stderr,"Got unexpected RESPONSE2 message"
284             raise PermIDException
285         self.peer_pub = check_response2(rdata2,self.my_random,self.my_id,self.peer_random,self.peer_id)
286         if self.peer_pub is None:
287             self.state = STATE_FAILED
288             if DEBUG:
289                 print >> sys.stderr,"Got bad RESPONSE2 message, authentication failed."
290             raise PermIDException
291         else:
292             # avoid being connected by myself
293             peer_permid = str(self.peer_pub.get_der())
294             if self.permid == peer_permid:
295                 self.state = STATE_FAILED
296                 if DEBUG:
297                     print >> sys.stderr,"Got the same Permid as myself"
298                 raise PermIDException
299             else:
300                 self.set_peer_authenticated()
301
302     def set_peer_authenticated(self):
303         if DEBUG:
304             print >> sys.stderr,"permid: Challenge response succesful!"
305         self.state = STATE_AUTHENTICATED
306
307     def get_peer_authenticated(self):
308         return self.state == STATE_AUTHENTICATED
309     
310     def get_peer_permid(self):
311         if self.state != STATE_AUTHENTICATED:
312             raise PermIDException
313         return self.peer_pub.get_der()
314
315     def get_auth_peer_id(self):
316         if self.state != STATE_AUTHENTICATED:
317             raise PermIDException
318         return self.peer_id
319
320     def get_challenge_minlen(self):
321         return self.minchal
322
323     def get_response1_minlen(self):
324         return self.minr1
325
326     def get_response2_minlen(self):
327         return self.minr2
328
329 #---------------------------------------
330
331     def start_cr(self, conn):
332         if not self.get_peer_authenticated() and self.starting_party(conn.is_locally_initiated()):
333             self.send_challenge(conn)
334
335     def send_challenge(self, conn):
336         cdata = self.create_challenge()
337         conn.send_message(CHALLENGE + str(cdata) )
338
339     def got_challenge(self, cdata, conn):
340         rdata1 = self.got_challenge_event(cdata, conn.get_unauth_peer_id())
341         conn.send_message(RESPONSE1 + rdata1)
342
343     def got_response1(self, rdata1, conn):
344         rdata2 = self.got_response1_event(rdata1, conn.get_unauth_peer_id())
345         conn.send_message(RESPONSE2 + rdata2)
346         # get_peer_permid() throws exception if auth has failed
347         self.secure_overlay.got_auth_connection(conn,self.get_peer_permid(),self.get_auth_peer_id())
348      
349     def got_response2(self, rdata2, conn):
350         self.got_response2_event(rdata2)
351         if self.get_peer_authenticated():
352             #conn.send_message('')    # Send KeepAlive message as reply
353             self.secure_overlay.got_auth_connection(conn,self.get_peer_permid(),self.get_auth_peer_id())
354
355
356     def got_message(self, conn, message):
357         """ Handle message for PermID exchange and return if the message is valid """
358         
359         if not conn:
360             return False
361         t = message[0]
362         if message[1:]:
363             msg = message[1:]
364             
365         if t == CHALLENGE:
366             if len(message) < self.get_challenge_minlen():
367                 if DEBUG:
368                     print >> sys.stderr,"permid: Close on bad CHALLENGE: msg len",len(message)
369                 self.state = STATE_FAILED
370                 return False
371             try:
372                 self.got_challenge(msg, conn)
373             except Exception,e:
374                 if DEBUG:
375                     print >> sys.stderr,"permid: Close on bad CHALLENGE: exception",str(e)
376                     traceback.print_exc()
377                 return False
378         elif t == RESPONSE1:
379             if len(message) < self.get_response1_minlen():
380                 if DEBUG:
381                     print >> sys.stderr,"permid: Close on bad RESPONSE1: msg len",len(message)
382                 self.state = STATE_FAILED
383                 return False
384             try:
385                 self.got_response1(msg, conn)
386             except Exception,e:
387                 if DEBUG:
388                     print >> sys.stderr,"permid: Close on bad RESPONSE1: exception",str(e)
389                     traceback.print_exc()
390                 return False
391         elif t == RESPONSE2:
392             if len(message) < self.get_response2_minlen():
393                 if DEBUG:
394                     print >> sys.stderr,"permid: Close on bad RESPONSE2: msg len",len(message)
395                 self.state = STATE_FAILED
396                 return False
397             try:
398                 self.got_response2(msg, conn)
399             except Exception,e:
400                 if DEBUG:
401                     print >> sys.stderr,"permid: Close on bad RESPONSE2: exception",str(e)
402                     traceback.print_exc()
403                 return False
404         else:
405             return False
406         return True
407
408 if __name__ == '__main__':
409     init()
410 #    ChallengeResponse(None, None)