1 # written by Arno Bakker
2 # see LICENSE.txt for license information
5 from traceback import print_exc
6 from cStringIO import StringIO
11 from BaseLib.Core.Utilities.Crypto import sha,RSA_pub_key_from_der
12 from BaseLib.Core.osutils import *
13 from M2Crypto import EC
14 from BaseLib.Core.osutils import *
15 from types import StringType
21 def __init__(self,piecelen,npieces):
22 self.piecelen = piecelen
23 self.npieces = npieces
26 def get_piece_length(self):
29 def get_npieces(self):
32 def get_content_blocksize(self):
35 def sign(self,content):
38 def verify(self,piece):
41 def get_content(self,piece):
44 def get_source_seqnum(self):
47 def set_source_seqnum(self,seqnum):
51 class NullAuthenticator(Authenticator):
53 def __init__(self,piecelen,npieces):
54 Authenticator.__init__(self,piecelen,npieces)
55 self.contentblocksize = piecelen
57 def get_content_blocksize(self):
58 return self.contentblocksize
60 def sign(self,content):
63 def verify(self,piece):
66 def get_content(self,piece):
70 class ECDSAAuthenticator(Authenticator):
71 """ Authenticator who places a ECDSA signature in the last part of a
72 piece. In particular, the sig consists of:
73 - an 8 byte sequence number
74 - an 8 byte real-time timestamp
75 - a 1 byte length field followed by
76 - a variable-length ECDSA signature in ASN.1, (max 64 bytes)
77 - optionally 0x00 padding bytes, if the ECDSA sig is less than 64 bytes,
78 to give a total of 81 bytes.
84 MAX_ECDSA_ASN1_SIGSIZE = 64
85 EXTRA_SIZE = SEQNUM_SIZE + RTSTAMP_SIZE
86 # = seqnum + rtstamp + 1 byte length + MAX_ECDSA, padded
87 # put seqnum + rtstamp directly after content, so we calc the sig directly
88 # from the received buffer.
89 OUR_SIGSIZE = EXTRA_SIZE+LENGTH_SIZE+MAX_ECDSA_ASN1_SIGSIZE
91 def __init__(self,piecelen,npieces,keypair=None,pubkeypem=None):
93 print >>sys.stderr,"ECDSAAuth: npieces",npieces
95 Authenticator.__init__(self,piecelen,npieces)
96 self.contentblocksize = piecelen-self.OUR_SIGSIZE
97 self.keypair = keypair
98 if pubkeypem is not None:
99 #print >>sys.stderr,"ECDSAAuth: pubkeypem",`pubkeypem`
100 self.pubkey = EC.pub_key_from_der(pubkeypem)
105 def get_content_blocksize(self):
106 return self.contentblocksize
108 def sign(self,content):
109 rtstamp = time.time()
110 #print >>sys.stderr,"ECDSAAuth: sign: ts %.5f s" % rtstamp
112 extra = struct.pack('>Qd', self.seqnum,rtstamp)
115 sig = ecdsa_sign_data(content,extra,self.keypair)
116 # The sig returned is either 64 or 63 bytes long (62 also possible I
117 # guess). Therefore we transmit size as 1 bytes and fill to 64 bytes.
118 lensig = chr(len(sig))
119 if len(sig) != self.MAX_ECDSA_ASN1_SIGSIZE:
120 # Note: this is not official ASN.1 padding. Also need to modify
121 # the header length for that I assume.
122 diff = self.MAX_ECDSA_ASN1_SIGSIZE-len(sig)
123 padding = '\x00' * diff
124 return [content,extra,lensig,sig,padding]
126 return [content,extra,lensig,sig]
128 def verify(self,piece,index):
129 """ A piece is valid if:
130 - the signature is correct,
131 - the seqnum % npieces == piecenr.
132 - the seqnum is no older than self.seqnum - npieces
133 @param piece The piece data as received from peer
134 @param index The piece number as received from peer
138 # Can we do this without memcpy?
139 #print >>sys.stderr,"ECDSAAuth: verify",len(piece)
140 extra = piece[-self.OUR_SIGSIZE:-self.OUR_SIGSIZE+self.EXTRA_SIZE]
141 lensig = ord(piece[-self.OUR_SIGSIZE+self.EXTRA_SIZE])
142 if lensig > self.MAX_ECDSA_ASN1_SIGSIZE:
143 print >>sys.stderr,"ECDSAAuth: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ failed piece",index,"lensig wrong",lensig
145 #print >>sys.stderr,"ECDSAAuth: verify lensig",lensig
146 diff = lensig-self.MAX_ECDSA_ASN1_SIGSIZE
148 sig = piece[-self.OUR_SIGSIZE+self.EXTRA_SIZE+self.LENGTH_SIZE:]
150 sig = piece[-self.OUR_SIGSIZE+self.EXTRA_SIZE+self.LENGTH_SIZE:diff]
151 content = piece[:-self.OUR_SIGSIZE]
153 print >>sys.stderr,"ECDSAAuth: verify piece",index,"sig",`sig`
154 print >>sys.stderr,"ECDSAAuth: verify dig",sha(content).hexdigest()
156 ret = ecdsa_verify_data_pubkeyobj(content,extra,self.pubkey,sig)
158 (seqnum, rtstamp) = self._decode_extra(piece)
161 print >>sys.stderr,"ECDSAAuth: verify piece",index,"seq",seqnum,"ts %.5f s" % rtstamp,"ls",lensig
163 mod = seqnum % self.get_npieces()
164 thres = self.seqnum - self.get_npieces()/2
166 print >>sys.stderr,"ECDSAAuth: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ failed piece",index,"old seqnum",seqnum,"<<",self.seqnum
169 print >>sys.stderr,"ECDSAAuth: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ failed piece",index,"expected",mod
171 elif self.startts is not None and rtstamp < self.startts:
172 print >>sys.stderr,"ECDSAAuth: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ failed piece",index,"older than oldest known ts",rtstamp,self.startts
175 self.seqnum = max(self.seqnum,seqnum)
176 if self.startts is None:
177 self.startts = rtstamp-300.0 # minus 5 min in case we read piece N+1 before piece N
178 print >>sys.stderr,"ECDSAAuth: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@: startts",self.startts
180 print >>sys.stderr,"ECDSAAuth: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ piece",index,"failed sig"
187 def get_content(self,piece):
188 return piece[:-self.OUR_SIGSIZE]
191 def get_seqnum(self,piece):
192 (seqnum, rtstamp) = self._decode_extra(piece)
195 def get_rtstamp(self,piece):
196 (seqnum, rtstamp) = self._decode_extra(piece)
199 def _decode_extra(self,piece):
200 extra = piece[-self.OUR_SIGSIZE:-self.OUR_SIGSIZE+self.EXTRA_SIZE]
201 if type(extra) == array.array:
202 extra = extra.tostring()
203 return struct.unpack('>Qd',extra)
206 def ecdsa_sign_data(plaintext,extra,ec_keypair):
207 digester = sha(plaintext)
208 digester.update(extra)
209 digest = digester.digest()
210 return ec_keypair.sign_dsa_asn1(digest)
212 def ecdsa_verify_data_pubkeyobj(plaintext,extra,pubkey,blob):
213 digester = sha(plaintext)
214 digester.update(extra)
215 digest = digester.digest()
216 return pubkey.verify_dsa_asn1(digest,blob)
221 class RSAAuthenticator(Authenticator):
222 """ Authenticator who places a RSA signature in the last part of a piece.
223 In particular, the sig consists of:
224 - an 8 byte sequence number
225 - an 8 byte real-time timestamp
226 - a variable-length RSA signature, length equivalent to the keysize in bytes
227 to give a total of 16+(keysize/8) bytes.
232 EXTRA_SIZE = SEQNUM_SIZE + RTSTAMP_SIZE
233 # put seqnum + rtstamp directly after content, so we calc the sig directly
234 # from the received buffer.
235 def our_sigsize(self):
236 return self.EXTRA_SIZE+self.rsa_sigsize()
238 def rsa_sigsize(self):
239 return len(self.pubkey)/8
241 def __init__(self,piecelen,npieces,keypair=None,pubkeypem=None):
242 Authenticator.__init__(self,piecelen,npieces)
243 self.keypair = keypair
244 if pubkeypem is not None:
245 #print >>sys.stderr,"ECDSAAuth: pubkeypem",`pubkeypem`
246 self.pubkey = RSA_pub_key_from_der(pubkeypem)
248 self.pubkey = self.keypair
249 self.contentblocksize = piecelen-self.our_sigsize()
252 def get_content_blocksize(self):
253 return self.contentblocksize
255 def sign(self,content):
256 rtstamp = time.time()
257 #print >>sys.stderr,"ECDSAAuth: sign: ts %.5f s" % rtstamp
259 extra = struct.pack('>Qd', self.seqnum,rtstamp)
262 sig = rsa_sign_data(content,extra,self.keypair)
263 return [content,extra,sig]
265 def verify(self,piece,index):
266 """ A piece is valid if:
267 - the signature is correct,
268 - the seqnum % npieces == piecenr.
269 - the seqnum is no older than self.seqnum - npieces
270 @param piece The piece data as received from peer
271 @param index The piece number as received from peer
275 # Can we do this without memcpy?
276 #print >>sys.stderr,"ECDSAAuth: verify",len(piece)
277 extra = piece[-self.our_sigsize():-self.our_sigsize()+self.EXTRA_SIZE]
278 sig = piece[-self.our_sigsize()+self.EXTRA_SIZE:]
279 content = piece[:-self.our_sigsize()]
281 # print >>sys.stderr,"RSAAuth: verify piece",index,"sig",`sig`
282 # print >>sys.stderr,"RSAAuth: verify dig",sha(content).hexdigest()
284 ret = rsa_verify_data_pubkeyobj(content,extra,self.pubkey,sig)
286 (seqnum, rtstamp) = self._decode_extra(piece)
289 print >>sys.stderr,"RSAAuth: verify piece",index,"seq",seqnum,"ts %.5f s" % rtstamp
291 mod = seqnum % self.get_npieces()
292 thres = self.seqnum - self.get_npieces()/2
294 print >>sys.stderr,"RSAAuth: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ failed piece",index,"old seqnum",seqnum,"<<",self.seqnum
297 print >>sys.stderr,"RSAAuth: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ failed piece",index,"expected",mod
299 elif self.startts is not None and rtstamp < self.startts:
300 print >>sys.stderr,"RSAAuth: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ failed piece",index,"older than oldest known ts",rtstamp,self.startts
303 self.seqnum = max(self.seqnum,seqnum)
304 if self.startts is None:
305 self.startts = rtstamp-300.0 # minus 5 min in case we read piece N+1 before piece N
306 print >>sys.stderr,"RSAAuth: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@: startts",self.startts
308 print >>sys.stderr,"RSAAuth: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ piece",index,"failed sig"
315 def get_content(self,piece):
316 return piece[:-self.our_sigsize()]
319 def get_seqnum(self,piece):
320 (seqnum, rtstamp) = self._decode_extra(piece)
323 def get_rtstamp(self,piece):
324 (seqnum, rtstamp) = self._decode_extra(piece)
327 def _decode_extra(self,piece):
328 extra = piece[-self.our_sigsize():-self.our_sigsize()+self.EXTRA_SIZE]
329 if type(extra) == array.array:
330 extra = extra.tostring()
331 return struct.unpack('>Qd',extra)
334 def rsa_sign_data(plaintext,extra,rsa_keypair):
335 digester = sha(plaintext)
336 digester.update(extra)
337 digest = digester.digest()
338 return rsa_keypair.sign(digest)
340 def rsa_verify_data_pubkeyobj(plaintext,extra,pubkey,sig):
341 digester = sha(plaintext)
342 digester.update(extra)
343 digest = digester.digest()
345 # The type of sig is array.array() at this point (why?), M2Crypto RSA verify
346 # will complain if it is not a string or Unicode object. Check if this is a
349 return pubkey.verify(digest,s)
357 class AuthStreamWrapper:
358 """ Wrapper around the stream returned by VideoOnDemand/MovieOnDemandTransporter
359 that strips of the signature info
362 def __init__(self,inputstream,authenticator):
363 self.inputstream = inputstream
364 self.buffer = StringIO()
365 self.authenticator = authenticator
366 self.piecelen = authenticator.get_piece_length()
367 self.last_rtstamp = None
369 def read(self,numbytes=None):
370 rawdata = self._readn(self.piecelen)
371 if len(rawdata) == 0:
374 content = self.authenticator.get_content(rawdata)
375 self.last_rtstamp = self.authenticator.get_rtstamp(rawdata)
376 if numbytes is None or numbytes < 0:
377 raise ValueError('Stream has unlimited size, read all not supported.')
378 elif numbytes < len(content):
379 # TODO: buffer unread data for next read
380 raise ValueError('reading less than piecesize not supported yet')
384 def get_generation_time(self):
385 """ Returns the time at which the last read piece was generated at the source. """
386 return self.last_rtstamp
388 def seek(self,pos,whence=os.SEEK_SET):
389 if pos == 0 and whence == os.SEEK_SET:
390 print >>sys.stderr,"authstream: seek: Ignoring seek 0 in live"
392 raise ValueError("authstream does not support seek")
395 self.inputstream.close()
398 return self.inputstream.available()
403 """ read exactly n bytes from inputstream, block if unavail """
406 data = self.inputstream.read(nwant)
410 self.buffer.write(data)
414 data = self.buffer.read(n)
420 class VariableReadAuthStreamWrapper:
421 """ Wrapper around AuthStreamWrapper that allows reading of variable
422 number of bytes. TODO: optimize whole stack of AuthWrapper,
423 MovieTransportWrapper, MovieOnDemandTransporter
426 def __init__(self,inputstream,piecelen):
427 self.inputstream = inputstream
429 self.piecelen = piecelen
431 def read(self,numbytes=None):
432 if numbytes is None or numbytes < 0:
433 raise ValueError('Stream has unlimited size, read all not supported.')
434 return self._readn(numbytes)
436 def get_generation_time(self):
437 """ Returns the time at which the last read piece was generated at the source. """
438 return self.inputstream.get_generation_time()
440 def seek(self,pos,whence=os.SEEK_SET):
441 return self.inputstream.seek(pos,whence=whence)
444 self.inputstream.close()
447 return self.inputstream.available()
450 def _readn(self,nwant):
451 """ read *at most* nwant bytes from inputstream """
453 if len(self.buffer) == 0:
454 # Must read fixed size blocks from authwrapper
455 data = self.inputstream.read(self.piecelen)
456 #print >>sys.stderr,"varread: Got",len(data),"want",nwant
461 lenb = len(self.buffer)
462 tosend = min(nwant,lenb)
465 #print >>sys.stderr,"varread: zero copy 2 lenb",lenb
469 #print >>sys.stderr,"varread: copy",tosend,"lenb",lenb
470 pre = self.buffer[0:tosend]
471 post = self.buffer[tosend:]
474 #print >>sys.stderr,"varread: Returning",len(pre)