diff --git a/doc/html/opendocument_8py_source.html b/doc/html/opendocument_8py_source.html index 2bcbb52..a2bc5ff 100644 --- a/doc/html/opendocument_8py_source.html +++ b/doc/html/opendocument_8py_source.html @@ -1093,7 +1093,7 @@
- + @@ -1104,7 +1104,7 @@ - + diff --git a/odf/odfmanifest.py b/odf/odfmanifest.py index 2e2c9b5..93d74ad 100644 --- a/odf/odfmanifest.py +++ b/odf/odfmanifest.py @@ -46,11 +46,16 @@ def __init__(self): self.manifest = {} # Tags - # FIXME: Also handle encryption data self.elements = { - (MANIFESTNS, 'file-entry'): (self.s_file_entry, self.donothing), + (MANIFESTNS, 'file-entry'): (self.s_file_entry, self.donothing), + (MANIFESTNS, 'encryption-data'): (self.e_file_entry, self.e_file_entry_close), + (MANIFESTNS, 'algorithm'): (self.e_alg_file_entry, self.donothing), + (MANIFESTNS, 'key-derivation'): (self.e_key_der_file_entry, self.donothing), + (MANIFESTNS, 'start-key-generation'): (self.e_key_gen_file_entry, self.donothing) } + self._encr_el_key = None + def handle_starttag(self, tag, method, attrs): method(tag,attrs) @@ -81,9 +86,46 @@ def donothing(self, tag, attrs=None): pass def s_file_entry(self, tag, attrs): - m = attrs.get((MANIFESTNS, 'media-type'),"application/octet-stream") + m = attrs.get((MANIFESTNS, 'media-type'),"") p = attrs.get((MANIFESTNS, 'full-path')) - self.manifest[p] = { 'media-type':m, 'full-path':p } + + self.manifest[p] = {'media-type': m, 'full-path': p} + + s = attrs.get((MANIFESTNS, 'size'), None) + # only encrypted entries have 'size' attr + # so there we assume that the next element will be encrypted-data + if s: + self.manifest[p]['size'] = s + self._encr_el_key = p + self.manifest[p]['encrypted-data'] = {} + + def e_file_entry(self, tag, attrs): + self.manifest[self._encr_el_key]['encrypted-data']['checksum-type'] = \ + attrs.get((MANIFESTNS, 'checksum-type'), "SHA1/1K") + self.manifest[self._encr_el_key]['encrypted-data']['checksum'] = attrs.get((MANIFESTNS, 'checksum'), "") + + def e_file_entry_close(self, tag): + self._encr_el_key = None + + def e_alg_file_entry(self, tag, attrs): + self.manifest[self._encr_el_key]['encrypted-data']['algorithm'] = { + 'algorithm-name': attrs.get((MANIFESTNS, 'algorithm-name'), "Blowfish CFB"), + 'initialisation-vector': attrs.get((MANIFESTNS, 'initialisation-vector'), "") + } + + def e_key_der_file_entry(self, tag, attrs): + self.manifest[self._encr_el_key]['encrypted-data']['key-derivation'] = { + 'key-derivation-name': attrs.get((MANIFESTNS, 'key-derivation-name'), "PBKDF2"), + 'key-size': attrs.get((MANIFESTNS, 'key-size'), "16"), + 'iteration-count': attrs.get((MANIFESTNS, 'iteration-count'), "1024"), + 'salt': attrs.get((MANIFESTNS, 'salt'), "") + } + + def e_key_gen_file_entry(self, tag, attrs): + self.manifest[self._encr_el_key]['encrypted-data']['start-key-generation'] = { + 'start-key-generation-name': attrs.get((MANIFESTNS, 'start-key-generation-name'), "SHA1"), + 'key-size': attrs.get((MANIFESTNS, 'key-size'), "20") + } #----------------------------------------------------------------------------- diff --git a/odf/opendocument.py b/odf/opendocument.py index 25d736b..9f4c7ba 100644 --- a/odf/opendocument.py +++ b/odf/opendocument.py @@ -24,9 +24,15 @@ __doc__="""Use OpenDocument to generate your documents.""" +import base64 +import hashlib import zipfile, time, uuid, sys, mimetypes, copy, os.path # to allow Python3 to access modules in the same path +import zlib + +from Crypto.Cipher import Blowfish, AES, DES3 + sys.path.append(os.path.dirname(__file__)) # using BytesIO provides a cleaner interface than StringIO @@ -798,6 +804,15 @@ def getElementsByType(self, elt): return result + +class OpenDocumentException(Exception): + pass + + +class OpenDocumentEncryptionException(OpenDocumentException): + pass + + # Convenience functions def OpenDocumentChart(): """ @@ -869,7 +884,8 @@ def OpenDocumentTextMaster(): doc.body.addElement(doc.text) return doc -def __loadxmlparts(z, manifest, doc, objectpath): + +def __loadxmlparts(z, manifest, doc, objectpath, password=None): """ Parses a document from its zipfile @param z an instance of zipfile.ZipFile @@ -895,7 +911,16 @@ def __loadxmlparts(z, manifest, doc, objectpath): from xml.sax._exceptions import SAXParseException ########################################################## try: - xmlpart = z.read(xmlfile).decode("utf-8") + xmlpart = z.read(xmlfile) + if 'encrypted-data' in manifest[xmlfile].keys(): + if not password: + raise OpenDocumentEncryptionException('Document is encrypted and password is not provided') + try: + xmlpart = __decrypt(xmlpart, manifest[xmlfile]['encrypted-data'], password, verify_checksum=False) + except OpenDocumentEncryptionException as err: + raise OpenDocumentEncryptionException('{}: {}'.format(xmlfile, err.message)) + xmlpart = xmlpart.decode("utf-8") + doc._parsing = xmlfile parser = make_parser() @@ -972,7 +997,7 @@ def __detectmimetype(zipfd, odffile): # Fall-through to last mechanism return u'application/vnd.oasis.opendocument.text' -def load(odffile): +def load(odffile, password=None): """ Load an ODF file into memory @param odffile unicode string: name of a file, or as an alternative, @@ -986,10 +1011,18 @@ def load(odffile): # Look in the manifest file to see if which of the four files there are manifestpart = z.read('META-INF/manifest.xml') manifest = manifestlist(manifestpart) - __loadxmlparts(z, manifest, doc, u'') - for mentry,mvalue in manifest.items(): + __loadxmlparts(z, manifest, doc, u'', password) + for mentry, mvalue in manifest.items(): if mentry[:9] == u"Pictures/" and len(mentry) > 9: - doc.addPicture(mvalue['full-path'], mvalue['media-type'], z.read(mentry)) + raw_pic = z.read(mentry) + if 'encrypted-data' in mvalue.keys(): + if not password: + raise OpenDocumentEncryptionException('Document is encrypted and password is not provided') + try: + raw_pic = __decrypt(raw_pic, mvalue['encrypted-data'], password, verify_checksum=True) + except OpenDocumentEncryptionException as err: + raise OpenDocumentEncryptionException('{}: {}'.format("filename", err.message)) + doc.addPicture(mvalue['full-path'], mvalue['media-type'], raw_pic) elif mentry == u"Thumbnails/thumbnail.png": doc.addThumbnail(z.read(mentry)) elif mentry in (u'settings.xml', u'meta.xml', u'content.xml', u'styles.xml'): @@ -1027,4 +1060,142 @@ def load(odffile): return doc + +# -------------------------------------- +# Encryption functions +# -------------------------------------- + +def __normalize_name(algorithm_name): + """ According to the OpenDocument docs (https://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part3.html), + the algorithms presented in the "encrypted-data" section can be specified in different ways: + + - As plain algorithm name: SHA1, SHA1/1K, Blowfish CFB + - As IRI: http://www.w3.org/2000/09/xmldsig#sha256 + - With MANIFEST prefix: urn:oasis:names:tc:opendocument:xmlns:manifest:1.0#sha256-1k + + This function tries to normalize algorithm name. + """ + assert isinstance(algorithm_name, type(u'')) + if algorithm_name.startswith('http') or algorithm_name.startswith(MANIFESTNS): + algorithm_name = algorithm_name.split('#')[1] + return algorithm_name.lower() + + +def __inflate(data): + decompress = zlib.decompressobj(-zlib.MAX_WBITS) + inflated = decompress.decompress(data) + inflated += decompress.flush() + return inflated + + +def __deflate(data, compress_level=9): + compress = zlib.compressobj(compress_level, zlib.DEFLATED, -zlib.MAX_WBITS, zlib.DEF_MEM_LEVEL, 0) + deflated = compress.compress(data) + deflated += compress.flush() + return deflated + + +def __make_key(password, algorithm, salt, deriv_iter_count, deriv_key_size): + """ Makes encryption key from password with addition derivation. + :param password: document's password. + :param algorithm: manifest:start-key-generation-name, the algorithm used to generate a start key from the + user password. Can be SHA1, SHA256. + :param salt: manifest:salt, base64-encoded salt + :param deriv_iter_count: manifest:iteration-count, the number of iterations used by the key derivation algorithm + to derive a key. + :param deriv_key_size: manifest:key-size, the length in octets of a key delivered by a key-developing algorithm. + """ + assert algorithm in ('sha1', 'sha256'), 'Only sha251 and sha1 are allowed' + sha_key = hashlib.new(algorithm, password.encode()).digest() + return hashlib.pbkdf2_hmac('sha1', sha_key, base64.b64decode(salt), int(deriv_iter_count), int(deriv_key_size)) + + +def __decrypt_data(algorithm, iv, derived_key, encrypted_data): + """ Decrypt data. + :param algorithm: manifest:algorithm-name, the algorithm and mode used to encrypt a file entry. Can be: + 1. An IRI listed in §5.2 of xmlenc-core (Block Encryption Algorithms): tripledes-cbc, aes128-cbc, aes192-cbc, + aes256-cbc + 2. Blowfish CFB: The Blowfish algorithm in 8-bit CFB mode. + 3. An IRI listed in §5.1 of xmlenc-core (Algorithm Identifiers and Implementation Requirements): NOT IMPLEMENTED + :param iv: manifest:initialisation-vector, base64-encrypted initialization vector used by the encryption algorithm. + :param derived_key: the key derived from a password. + :param encrypted_data: the encrypted data + """ + algorithm = __normalize_name(algorithm) + assert algorithm in ('blowfish cfb', 'blowfish', 'aes128-cbc', 'aes192-cbc', 'aes256-cbc', 'tripledes-cbc'), \ + 'Unknown algorithm: {}'.format(algorithm) + + iv = base64.b64decode(iv) + + if 'blowfish' in algorithm: + return Blowfish.new(key=derived_key, mode=Blowfish.MODE_CFB, IV=iv, segment_size=64).decrypt(encrypted_data) + elif 'aes' in algorithm: + return AES.new(key=derived_key, mode=AES.MODE_CBC, IV=iv).decrypt(encrypted_data) + elif 'tripledes' in algorithm: + return DES3.new(key=derived_key, mode=DES3.MODE_CBC, IV=iv).decrypt(encrypted_data) + + +def __decrypt(raw_data, manifest_data, password, verify_checksum=True): + # Get the encryption key from the password. + start_key_generation_alg = __normalize_name(manifest_data['start-key-generation']['start-key-generation-name']) + derived_key = __make_key(password, + algorithm=start_key_generation_alg, + salt=manifest_data['key-derivation']['salt'], + deriv_iter_count=manifest_data['key-derivation']['iteration-count'], + deriv_key_size=manifest_data['key-derivation']['key-size']) + + # Add padding if needed + raw_data = __append_padding(raw_data) + + # Decrypt data. + decrypted_data = __decrypt_data(algorithm=manifest_data['algorithm']['algorithm-name'], + iv=manifest_data['algorithm']['initialisation-vector'], + derived_key=derived_key, + encrypted_data=raw_data) + + # Verify the result with checksum + if verify_checksum and not verify(manifest_data['checksum'], manifest_data['checksum-type'], decrypted_data): + raise OpenDocumentEncryptionException("Checksum verification failed. Wrong password or corrupted document") + + # Inflate decrypted data. + try: + return __inflate(decrypted_data) + except zlib.error: + raise OpenDocumentEncryptionException("Wrong password or corrupted document") + + +def __append_padding(ciphertext, segment_size=64., block_size=8.): + assert isinstance(segment_size, float) + assert isinstance(block_size, float) + while not (len(ciphertext) % segment_size / block_size).is_integer(): + ciphertext += b'\x00' + return ciphertext + + +def verify(checksum, checksum_type, decrypted_data): + """ + Verify password. + + :param checksum: base64 encoded checksum from manifest + :param checksum_type: name of a digest algorithm that can be used to check password correctness. SHA1 or SHA256. + Can be: + 1. SHA1/1K: SHA1 algorithm applied to first 1024 bytes of the compressed unencrypted file. + 2. urn:oasis:names:tc:opendocument:xmlns:manifest:1.0#sha1-1k: The same as SHA1/1K. + 3. SHA1: The same as http://www.w3.org/2000/09/xmldsig#sha1. + 4. urn:oasis:names:tc:opendocument:xmlns:manifest:1.0#sha256-1k: SHA256 algorithm applied to first 1024 bytes + of the compressed unencrypted file. + :param decrypted_data: ... + """ + + checksum_type = __normalize_name(checksum_type) + assert checksum_type in ('sha1/1k', 'sha1-1k', 'sha1', 'sha256-1k'), \ + 'Wrong checksum algorithm: {}'.format(checksum_type) + + checksum = base64.b64decode(checksum) + + if 'sha256' in checksum_type: + return checksum == hashlib.sha256(decrypted_data[:1024]).digest() + elif 'sha1' in checksum_type: + return checksum == hashlib.sha1(decrypted_data[:1024]).digest()[:1024] + # vim: set expandtab sw=4 : diff --git a/setup.py b/setup.py index bbebf57..dd599e1 100644 --- a/setup.py +++ b/setup.py @@ -115,5 +115,5 @@ 'odfuserfield/odfuserfield', 'xml2odf/xml2odf'], data_files=datafiles, - install_requires=['defusedxml', ] + install_requires=['defusedxml', 'pycrypto'] ) diff --git a/tests/examples/aes_sample.odt b/tests/examples/aes_sample.odt new file mode 100644 index 0000000..cb508c2 Binary files /dev/null and b/tests/examples/aes_sample.odt differ diff --git a/tests/examples/blowfish_sample.odt b/tests/examples/blowfish_sample.odt new file mode 100644 index 0000000..2e697b4 Binary files /dev/null and b/tests/examples/blowfish_sample.odt differ diff --git a/tests/testenencrypted.py b/tests/testenencrypted.py new file mode 100644 index 0000000..13a9eb2 --- /dev/null +++ b/tests/testenencrypted.py @@ -0,0 +1,323 @@ +import unittest, sys, os +import zipfile + +from odf.odfmanifest import manifestlist +from odf.opendocument import load, OpenDocumentEncryptionException + +if sys.version_info[0] == 3: + unicode = str + + +class TestEncryption(unittest.TestCase): + BLOWFISH_SAMPLE = (os.path.join(os.path.abspath('tests/examples/blowfish_sample.odt')), '12345') + AES_SAMPLE = (os.path.join(os.path.abspath('tests/examples/aes_sample.odt')), 'qwerty') + TEMPFILE = 'tmpfile' + + def test_manifest_parsed_correctly(self): + self.maxDiff = None + blowsidh_sample_manifest = { + '/': { + 'full-path': u'/', + 'media-type': u'application/vnd.oasis.opendocument.presentation' + }, + 'Configurations2/': { + 'full-path': u'Configurations2/', + 'media-type': u'application/vnd.sun.xml.ui.configuration' + }, + 'settings.xml': { + 'full-path': u'settings.xml', + 'media-type': u'text/xml', + 'size': u'8942', + 'encrypted-data': { + 'checksum-type': u'SHA1/1K', + 'checksum': u'0BVONlG3NF6qj2+AsMWYaOov8IM=', + 'algorithm': { + 'algorithm-name': u'Blowfish CFB', + 'initialisation-vector': u'8hDMaxsAqzc=' + }, + 'key-derivation': { + 'key-derivation-name': u'PBKDF2', + 'key-size': u'16', + 'iteration-count': u'1024', + 'salt': u'4Ypn9QHpxnZO30R86dPtug==' + }, + 'start-key-generation': { + 'start-key-generation-name': u'SHA1', + 'key-size': u'20' + } + } + }, + 'Pictures/1000000000000150000000BC80E315B6.jpg': { + 'full-path': u'Pictures/1000000000000150000000BC80E315B6.jpg', + 'media-type': u'image/jpeg', + 'size': u'22164', + 'encrypted-data': { + 'checksum-type': u'SHA1/1K', + 'checksum': u'9AWsVbk/G4bqyqMhPUioQVaPLQg=', + 'algorithm': { + 'algorithm-name': u'Blowfish CFB', + 'initialisation-vector': u'v1ZiOAgBXm8=' + }, + 'key-derivation': { + 'key-derivation-name': u'PBKDF2', + 'key-size': u'16', + 'iteration-count': u'1024', + 'salt': u'W6eCPnE9vThx1an/MzzCWg==' + }, + 'start-key-generation': { + 'start-key-generation-name': u'SHA1', + 'key-size': u'20' + } + } + }, + 'Configurations2/accelerator/current.xml': { + 'full-path': u'Configurations2/accelerator/current.xml', + 'media-type': u'', + 'size': u'0', + 'encrypted-data': { + 'checksum-type': u'SHA1/1K', + 'checksum': u'aIk0hF8iBJyxRmiDLvoz1FATtrk=', + 'algorithm': { + 'algorithm-name': u'Blowfish CFB', + 'initialisation-vector': u'4DlNA+KL0IE=' + }, + 'key-derivation': { + 'key-derivation-name': u'PBKDF2', + 'key-size': u'16', + 'iteration-count': u'1024', + 'salt': u'VifaM7QswXHS8CkN5o51fQ==' + }, + 'start-key-generation': { + 'start-key-generation-name': u'SHA1', + 'key-size': u'20' + } + } + }, + 'content.xml': { + 'full-path': u'content.xml', + 'media-type': u'text/xml', + 'size': u'16347', + 'encrypted-data': { + 'checksum-type': u'SHA1/1K', + 'checksum': u'0rM17DiBsKx/9aspiGD1eFjeZUE=', + 'algorithm': { + 'algorithm-name': u'Blowfish CFB', + 'initialisation-vector': u'trBAXSe6bUA=' + }, + 'key-derivation': { + 'key-derivation-name': u'PBKDF2', + 'key-size': u'16', + 'iteration-count': u'1024', + 'salt': u't3FdHW1+iRMumMs4dmlfLA==' + }, + 'start-key-generation': { + 'start-key-generation-name': u'SHA1', + 'key-size': u'20' + } + } + }, + "styles.xml": { + 'full-path': u'styles.xml', + 'media-type': u'text/xml', + 'size': u'66035', + 'encrypted-data': { + 'checksum-type': u'SHA1/1K', + 'checksum': u'85p5eIsIbfFqE62ckasHZIcxTQs=', + 'algorithm': { + 'algorithm-name': u'Blowfish CFB', + 'initialisation-vector': u'nCmyklQDyqE=' + }, + 'key-derivation': { + 'key-derivation-name': u'PBKDF2', + 'key-size': u'16', + 'iteration-count': u'1024', + 'salt': u'4oH5Af40o5Ek950NCITYlA==' + }, + 'start-key-generation': { + 'start-key-generation-name': u'SHA1', + 'key-size': u'20' + } + } + }, + 'meta.xml': { + 'full-path': u'meta.xml', + 'media-type': u'text/xml', + 'size': u'1245', + 'encrypted-data': { + 'checksum-type': u'SHA1/1K', + 'checksum': u'+b0bqfMlpPJhgepExk3EIrVLZRY=', + 'algorithm': { + 'algorithm-name': u'Blowfish CFB', + 'initialisation-vector': u'ZYs05Y2GFfE=' + }, + 'key-derivation': { + 'key-derivation-name': u'PBKDF2', + 'key-size': u'16', + 'iteration-count': u'1024', + 'salt': u'txyMBw+SJzjnDhhLAj/w2g==' + }, + 'start-key-generation': { + 'start-key-generation-name': u'SHA1', + 'key-size': u'20' + } + } + } + } + aes_sample_manifest = { + "/": { + "full-path": u"/", + "media-type": u"application/vnd.oasis.opendocument.text" + }, + "Configurations2/": { + "full-path": u"Configurations2/", + "media-type": u"application/vnd.sun.xml.ui.configuration" + }, + "content.xml": { + "encrypted-data": { + "algorithm": { + "algorithm-name": u"http://www.w3.org/2001/04/xmlenc#aes256-cbc", + "initialisation-vector": u"LDs4+q6WNg4uIJWjOmhDTg==" + }, + "checksum": u"8eyCzGMpShjJ/NUsDVz8WxW4qIKqKZcQj/a/BFPnLR4=", + "checksum-type": u"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0#sha256-1k", + "key-derivation": { + "iteration-count": u"100000", + "key-derivation-name": u"PBKDF2", + "key-size": u"32", + "salt": u"WkW29GRMS4E21D9tN4ruyA==" + }, + "start-key-generation": { + "key-size": u"32", + "start-key-generation-name": u"http://www.w3.org/2000/09/xmldsig#sha256" + } + }, + "full-path": u"content.xml", + "media-type": u"text/xml", + "size": u"4815" + }, + "manifest.rdf": { + "encrypted-data": { + "algorithm": { + "algorithm-name": u"http://www.w3.org/2001/04/xmlenc#aes256-cbc", + "initialisation-vector": u"Ie49uwN6me5eR+wca4Vb0Q==" + }, + "checksum": u"DDVx7b20Jc3ZljYbTtRVsWm8wsLln7jSbZTqeZ51Xzk=", + "checksum-type": u"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0#sha256-1k", + "key-derivation": { + "iteration-count": u"100000", + "key-derivation-name": u"PBKDF2", + "key-size": u"32", + "salt": u"hUGs9XeHpfpK9lPi33LVeQ==" + }, + "start-key-generation": { + "key-size": u"32", + "start-key-generation-name": u"http://www.w3.org/2000/09/xmldsig#sha256" + } + }, + "full-path": u"manifest.rdf", + "media-type": u"application/rdf+xml", + "size": u"2000" + }, + "meta.xml": { + "encrypted-data": { + "algorithm": { + "algorithm-name": u"http://www.w3.org/2001/04/xmlenc#aes256-cbc", + "initialisation-vector": u"8fpmUzFBSwX4o8O2g7blrg==" + }, + "checksum": u"yFqAaEN+PnmXezNqj+Mbxn3XUREuxQgV4nBc6cqh3hY=", + "checksum-type": u"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0#sha256-1k", + "key-derivation": { + "iteration-count": u"100000", + "key-derivation-name": u"PBKDF2", + "key-size": u"32", + "salt": u"Uom2A33muYrdosXTyLKx1Q==" + }, + "start-key-generation": { + "key-size": u"32", + "start-key-generation-name": u"http://www.w3.org/2000/09/xmldsig#sha256" + } + }, + "full-path": u"meta.xml", + "media-type": u"text/xml", + "size": u"2002" + }, + "settings.xml": { + "encrypted-data": { + "algorithm": { + "algorithm-name": u"http://www.w3.org/2001/04/xmlenc#aes256-cbc", + "initialisation-vector": u"U500jUE6q3lkTsxllfFVPA==" + }, + "checksum": u"ttwW8LNKijWb6Fr6Db5ZYlIR6c8j3Mf8SRhuLUyOLj4=", + "checksum-type": u"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0#sha256-1k", + "key-derivation": { + "iteration-count": u"100000", + "key-derivation-name": u"PBKDF2", + "key-size": u"32", + "salt": u"xeh44N7pUOUbIaM74k0//g==" + }, + "start-key-generation": { + "key-size": u"32", + "start-key-generation-name": u"http://www.w3.org/2000/09/xmldsig#sha256" + } + }, + "full-path": u"settings.xml", + "media-type": u"text/xml", + "size": u"13726" + }, + "styles.xml": { + "encrypted-data": { + "algorithm": { + "algorithm-name": u"http://www.w3.org/2001/04/xmlenc#aes256-cbc", + "initialisation-vector": u"+KzPMu28KeBaqYjI+OJ7Ag==" + }, + "checksum": u"xm2kig2Hlf7GZxFQlmhyc+J7d4InqB17TIDwf8EqSJk=", + "checksum-type": u"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0#sha256-1k", + "key-derivation": { + "iteration-count": u"100000", + "key-derivation-name": u"PBKDF2", + "key-size": u"32", + "salt": u"h7e0tH5YtpIZiUcYlSgSmw==" + }, + "start-key-generation": { + "key-size": u"32", + "start-key-generation-name": u"http://www.w3.org/2000/09/xmldsig#sha256" + } + }, + "full-path": u"styles.xml", + "media-type": u"text/xml", + "size": u"13426" + } + } + + z = zipfile.ZipFile(self.BLOWFISH_SAMPLE[0]) + manifestpart = z.read('META-INF/manifest.xml') + manifest = manifestlist(manifestpart) + self.assertEqual(sorted(blowsidh_sample_manifest.items()), sorted(manifest.items())) + + z = zipfile.ZipFile(self.AES_SAMPLE[0]) + manifestpart = z.read('META-INF/manifest.xml') + manifest = manifestlist(manifestpart) + self.assertEqual(sorted(aes_sample_manifest.items()), sorted(manifest.items())) + + def test_decryption_with_right_password(self): + for sample in (self.BLOWFISH_SAMPLE, self.AES_SAMPLE): + load(sample[0], sample[1]) + return True + + def test_decryption_with_wrong_password(self): + for sample in (self.BLOWFISH_SAMPLE, self.AES_SAMPLE): + with self.assertRaises(OpenDocumentEncryptionException) as context: + load(sample[0], 'wrong_password') + + def test_decryption_and_save(self): + for sample in (self.BLOWFISH_SAMPLE, self.AES_SAMPLE): + doc = load(sample[0], sample[1]) + doc.save(self.TEMPFILE, False) + + load(self.TEMPFILE) + + return True + + def tearDown(self): + if os.path.exists(self.TEMPFILE): + os.remove(self.TEMPFILE)