Converted the crypto and compression plugins to core features, and added the utility variables and functions to the Config class in ``config.py``. Added helper functions in PappySession class in ``pappy.py`` to enable the user to pass in an encrypted project archive. Next moving to testing and debugging!master
parent
6a79209224
commit
5be69b205d
5 changed files with 355 additions and 4 deletions
@ -0,0 +1,95 @@ |
||||
#!/usr/bin/env python |
||||
|
||||
import crochet |
||||
import glob |
||||
import pappyproxy |
||||
|
||||
import zipfile |
||||
import tarfile |
||||
|
||||
# This is a gross hack, please help |
||||
bz2 = None |
||||
try: |
||||
import bz2 |
||||
except: |
||||
print "BZ2 not installed on your system" |
||||
|
||||
from base64 import b64encode, b64decode |
||||
from os import getcwd, sep, path, urandom |
||||
from pappyproxy.plugins.misc import CryptoCompressUtils as ccu |
||||
|
||||
class Compress(object): |
||||
def __init__(self, sessconfig): |
||||
self.config = sessconfig |
||||
self.zip_archive = sessconfig.archive |
||||
self.bz2_archive = sessconfig.archive |
||||
|
||||
def compress_project(): |
||||
if bz2: |
||||
tar_project() |
||||
else: |
||||
zip_project() |
||||
|
||||
def decompress_project(): |
||||
if bz2: |
||||
untar_project() |
||||
else: |
||||
unzip_project() |
||||
|
||||
def zip_project(): |
||||
""" |
||||
Zip project files |
||||
|
||||
Using append mode (mode='a') will create a zip archive |
||||
if none exists in the project. |
||||
""" |
||||
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.close() |
||||
except e: |
||||
raise PappyException("Error creating the zipfile", e) |
||||
pass |
||||
|
||||
def unzip_project(): |
||||
""" |
||||
Extract project files from decrypted zip archive. |
||||
Initially checks the zip archive's magic number and |
||||
attempts to extract pappy.json to validate integrity |
||||
of the zipfile. |
||||
""" |
||||
if not zipfile.is_zipfile(self.zip_archive): |
||||
raise PappyException("Project archive corrupted.") |
||||
|
||||
zf = zipfile.ZipFile(self.zip_archive) |
||||
|
||||
try: |
||||
zf.extract("config.json") |
||||
except e: |
||||
raise PappyException("Project archive contents corrupted. Error: ", e) |
||||
|
||||
zf.extractall() |
||||
|
||||
def tar_project(): |
||||
if tarfile.is_tarfile(self.bz2_archive): |
||||
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.close() |
||||
|
||||
def untar_project(): |
||||
if tarfile.is_tarfile(self.bz2_archive): |
||||
# Attempt to read the first 16 bytes of the archive |
||||
# Raise exception if there is a failure |
||||
project_files = self.config.get_project_files() |
||||
try: |
||||
with tarfile.open(self.bz2_archive, "r:bz2") as archive: |
||||
for pf in project_files: |
||||
archive.add(pf) |
||||
except e: |
||||
raise PappyException("Project archive contents corrupted. Error: ", e |
@ -0,0 +1,164 @@ |
||||
#!/usr/bin/env python |
||||
|
||||
import crochet |
||||
import glob |
||||
import os |
||||
import pappyproxy |
||||
import scrypt |
||||
import shutil |
||||
import twisted |
||||
|
||||
from . import compress |
||||
from base64 import b64encode, b64decode |
||||
from cryptography import Fernet |
||||
from twisted.internet import reactor, defer |
||||
|
||||
class Crypto(object): |
||||
def __init__(self, sessconfig): |
||||
self.config = sessconfig |
||||
self.archive = self.config.archive |
||||
self.compressor = compress.Compress(sessconfig) |
||||
|
||||
def encrypt_project(passwd): |
||||
""" |
||||
Compress and encrypt the project files, deleting clear-text files afterwards |
||||
""" |
||||
# Derive the key |
||||
key = crypto_ramp_up(passwd) |
||||
|
||||
# Instantiate the crypto module |
||||
fern = Fernet(key) |
||||
|
||||
# Create project archive and crypto archive |
||||
self.compressor.compress_project() |
||||
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) |
||||
archive_crypt.write(crypt_token) |
||||
|
||||
# Delete clear-text files |
||||
delete_clear_files() |
||||
|
||||
# Leave crypto working directory |
||||
os.chdir('../') |
||||
|
||||
@defer.inlineCallbacks |
||||
def decrypt_project(passwd): |
||||
""" |
||||
Decrypt and decompress the project files |
||||
""" |
||||
|
||||
# Create crypto working directory |
||||
crypto_path = os.path.join(os.getcwd(), pappy_config.crypt_dir) |
||||
os.mkdir(crypto_path) |
||||
|
||||
if os.path.isfile(self.config.crypt_file): |
||||
# Derive the key |
||||
key = crypto_ramp_up(passwd) |
||||
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() |
||||
for pf in project_files: |
||||
shutil.copy2(pf, crypto_path) |
||||
os.chdir(crypto_path) |
||||
|
||||
|
||||
def crypto_ramp_up(passwd): |
||||
salt = "" |
||||
if os.path.isfile(self.config.salt_file): |
||||
salt = get_salt() |
||||
else: |
||||
salt = create_salt_file() |
||||
key = derive_key(passwd, salt) |
||||
return key |
||||
|
||||
def delete_clear_files(): |
||||
""" |
||||
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(): |
||||
""" |
||||
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.config.salt = urandom(16) |
||||
salt_file = open(self.config.salt_file, 'wb') |
||||
salt_file.write(self.config.salt) |
||||
salt_file.close() |
||||
return salt |
||||
|
||||
def get_salt(): |
||||
try: |
||||
salt_file = open(self.config.salt_file, 'rb') |
||||
salt = salt_file.readline() |
||||
except: |
||||
raise PappyException("Unable to read pappy.salt") |
||||
return salt |
||||
|
||||
def get_password(): |
||||
""" |
||||
Retrieve password from the user. Raise an exception if the |
||||
password is not capable of utf-8 encoding. |
||||
""" |
||||
encoded_passwd = "" |
||||
try: |
||||
passwd = raw_input("Enter a password: ") |
||||
encode_passwd = passwd.encode("utf-8") |
||||
except: |
||||
raise PappyException("Invalid password, try again") |
||||
return encoded_passwd |
||||
|
||||
def derive_key(passwd, salt): |
||||
""" |
||||
Derive a key sufficient for use as a cryptographic key |
||||
used to encrypt the project (currently: cryptography.Fernet). |
||||
|
||||
cryptography.Fernet utilizes AES-CBC-128, requiring a 32-byte key. |
||||
Parameter notes from the py-scrypt source-code: |
||||
https://bitbucket.org/mhallin/py-scrypt/ |
||||
|
||||
Compute scrypt(password, salt, N, r, p, buflen). |
||||
|
||||
The parameters r, p, and buflen must satisfy r * p < 2^30 and |
||||
buflen <= (2^32 - 1) * 32. The parameter N must be a power of 2 |
||||
greater than 1. N, r and p must all be positive. |
||||
|
||||
Notes for Python 2: |
||||
- `password` and `salt` must be str instances |
||||
- The result will be a str instance |
||||
|
||||
Notes for Python 3: |
||||
- `password` and `salt` can be both str and bytes. If they are str |
||||
instances, they wil be encoded with utf-8. |
||||
- The result will be a bytes instance |
||||
|
||||
Exceptions raised: |
||||
- TypeError on invalid input |
||||
- scrypt.error if scrypt failed |
||||
""" |
||||
|
||||
derived_key = "" |
||||
try: |
||||
dkey = scrypt.hash(passwd, salt, bufflen=32) |
||||
except e: |
||||
raise PappyException("Error deriving the key: ", e) |
||||
return derived_key |
Loading…
Reference in new issue