Worked out bugs for crypto mode, it works now!

Crypto mode for pappy now works. Still need to work out the kinks
for not supplying a project file to `pappy -c`, but when supplied
encryption and decryption work.
This commit is contained in:
Nich 2016-03-28 21:12:19 +00:00
parent a3cb5f13ed
commit bf914e6f86
5 changed files with 114 additions and 102 deletions

View file

@ -7,11 +7,10 @@ import pappyproxy
import zipfile import zipfile
import tarfile import tarfile
# This is a gross hack, please help
bz2 = None
try: try:
import bz2 import bz2
except: except ImportError:
bz2 = None
print "BZ2 not installed on your system" print "BZ2 not installed on your system"
from base64 import b64encode, b64decode from base64 import b64encode, b64decode
@ -44,12 +43,10 @@ class Compress(object):
""" """
try: try:
zf = zipfile.ZipFile(self.zip_archive, mode="a") zf = zipfile.ZipFile(self.zip_archive, mode="a")
project_files = self.config.get_project_files() zf.write(self.config.crypt_dir)
for pf in project_files:
zf.write(pf)
zf.close() zf.close()
except e: except e:
raise PappyException("Error creating the zipfile", e) raise PappyException("Error creating the zipfile. Error: ", e)
pass pass
def unzip_project(self): def unzip_project(self):
@ -73,11 +70,8 @@ class Compress(object):
def tar_project(self): def tar_project(self):
archive = tarfile.open(self.bz2_archive, 'w:bz2') 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 archive.add(self.config.crypt_dir)
for pf in project_files:
archive.add(pf)
archive.close() archive.close()
def untar_project(self): def untar_project(self):
@ -86,5 +80,5 @@ class Compress(object):
try: try:
with tarfile.open(self.bz2_archive, "r:bz2") as archive: with tarfile.open(self.bz2_archive, "r:bz2") as archive:
archive.extractall() archive.extractall()
except e: except tarfile.ExtractError, e:
raise PappyException("Project archive contents corrupted. Error: ", e) raise PappyException("Project archive contents corrupted. Error: ", e)

View file

@ -105,7 +105,7 @@ class PappyConfig(object):
Project archive compressed as a ``tar.bz2`` archive if libraries available on the system, Project archive compressed as a ``tar.bz2`` archive if libraries available on the system,
otherwise falls back to zip archive. otherwise falls back to zip archive.
:Default: 'project.archive' :Default: ``project.archive``
.. data: crypt_dir .. data: crypt_dir
@ -115,14 +115,20 @@ class PappyConfig(object):
Compressed as a tar.bz2 archive if libraries available on the system, Compressed as a tar.bz2 archive if libraries available on the system,
otherwise falls back to zip. otherwise falls back to zip.
:Default: 'crypt' :Default: ``crypt``
.. data: crypt_file .. data: crypt_file
Encrypted archive of the temporary working directory ``crypt_dir``. Compressed as a 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. 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 .. data: salt_file
@ -161,8 +167,9 @@ class PappyConfig(object):
self.global_config_dict = {} self.global_config_dict = {}
self.archive = 'project.archive' self.archive = 'project.archive'
self.crypt_dir = os.path.join(os.getcwd(), 'crypt') self.crypt_dir = 'crypt'
self.crypt_file = 'project.crypt' self.crypt_file = 'project.crypt'
self.crypt_session = False
self.salt_file = 'project.salt' self.salt_file = 'project.salt'
def get_default_config(self): def get_default_config(self):

View file

@ -42,6 +42,8 @@ class ProxyCmd(cmd2.Cmd):
self._cmds = {} self._cmds = {}
self._aliases = {} self._aliases = {}
# Only read and save history when not in crypto mode
if not self.session.config.crypt_session:
atexit.register(self.save_histfile) atexit.register(self.save_histfile)
readline.set_history_length(self.session.config.histsize) readline.set_history_length(self.session.config.histsize)
if os.path.exists('cmdhistory'): if os.path.exists('cmdhistory'):
@ -110,6 +112,8 @@ class ProxyCmd(cmd2.Cmd):
raise AttributeError(attr) raise AttributeError(attr)
def save_histfile(self): def save_histfile(self):
# Only write to file if not in crypto mode
if not self.session.config.crypt_session:
# Write the command to the history file # Write the command to the history file
if self.session.config.histsize != 0: if self.session.config.histsize != 0:
readline.set_history_length(self.session.config.histsize) readline.set_history_length(self.session.config.histsize)

