Subversion Repositories pub

Compare Revisions

Ignore whitespace Rev 594 → Rev 595

/relevation/branches/1.3/relevation.py
72,12 → 72,15
sys.stderr.write('Either PyCrypto or cryptopy is required\n')
raise
 
RELEASE=not True
__author__ = 'Toni Corvera'
__date__ = '$Date$'
__revision__ = '$Rev$'
__version_info__ = ( 1, 3 ) #, 0 ) # Note: For x.y.0, only x and y are kept
if not RELEASE:
import traceback
__version_info__ += ( '0-pre1', )
__version__ = '.'.join(map(str, __version_info__))
RELEASE=not True
 
# These are pseudo-standardized exit codes, in Linux (*NIX?) they are defined
#+in the header </usr/include/sysexits.h> and available as properties of 'os'
110,16 → 113,25
MODE_OR='or'
 
# Errors
class Errors(object):
class Error(Exception):
def __str__(self):
return self.msg
class DecryptError(Error):
def __init__(self, msg = 'Failed to decrypt data. Wrong password?'):
self.msg = msg
class DataFormatError(Error):
def __init__(self, msg = 'Incorrect data format'):
self.msg = msg
class RlvError(Exception):
def __str__(self):
return self.msg
class DecryptError(RlvError):
exitCode = os.EX_DATAERR
def __init__(self, msg = 'Failed to decrypt data. Wrong password?'):
self.msg = msg
class DecompressError(RlvError):
exitCode = os.EX_DATAERR
def __init__(self, msg = 'Failed to decompress data.'):
self.msg = msg
class DataFormatError(RlvError):
exitCode = os.EX_DATAERR
def __init__(self, msg = 'Incorrect data format'):
self.msg = msg
class DataVersionError(RlvError):
exitCode = os.EX_DATAERR
def __init__(self, msg = 'Data format version not supported'):
self.msg = msg
 
def printe(s):
' Print to stderr '
387,7 → 399,7
'''
# Minimum length of header
if len(cipher_text) < 28:
raise Errors.DataFormatError
raise DataFormatError
# Key <= Padded password
key = password
key += (chr(0) * (32 - len(password)))
397,7 → 409,7
cipher_text = cipher_text[28:]
# Input strings for decrypt must be a multiple of 16 in length
if not self.validate_cipher_length(cipher_text):
raise Errors.DataFormatError
raise DataFormatError
# Decrypt data, CBC mode
return self._aes_decrypt_cbc(key, iv, cipher_text)
 
406,10 → 418,13
cleardata_gz = self._decrypt_compressed_data(password, data)
# Validate padding for decompression
if not self.validate_compressed_padding(cleardata_gz):
raise Errors.DataFormatError
raise DataFormatError
# Decompress actual data (15 is wbits [ref3] DON'T CHANGE, 2**15 is the (initial) buf size)
padlen = ord(cleardata_gz[-1])
return zlib.decompress(cleardata_gz[:-padlen], 15, 2**15)
try:
return zlib.decompress(cleardata_gz[:-padlen], 15, 2**15)
except zlib.error:
raise DecompressError
 
class DataReaderV2(_DataReaderBase):
''' Data reading for Revelation data files in the new format.
424,7 → 439,7
def _decrypt_compressed_data(self, password, cipher_text):
# Minimum length of header
if len(cipher_text) < 36:
raise Errors.DataFormatError
raise DataFormatError
salt = cipher_text[12:20]
iv = cipher_text[20:36]
key = PBKDF2.PBKDF2(password, salt, iterations=12000).read(32)
431,7 → 446,7
# Skip encryption header
cipher_text = cipher_text[36:]
if not self.validate_cipher_length(cipher_text):
raise Errors.DataFormatError
raise DataFormatError
# Decrypt data (CBC)
decrypted = self._aes_decrypt_cbc(key, iv, cipher_text)
sha256hash = decrypted[0:32]
439,7 → 454,7
decrypted = decrypted[32:]
# Validate hash
if sha256hash != hashlib.sha256(decrypted).digest():
raise Errors.DecryptError
raise DecryptError
return decrypted
 
def get_xml(self, data, password):
447,20 → 462,23
cleardata_gz = self._decrypt_compressed_data(password, data)
# Validate padding for decompression
if not self.validate_compressed_padding(cleardata_gz):
raise Errors.DataFormatError
raise DataFormatError
# Decompress
padlen = ord(cleardata_gz[-1])
return zlib.decompress(cleardata_gz[:-padlen])
try:
return zlib.decompress(cleardata_gz[:-padlen])
except zlib.error:
raise DecompressError
 
class DataReader(object):
''' Interface to read Revelation's data files '''
def __init__(self, filename):
''' DataReader(str)
Loads file data and checks file format and data version for compatibility
''' Loads file data and checks file format and data version for compatibility
DataReader(str)
 
raises IOError If filename not readable
raises IOError If filename not in Revelation format
raises IOError If filename not in a supported data format version
raises IOError If 'filename' not readable
raises DataFormatError If 'filename' not in Revelation format
raises DataVersionError If filename not in a supported data format version
'''
self._impl = None
self._data = None
477,13 → 495,16
f.close()
self._check_header()
def _check_header(self):
''' Checks the file header for compatibility
''' Checks the file header for compatibility.
_check_header() -> None
 
raises DataFormatError If the file isn't a Revelation data file
raises DataVersionError If the data format is in an unsupported version
'''
header = self._data[0:12]
magic = header[0:4]
if magic != "rvl\x00":
raise IOError('File \'%s\' not in the correct format' % self._filename)
raise DataFormatError
data_version = header[4]
app_version = header[6:9]
if data_version == '\x01':
491,7 → 512,7
elif data_version == '\x02':
self._impl = DataReaderV2()
else:
raise IOError('File \'%s\' is in a newer, unsupported, data format' % self._filename)
raise DataVersionError
def get_xml(self, password):
''' Decrypt and decompress file data
get_xml(str) -> str
632,9 → 653,11
if __name__ == '__main__':
try:
main(sys.argv[1:])
except zlib.error:
printe('Failed to decompress decrypted data. Wrong password?')
sys.exit(os.EX_DATAERR)
except RlvError as e:
printe('Error: %s' % e.msg)
if not RELEASE:
traceback.print_exc()
sys.exit(e.exitCode)
except etree.XMLSyntaxError as e:
printe('XML parsing error')
if not RELEASE:
/relevation/branches/1.3/CHANGELOG
4,6 → 4,7
- Check file magic [#230] and reject unsupported data formats
- Support the new data file format [#228]
- Added extra checks for data integrity
- Actually import traceback in pre-releases
 
1.2.1 (2013-11-05):
- Minimal GUI Fixes: