1 # Written by Arno Bakker
2 # see LICENSE.txt for license information
5 from BaseLib.Core.Utilities.Crypto import sha
6 from base64 import encodestring
7 from copy import deepcopy
10 from M2Crypto import Rand,EC
11 from BaseLib.Core.BitTornado.bencode import bencode, bdecode
12 from BaseLib.Core.BitTornado.BT1.MessageID import *
17 keypair_ecc_curve = EC.NID_sect233k1
18 num_random_bits = 1024*8 # bits
24 STATE_AUTHENTICATED = 3
29 Rand.rand_seed(os.urandom(num_random_bits/8))
34 def generate_keypair():
35 ec_keypair=EC.gen_params(keypair_ecc_curve)
39 def read_keypair(keypairfilename):
40 return EC.load_key(keypairfilename)
42 def read_pub_key(pubfilename):
43 return EC.load_pub_key(pubfilename)
45 def save_keypair(keypair,keypairfilename):
46 keypair.save_key(keypairfilename, None)
48 def save_pub_key(keypair,pubkeyfilename):
49 keypair.save_pub_key(pubkeyfilename)
52 # def show_permid(permid):
53 # See Tribler/utilities.py
55 def permid_for_user(permid):
57 return encodestring(permid).replace("\n","")
60 def sign_data(plaintext,ec_keypair):
61 digest = sha(plaintext).digest()
62 return ec_keypair.sign_dsa_asn1(digest)
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)
69 def verify_data_pubkeyobj(plaintext,pubkey,blob):
70 digest = sha(plaintext).digest()
71 return pubkey.verify_dsa_asn1(digest,blob)
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.
83 def generate_challenge():
84 randomB = Rand.rand_bytes(num_random_bits/8)
85 return [randomB,bencode(randomB)]
87 def check_challenge(cdata):
89 randomB = bdecode(cdata)
92 if len(randomB) != num_random_bits/8:
97 def generate_response1(randomB,peeridB,keypairA):
98 randomA = Rand.rand_bytes(num_random_bits/8)
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)]
106 def check_response1(rdata1,randomB,peeridB):
108 response1 = bdecode(rdata1)
111 if response1['B'] != peeridB:
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]
122 def generate_response2(randomA,peeridA,randomB,keypairB):
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)
129 def check_response2(rdata2,randomA,peeridA,randomB,peeridB):
131 response2 = bdecode(rdata2)
134 if response2['A'] != peeridA:
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):
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)
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)
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())
169 def verify_torrent_signature(metainfo):
170 r = deepcopy(metainfo)
171 signature = r['signature']
175 bmetainfo = bencode(r)
176 digester = sha(bmetainfo[:])
177 digest = digester.digest()
178 return do_verify_torrent_signature(digest,signature,signer)
183 def do_verify_torrent_signature(digest,sigstr,permid):
187 ecpub = EC.pub_key_from_der(permid)
190 intret = ecpub.verify_dsa_asn1(digest,sigstr)
193 print >> sys.stderr,"permid: Exception in verify_torrent_signature:",str(e)
198 class PermIDException(Exception): pass
200 class ChallengeResponse:
201 """ Exchange Challenge/Response via Overlay Swarm """
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())
207 self.secure_overlay = secure_overlay
209 self.my_random = None
211 self.peer_random = 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
222 def starting_party(self,locally_initiated):
223 if self.state == STATE_INITIAL and locally_initiated:
224 self.state = STATE_AWAIT_R1
229 def create_challenge(self):
230 [self.my_random,cdata] = generate_challenge()
233 def got_challenge_event(self,cdata,peer_id):
234 if self.state != STATE_INITIAL:
235 self.state = STATE_FAILED
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
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
250 def got_response1_event(self,rdata1,peer_id):
251 if self.state != STATE_AWAIT_R1:
252 self.state = STATE_FAILED
254 print >> sys.stderr,"Got unexpected RESPONSE1 message"
255 raise PermIDException
256 [randomA,peer_pub] = check_response1(rdata1,self.my_random,self.my_id)
258 if randomA is None or peer_pub is None:
259 self.state = STATE_FAILED
261 print >> sys.stderr,"Got bad RESPONSE1 message"
262 raise PermIDException
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
269 print >> sys.stderr,"Got the same Permid as myself"
270 raise PermIDException
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)
279 def got_response2_event(self,rdata2):
280 if self.state != STATE_AWAIT_R2:
281 self.state = STATE_FAILED
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
289 print >> sys.stderr,"Got bad RESPONSE2 message, authentication failed."
290 raise PermIDException
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
297 print >> sys.stderr,"Got the same Permid as myself"
298 raise PermIDException
300 self.set_peer_authenticated()
302 def set_peer_authenticated(self):
304 print >> sys.stderr,"permid: Challenge response succesful!"
305 self.state = STATE_AUTHENTICATED
307 def get_peer_authenticated(self):
308 return self.state == STATE_AUTHENTICATED
310 def get_peer_permid(self):
311 if self.state != STATE_AUTHENTICATED:
312 raise PermIDException
313 return self.peer_pub.get_der()
315 def get_auth_peer_id(self):
316 if self.state != STATE_AUTHENTICATED:
317 raise PermIDException
320 def get_challenge_minlen(self):
323 def get_response1_minlen(self):
326 def get_response2_minlen(self):
329 #---------------------------------------
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)
335 def send_challenge(self, conn):
336 cdata = self.create_challenge()
337 conn.send_message(CHALLENGE + str(cdata) )
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)
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())
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())
356 def got_message(self, conn, message):
357 """ Handle message for PermID exchange and return if the message is valid """
366 if len(message) < self.get_challenge_minlen():
368 print >> sys.stderr,"permid: Close on bad CHALLENGE: msg len",len(message)
369 self.state = STATE_FAILED
372 self.got_challenge(msg, conn)
375 print >> sys.stderr,"permid: Close on bad CHALLENGE: exception",str(e)
376 traceback.print_exc()
379 if len(message) < self.get_response1_minlen():
381 print >> sys.stderr,"permid: Close on bad RESPONSE1: msg len",len(message)
382 self.state = STATE_FAILED
385 self.got_response1(msg, conn)
388 print >> sys.stderr,"permid: Close on bad RESPONSE1: exception",str(e)
389 traceback.print_exc()
392 if len(message) < self.get_response2_minlen():
394 print >> sys.stderr,"permid: Close on bad RESPONSE2: msg len",len(message)
395 self.state = STATE_FAILED
398 self.got_response2(msg, conn)
401 print >> sys.stderr,"permid: Close on bad RESPONSE2: exception",str(e)
402 traceback.print_exc()
408 if __name__ == '__main__':
410 # ChallengeResponse(None, None)