View file

@ -28,57 +28,67 @@ class Crypto(object):
Compress and encrypt the project files, deleting clear-text files afterwards 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 # Get the password and salt, then derive the key
self.crypto_ramp_up() self.crypto_ramp_up()
# Instantiate the crypto module
fern = Fernet(self.key)
# Create project archive and crypto archive
self.compressor.compress_project() 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') archive_crypt = open(self.config.crypt_file, 'wb')
# Encrypt the archive read as a bytestring # 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) archive_crypt.write(crypt_token)
# Delete clear-text files # Store the salt for the next decryption
# delete_clear_files() self.create_salt_file()
archive_file.close()
archive_crypt.close()
# Delete clear-text files
self.delete_clear_files()
# Leave crypto working directory
os.chdir('../')
def decrypt_project(self): def decrypt_project(self):
""" """
Decrypt and decompress the project files Decrypt and decompress the project files
""" """
# Get the password and salt, then derive the key # If project hasn't been encrypted before, setup crypt working directory
self.crypto_ramp_up() 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
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)
# Decrypt the project archive
archive_crypt = open(self.config.crypt_file, 'rb')
archive = fern.decrypt(archive_crypt)
shutil.move(archive, crypto_path)
os.chdir(crypto_path)
self.compressor.decompress_project()
else:
project_files = self.config.get_project_files() project_files = self.config.get_project_files()
for pf in project_files: for pf in project_files:
shutil.copy2(pf, crypto_path) shutil.copy2(pf, self.config.crypt_dir)
os.chdir(crypto_path) os.chdir(self.config.crypt_dir)
# 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').read()
archive_file = open(self.config.archive, 'wb')
archive = fern.decrypt(archive_crypt)
archive_file.write(archive)
archive_file.close()
self.compressor.decompress_project()
# Force generation of new salt and crypt archive
self.delete_crypt_files()
os.chdir(self.config.crypt_dir)
def crypto_ramp_up(self): def crypto_ramp_up(self):
if not self.password: if not self.password:
@ -86,45 +96,6 @@ class Crypto(object):
self.set_salt() self.set_salt()
self.derive_key() 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):
"""
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)
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):
try:
salt_file = open(self.config.salt_file, 'rb')
self.salt = salt_file.readline().strip()
except:
raise PappyException("Unable to read project.salt")
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): def get_password(self):
""" """
Retrieve password from the user. Raise an exception if the Retrieve password from the user. Raise an exception if the
@ -137,6 +108,25 @@ class Crypto(object):
except: except:
raise PappyException("Invalid password, try again") 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 set_salt_from_file(self):
try:
salt_file = open(self.config.salt_file, 'rb')
self.salt = salt_file.readline().strip()
except:
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): def derive_key(self):
""" """
Derive a key sufficient for use as a cryptographic key Derive a key sufficient for use as a cryptographic key
@ -173,3 +163,19 @@ class Crypto(object):
raise PappyException("Scrypt failed with type error: ", e) raise PappyException("Scrypt failed with type error: ", e)
except scrypt.error, e: except scrypt.error, e:
raise PappyException("Scrypt failed with internal 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)

View file

@ -160,9 +160,8 @@ class PappySession(object):
print 'Deleting temporary datafile' print 'Deleting temporary datafile'
os.remove(self.config.datafile) os.remove(self.config.datafile)
# If currently in the crypt directory, # Encrypt the project when in crypto mode
# encrypt the project, delete clear files if self.config.crypt_session:
if os.getcwd() == self.config.crypt_dir:
self.encrypt() self.encrypt()
def parse_args(): def parse_args():
@ -215,6 +214,8 @@ def main():
signal.signal(signal.SIGINT, inturrupt_handler) signal.signal(signal.SIGINT, inturrupt_handler)
if settings['crypt']: if settings['crypt']:
pappy_config.crypt_file = settings['crypt']
pappy_config.crypt_session = True
session.decrypt() session.decrypt()
conf_settings = pappy_config.load_from_file('./config.json') conf_settings = pappy_config.load_from_file('./config.json')
pappy_config.global_load_from_file() pappy_config.global_load_from_file()