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.
master
Nich 8 years ago
parent a3cb5f13ed
commit bf914e6f86
  1. 18
      pappyproxy/compress.py
  2. 17
      pappyproxy/config.py
  3. 26
      pappyproxy/console.py
  4. 138
      pappyproxy/crypto.py
  5. 7
      pappyproxy/pappy.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)

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

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

@ -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)

@ -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()

Loading…
Cancel
Save