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

@ -99,13 +99,13 @@ class PappyConfig(object):
The dictionary from ~/.pappy/global_config.json. It contains settings for The dictionary from ~/.pappy/global_config.json. It contains settings for
Pappy that are specific to the current computer. Avoid putting settings here, Pappy that are specific to the current computer. Avoid putting settings here,
especially if it involves specific projects. especially if it involves specific projects.
.. data: archive .. data: archive
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):

@ -42,13 +42,15 @@ class ProxyCmd(cmd2.Cmd):
self._cmds = {} self._cmds = {}
self._aliases = {} self._aliases = {}
atexit.register(self.save_histfile) # Only read and save history when not in crypto mode
readline.set_history_length(self.session.config.histsize) if not self.session.config.crypt_session:
if os.path.exists('cmdhistory'): atexit.register(self.save_histfile)
if self.session.config.histsize != 0: readline.set_history_length(self.session.config.histsize)
readline.read_history_file('cmdhistory') if os.path.exists('cmdhistory'):
else: if self.session.config.histsize != 0:
os.remove('cmdhistory') readline.read_history_file('cmdhistory')
else:
os.remove('cmdhistory')
cmd2.Cmd.__init__(self, *args, **kwargs) cmd2.Cmd.__init__(self, *args, **kwargs)
@ -110,10 +112,12 @@ class ProxyCmd(cmd2.Cmd):
raise AttributeError(attr) raise AttributeError(attr)
def save_histfile(self): def save_histfile(self):
# Write the command to the history file # Only write to file if not in crypto mode
if self.session.config.histsize != 0: if not self.session.config.crypt_session:
readline.set_history_length(self.session.config.histsize) # Write the command to the history file
readline.write_history_file('cmdhistory') if self.session.config.histsize != 0:
readline.set_history_length(self.session.config.histsize)
readline.write_history_file('cmdhistory')
def get_names(self): def get_names(self):
# Hack to get cmd to recognize do_/etc functions as functions for things # 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 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)
# Store the salt for the next decryption
self.create_salt_file()
archive_file.close()
archive_crypt.close()
# Delete clear-text files # Delete clear-text files
# delete_clear_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 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): # Otherwise, decrypt and decompress the project
os.mkdir(crypto_path) else:
self.crypto_ramp_up()
if os.path.isfile(self.config.crypt_file): fern = Fernet(self.key)
# Derive the key
key = self.crypto_ramp_up()
fern = Fernet(key)
# Decrypt the project archive # 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 = fern.decrypt(archive_crypt)
archive_file.write(archive)
archive_file.close()
shutil.move(archive, crypto_path)
os.chdir(crypto_path)
self.compressor.decompress_project() self.compressor.decompress_project()
else:
project_files = self.config.get_project_files() # Force generation of new salt and crypt archive
for pf in project_files: self.delete_crypt_files()
shutil.copy2(pf, crypto_path)
os.chdir(crypto_path) 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,56 +96,36 @@ class Crypto(object):
self.set_salt() self.set_salt()
self.derive_key() self.derive_key()
def delete_clear_files(self): def get_password(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. Retrieve password from the user. Raise an exception if the
Forces generation of new salt after opening and closing the project. password is not capable of utf-8 encoding.
Adds security in the case of a one-time compromise of the system.
""" """
os.remove(self.config.salt_file) encoded_passwd = ""
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: try:
salt_file = open(self.config.salt_file, 'rb') passwd = raw_input("Enter a password: ")
self.salt = salt_file.readline().strip() self.password = passwd.encode("utf-8")
except: except:
raise PappyException("Unable to read project.salt") raise PappyException("Invalid password, try again")
def set_salt(self): def set_salt(self):
if os.path.isfile(self.config.salt_file): if os.path.isfile(self.config.salt_file):
self.set_salt_from_file() self.set_salt_from_file()
else: else:
self.salt = os.urandom(16) self.salt = os.urandom(16)
def get_password(self): def set_salt_from_file(self):
"""
Retrieve password from the user. Raise an exception if the
password is not capable of utf-8 encoding.
"""
encoded_passwd = ""
try: try:
passwd = raw_input("Enter a password: ") salt_file = open(self.config.salt_file, 'rb')
self.password = passwd.encode("utf-8") self.salt = salt_file.readline().strip()
except: 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): def derive_key(self):
""" """
@ -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)

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

Loading…
Cancel
Save