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:
parent
a3cb5f13ed
commit
bf914e6f86
5 changed files with 114 additions and 102 deletions
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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…
Add table
Add a link
Reference in a new issue