|i+BPdZdZddlZddlZddlZddlmZmZddlm Z ddl Z ddl m Z  ddl Z n #e$rdZ YnwxYwejejejeZejejedZeejvrejdeddlmZmZmZmZmZdd lmZd Z ej!d Z"d Z#d Z$dZ%dZ&dZ'dZ(e'e(dddddgZ)dZ*dZ+ddZ,dS)a crypto.py Module to be used by other scripts and modules in oletools, that provides information on encryption in OLE files. Uses :py:mod:`msoffcrypto-tool` to decrypt if it is available. Otherwise decryption will fail with an ImportError. Encryption/Write-Protection can be realized in many different ways. They range from setting a single flag in an otherwise unprotected file to embedding a regular file (e.g. xlsx) in an EncryptedStream inside an OLE file. That means that (1) that lots of bad things are accesible even if no encryption password is known, and (2) even basic attributes like the file type can change by decryption. Therefore I suggest the following general routine to deal with potentially encrypted files:: def script_main_function(input_file, passwords, crypto_nesting=0, args): '''Wrapper around main function to deal with encrypted files.''' initial_stuff(input_file, args) result = None try: result = do_your_thing_assuming_no_encryption(input_file) if not crypto.is_encrypted(input_file): return result except Exception: if not crypto.is_encrypted(input_file): raise # we reach this point only if file is encrypted # check if this is an encrypted file in an encrypted file in an ... if crypto_nesting >= crypto.MAX_NESTING_DEPTH: raise crypto.MaxCryptoNestingReached(crypto_nesting, filename) decrypted_file = None try: decrypted_file = crypto.decrypt(input_file, passwords) if decrypted_file is None: raise crypto.WrongEncryptionPassword(input_file) # might still be encrypted, so call this again recursively result = script_main_function(decrypted_file, passwords, crypto_nesting+1, args) except Exception: raise finally: # clean up try: # (maybe file was not yet created) os.unlink(decrypted_file) except Exception: pass (Realized e.g. in :py:mod:`oletools.msodde`). That means that caller code needs another wrapper around its main function. I did try it another way first (a transparent on-demand unencrypt) but for the above reasons I believe this is the better way. Also, non-top-level-code can just assume that it works on unencrypted data and fail with an exception if encrypted data makes its work impossible. No need to check `if is_encrypted()` at the start of functions. .. seealso:: [MS-OFFCRYPTO] .. seealso:: https://github.com/nolze/msoffcrypto-tool crypto is part of the python-oletools package: http://www.decalage.info/python/oletools z0.60N)splitextisfile)mkstemp) OleFileIOz..)CryptoErrorBaseWrongEncryptionPasswordUnsupportedEncryptionErrorMaxCryptoNestingReachedCryptoLibNotImported) log_helper cryptocNttjdS)a  Enable logging for this module (disabled by default). For use by third-party libraries that import `crypto` as module. This will set the module-specific logger level to NOTSET, which means the main application controls the actual logging level. N)logsetLevelr NOTSET=C:\PYTHON\_runtimes\venv\Lib\site-packages\oletools/crypto.pyenable_loggingrsLL"#####rctr9tdd}d} t|tr1|j}|}|dnt|d}tj | |||S| S#t$r7}td|Yd}~Sd}~wwxYw#t$r7}td|Yd}~nd}~wwxYw |||n| n#t$r7}td|Yd}~n~d}~wwxYw# |||w| w#t$r7}td|Yd}~wd}~wwxYwxYw t|trt#|St%j|rt)|St |5}t#|cdddS#1swxYwYnE#t$r8}td||Yd}~nd}~wwxYwdS) a$ Determine whether document contains encrypted content. This should return False for documents that are just write-protected or signed or finalized. It should return True if ANY content of the file is encrypted and can therefore not be analyzed by other oletools modules without given a password. Exception: there are way to write-protect an office document by embedding it as encrypted stream with hard-coded standard password into an otherwise empty OLE file. From an office user point of view, this is no encryption, but regarding file structure this is encryption, so we return `True` for these. This should not raise exceptions needlessly. This implementation is rather simple: it returns True if the file contains streams with typical encryption names (c.f. [MS-OFFCRYPTO]). It does not test whether these streams actually contain data or whether the ole file structure contains the necessary references to these. It also checks the "well-known property" PIDSI_DOC_SECURITY if the SummaryInformation stream is accessible (c.f. [MS-OLEPS] 2.25.1) :param some_file: File name or an opened OleFileIO :type some_file: :py:class:`olefile.OleFileIO` or `str` :returns: True if (and only if) the file contains encrypted content z)Checking for encryption using msoffcryptoNrrbz"Ignoring error during clean up: {}zImsoffcrypto failed to parse file or determine whether it is encrypted: {}zCFailed to check {} for encryption ({}); assume it is not encrypted.F)check_msoffcryptordebug isinstancerfptellseekopen msoffcrypto OfficeFile is_encryptedclose Exceptionwarningformatinfo_is_encrypted_olezipfile is_zipfile_is_encrypted_zip) some_file file_handlefile_posexcoles rr"r"s:N =>>>  N)Y// 4'l &++--  ####"9d33 )+66CCEE N'$$X....%%'''' N N N @GGLLMMMMMMMM N & & & HH6 & & & & & & & & & & N'$$X....%%''' N N N @GGLLMMMMMMMM N  N'$$X....%%'''' N N N @GGLLMMMMMMMM N 9 i + + 0$Y// /  i ( ( 0$Y// / y ! ! *S$S)) * * * * * * * * * * * * * * * * * 999 !!' 3!7!7 9 9 9 9 9 9 9 99 5sA;D+CC D"-DD E&-EGEG",F G-G  GIH,I-HI I -H>9I>II #K."KK J</ K<KKKK L .LL ctdtj|d5}|d} ||d5}|td|jdddn #1swxYwY ddddS#t$r'}dt|vcYd}~cdddSd}~wwxYw#1swxYwYdS)z>Specialization of :py:func:`is_encrypted` for zip-based files.z#Checking for encryption in zip filerrNFcrypt) rrr)ZipFileinfolistrreadmin file_size RuntimeErrorstr)filenamezipper first_entryreaderrt_errs rr+r+sII3444 3 ' '*6oo''*  *[#.. <& CK$9::;;; < < < < < < < < < < < < < < < ********  * * *c&kk) ) ) ) ) )********  * **********s_C$ B0")B B0B B0B B00 C!:C C! C$C!!C$$C(+C(ctd|dr&|d}d|vr |ddzrdS|drtddS|dr|d sdS|d rd } |d g}|d t jd |dd}|dzdz }|r ||dSdS ||n#||wwxYwdS)z8Specialization of :py:func:`is_encrypted` for ole files.z#Checking for encryption in OLE filezSummaryInformationTEncryptionInfozfound stream EncryptionInfoEncryptedSummarySummaryInformation WordDocumentNr HrF) rrexists getproperties openstreamr7structunpackr#)r0 suminfo_datastreamtemp16 f_encrypteds rr(r(sII3444  zz*++(()ABB <  \$%7!%; 4  zz"## /000t zz$%% /00t zz.!! ^^^$455F KKOOO]3 A77:F!F?q0K ! "! ! ! " 5s A#E!!E:VelvetSweatshopz/01Hannes Ruescher/011231234123451234564321c0ttdS)zERaise a :py:class:`CryptoLibNotImported` if msoffcrypto not imported.N)r r rrr_check_msoffcryptor\2s"$$$rctduS)z:Return `True` iff :py:mod:`msoffcrypto` could be imported.N)r rrrrr8s d ""rc tt|tr|f}n |st}d|vrd|d<d|vrt |d|d<d|d<d}t |d5} t j|}ny#t$rl}d t|vrPt d d tj j d krt|tdnYd}~nd}~wwxYw|s"t#d||D]}t d|d}d} d} ||t)di|\}}t+j|d} d}|| | d} n#t$rtt dd | r| n|rt+j||r#t3|rt+j|d}YwxYwdddn #1swxYwYt d|S)a Try to decrypt an encrypted file This function tries to decrypt the given file using a given set of passwords. If no password is given, tries the standard password for write protection. Creates a file with decrypted data whose file name is returned. If the decryption fails, None is returned. :param str filename: path to an ole file on disc :param passwords: list/set/tuple/... of passwords or a single password or None :type passwords: iterable or str or None :param temp_file_args: arguments for :py:func:`tempfile.mkstemp` e.g., `dirname` or `prefix`. `suffix` will default to suffix of input `filename`, `prefix` defaults to `oletools-decrypt-`; `text` will be ignored :returns: name of the decrypted temporary file (type str) or `None` :raises: :py:class:`ImportError` if :py:mod:`msoffcrypto-tools` not found :raises: :py:class:`ValueError` if the given file is not encrypted prefixzoletools-decrypt-suffixrCFtextNrzUnrecognized file formatzCaught exceptionT)exc_inforIz4raise UnsupportedEncryptionError(filename) from Nonez%Given input file {} is not encrypted!z$Trying to decrypt with password {!r})passwordwbzFailed to decryptzAll passwords failedr)r\rr;DEFAULT_PASSWORDSrrr r!r$rrsys version_infomajorr execr" ValueErrorr&load_keyrosfdopendecryptr#runlink) r< passwordstemp_file_args decrypt_filer? crypto_filer/rcwrite_descriptor write_handles rrnrn=s>*)S!!&M &% ~%%#6x ~%%#+H#5#5a#8x "N6L h  /$ %088KK   )SXX55 ,t <<<#)Q..4X>>>OPPPPQPPPP '')) 0D$fX..00 0" $ $H II<CCHMM N N N# LL $$$h$777291J1J>1J1J. ,!y)94@@ #' ##L111""$$$#  $ $ $ - === / &&((((%/H-...,F<$8$8,Il+++#  $K/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$bII$%%% sd-I"/BI" C:A"C50I"5C::A1I",A'GI"A:II"II""I&)I&)N)-__doc__ __version__rfrOrlos.pathrrtempfilerr)olefilerr ImportErrorpathnormpathabspathdirname__file___thismodule_dirjoin _parent_dirinsertoletools.common.errorsrrr r r oletools.common.log_helperr MAX_NESTING_DEPTHget_or_create_silent_loggerrrr"r+r(EXCEL_TRANSPARENT_PASSWORDPOWERPOINT_TRANSPARENT_PASSWORDrer\rrnrrrrsZ==D $$$$$$$$KKK'""27??27??83L3L#M#MNNgrw||OTBBCC chHOOA{###NNNNNNNNNNNNNN111111  -j,X66 $ $ $KKK\ * * *)))`/ #:01PFGXv?%%% ### WWWWWWs /99