diff --git a/pappyproxy/compress.py b/pappyproxy/compress.py index 28d1e5d..427497c 100644 --- a/pappyproxy/compress.py +++ b/pappyproxy/compress.py @@ -7,11 +7,10 @@ import pappyproxy import zipfile import tarfile -# This is a gross hack, please help -bz2 = None try: import bz2 -except: +except ImportError: + bz2 = None print "BZ2 not installed on your system" from base64 import b64encode, b64decode @@ -44,12 +43,10 @@ class Compress(object): """ try: zf = zipfile.ZipFile(self.zip_archive, mode="a") - project_files = self.config.get_project_files() - for pf in project_files: - zf.write(pf) + zf.write(self.config.crypt_dir) zf.close() except e: - raise PappyException("Error creating the zipfile", e) + raise PappyException("Error creating the zipfile. Error: ", e) pass def unzip_project(self): @@ -73,11 +70,8 @@ class Compress(object): def tar_project(self): archive = tarfile.open(self.bz2_archive, 'w:bz2') - project_files = self.config.get_project_files() - # Read files line by line to accomodate larger files, e.g. the project database - for pf in project_files: - archive.add(pf) + archive.add(self.config.crypt_dir) archive.close() def untar_project(self): @@ -86,5 +80,5 @@ class Compress(object): try: with tarfile.open(self.bz2_archive, "r:bz2") as archive: archive.extractall() - except e: + except tarfile.ExtractError, e: raise PappyException("Project archive contents corrupted. Error: ", e) diff --git a/pappyproxy/config.py b/pappyproxy/config.py index 3d82fba..f2b71a6 100644 --- a/pappyproxy/config.py +++ b/pappyproxy/config.py @@ -99,13 +99,13 @@ class PappyConfig(object): The dictionary from ~/.pappy/global_config.json. It contains settings for Pappy that are specific to the current computer. Avoid putting settings here, especially if it involves specific projects. - + .. data: archive Project archive compressed as a ``tar.bz2`` archive if libraries available on the system, otherwise falls back to zip archive. - :Default: 'project.archive' + :Default: ``project.archive`` .. data: crypt_dir @@ -115,14 +115,20 @@ class PappyConfig(object): Compressed as a tar.bz2 archive if libraries available on the system, otherwise falls back to zip. - :Default: 'crypt' + :Default: ``crypt`` .. data: crypt_file Encrypted archive of the temporary working directory ``crypt_dir``. Compressed as a tar.bz2 archive if libraries available on the system, otherwise falls back to zip. - :Default: 'project.crypt' + :Default: ``project.crypt`` + + .. data: crypt_session + + Boolean variable to determine whether pappy started in crypto mode + + :Default: False .. data: salt_file @@ -161,8 +167,9 @@ class PappyConfig(object): self.global_config_dict = {} self.archive = 'project.archive' - self.crypt_dir = os.path.join(os.getcwd(), 'crypt') + self.crypt_dir = 'crypt' self.crypt_file = 'project.crypt' + self.crypt_session = False self.salt_file = 'project.salt' def get_default_config(self): diff --git a/pappyproxy/console.py b/pappyproxy/console.py index 7701da2..b670ba2 100644 --- a/pappyproxy/console.py +++ b/pappyproxy/console.py @@ -42,13 +42,15 @@ class ProxyCmd(cmd2.Cmd): self._cmds = {} self._aliases = {} - atexit.register(self.save_histfile) - readline.set_history_length(self.session.config.histsize) - if os.path.exists('cmdhistory'): - if self.session.config.histsize != 0: - readline.read_history_file('cmdhistory') - else: - os.remove('cmdhistory') + # Only read and save history when not in crypto mode + if not self.session.config.crypt_session: + atexit.register(self.save_histfile) + readline.set_history_length(self.session.config.histsize) + if os.path.exists('cmdhistory'): + if self.session.config.histsize != 0: + readline.read_history_file('cmdhistory') + else: + os.remove('cmdhistory') cmd2.Cmd.__init__(self, *args, **kwargs) @@ -110,10 +112,12 @@ class ProxyCmd(cmd2.Cmd): raise AttributeError(attr) def save_histfile(self): - # Write the command to the history file - if self.session.config.histsize != 0: - readline.set_history_length(self.session.config.histsize) - readline.write_history_file('cmdhistory') + # Only write to file if not in crypto mode + if not self.session.config.crypt_session: + # Write the command to the history file + if self.session.config.histsize != 0: + readline.set_history_length(self.session.config.histsize) + readline.write_history_file('cmdhistory') def get_names(self): # Hack to get cmd to recognize do_/etc functions as functions for things diff --git a/pappyproxy/crypto.py b/pappyproxy/crypto.py index d8fa3d2..14a96f1 100644 --- a/pappyproxy/crypto.py +++ b/pappyproxy/crypto.py @@ -27,58 +27,68 @@ class Crypto(object): """ Compress and encrypt the project files, deleting clear-text files afterwards """ + + # Leave the crypto working directory + os.chdir('../') # Get the password and salt, then derive the key self.crypto_ramp_up() - - # Instantiate the crypto module - fern = Fernet(self.key) - - # Create project archive and crypto archive + self.compressor.compress_project() - archive_file = open(self.archive, 'rb').read() + + # Create project and crypto archive + archive_file = open(self.archive, 'rb') archive_crypt = open(self.config.crypt_file, 'wb') # Encrypt the archive read as a bytestring - crypt_token = fern.encrypt(archive_file) + fern = Fernet(self.key) + crypt_token = fern.encrypt(archive_file.read()) archive_crypt.write(crypt_token) + + # Store the salt for the next decryption + self.create_salt_file() + + archive_file.close() + archive_crypt.close() # Delete clear-text files - # delete_clear_files() + self.delete_clear_files() - # Leave crypto working directory - os.chdir('../') def decrypt_project(self): """ Decrypt and decompress the project files """ - # Get the password and salt, then derive the key - self.crypto_ramp_up() + # If project hasn't been encrypted before, setup crypt working directory + crypt_fp = os.path.join(os.getcwd(), self.config.crypt_file) + if not os.path.isfile(crypt_fp): + os.mkdir(self.config.crypt_dir) - crypto_path = self.config.crypt_dir + project_files = self.config.get_project_files() + for pf in project_files: + shutil.copy2(pf, self.config.crypt_dir) + os.chdir(self.config.crypt_dir) - if not os.path.isdir(crypto_path): - os.mkdir(crypto_path) - - if os.path.isfile(self.config.crypt_file): - # Derive the key - key = self.crypto_ramp_up() - fern = Fernet(key) + # Otherwise, decrypt and decompress the project + else: + self.crypto_ramp_up() + fern = Fernet(self.key) # Decrypt the project archive - archive_crypt = open(self.config.crypt_file, 'rb') + archive_crypt = open(self.config.crypt_file, 'rb').read() + archive_file = open(self.config.archive, 'wb') archive = fern.decrypt(archive_crypt) + + archive_file.write(archive) + archive_file.close() - shutil.move(archive, crypto_path) - os.chdir(crypto_path) self.compressor.decompress_project() - else: - project_files = self.config.get_project_files() - for pf in project_files: - shutil.copy2(pf, crypto_path) - os.chdir(crypto_path) + + # Force generation of new salt and crypt archive + self.delete_crypt_files() + + os.chdir(self.config.crypt_dir) def crypto_ramp_up(self): if not self.password: @@ -86,56 +96,36 @@ class Crypto(object): self.set_salt() self.derive_key() - def delete_clear_files(self): - """ - Deletes all clear-text files left in the project directory. - """ - project_files = self.config.get_project_files() - for pf in project_files: - os.remove(pf) - - def delete_crypt_files(self): + def get_password(self): """ - Deletes all encrypted-text files in the project directory. - Forces generation of new salt after opening and closing the project. - Adds security in the case of a one-time compromise of the system. + Retrieve password from the user. Raise an exception if the + password is not capable of utf-8 encoding. """ - os.remove(self.config.salt_file) - os.remove(self.config.crypt_file) - - def create_salt_file(self): - salt_file = open(self.config.salt_file, 'wb') - - if not self.config.salt: - self.set_salt() - - salt_file.write(self.config.salt) - salt_file.close() - - def set_salt_from_file(self): + encoded_passwd = "" try: - salt_file = open(self.config.salt_file, 'rb') - self.salt = salt_file.readline().strip() + passwd = raw_input("Enter a password: ") + self.password = passwd.encode("utf-8") except: - raise PappyException("Unable to read project.salt") - + raise PappyException("Invalid password, try again") + def set_salt(self): if os.path.isfile(self.config.salt_file): self.set_salt_from_file() else: self.salt = os.urandom(16) - def get_password(self): - """ - Retrieve password from the user. Raise an exception if the - password is not capable of utf-8 encoding. - """ - encoded_passwd = "" + def set_salt_from_file(self): try: - passwd = raw_input("Enter a password: ") - self.password = passwd.encode("utf-8") + salt_file = open(self.config.salt_file, 'rb') + self.salt = salt_file.readline().strip() except: - raise PappyException("Invalid password, try again") + raise PappyException("Unable to read project.salt") + + def create_salt_file(self): + salt_file = open(self.config.salt_file, 'wb') + + salt_file.write(self.salt) + salt_file.close() def derive_key(self): """ @@ -173,3 +163,19 @@ class Crypto(object): raise PappyException("Scrypt failed with type error: ", e) except scrypt.error, e: raise PappyException("Scrypt failed with internal error: ", e) + + def delete_clear_files(self): + """ + Deletes all clear-text files left in the project directory. + """ + shutil.rmtree(self.config.crypt_dir) + os.remove(self.config.archive) + + def delete_crypt_files(self): + """ + Deletes all encrypted-text files in the project directory. + Forces generation of new salt after opening and closing the project. + Adds security in the case of a one-time compromise of the system. + """ + #os.remove(self.config.salt_file) + os.remove(self.config.crypt_file) diff --git a/pappyproxy/pappy.py b/pappyproxy/pappy.py index 5f92190..011f685 100755 --- a/pappyproxy/pappy.py +++ b/pappyproxy/pappy.py @@ -160,9 +160,8 @@ class PappySession(object): print 'Deleting temporary datafile' os.remove(self.config.datafile) - # If currently in the crypt directory, - # encrypt the project, delete clear files - if os.getcwd() == self.config.crypt_dir: + # Encrypt the project when in crypto mode + if self.config.crypt_session: self.encrypt() def parse_args(): @@ -215,6 +214,8 @@ def main(): signal.signal(signal.SIGINT, inturrupt_handler) if settings['crypt']: + pappy_config.crypt_file = settings['crypt'] + pappy_config.crypt_session = True session.decrypt() conf_settings = pappy_config.load_from_file('./config.json') pappy_config.global_load_from_file()