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: |