Pep8 corrections

master
Nich 9 years ago
parent f5c53add9c
commit f7d8df69cc
  1. 31
      pappyproxy/compress.py
  2. 100
      pappyproxy/crypto.py
  3. 13
      pappyproxy/pappy.py

@ -14,7 +14,8 @@ except ImportError:
print "BZ2 not installed on your system" print "BZ2 not installed on your system"
from base64 import b64encode, b64decode from base64 import b64encode, b64decode
from os import getcwd, sep, path, urandom from os import getcwd, sep, path, urandom
class Compress(object): class Compress(object):
def __init__(self, sessconfig): def __init__(self, sessconfig):
@ -27,17 +28,17 @@ class Compress(object):
self.tar_project() self.tar_project()
else: else:
self.zip_project() self.zip_project()
def decompress_project(self): def decompress_project(self):
if bz2: if bz2:
self.untar_project() self.untar_project()
else: else:
self.unzip_project() self.unzip_project()
def zip_project(self): def zip_project(self):
""" """
Zip project files Zip project files
Using append mode (mode='a') will create a zip archive Using append mode (mode='a') will create a zip archive
if none exists in the project. if none exists in the project.
""" """
@ -47,32 +48,32 @@ class Compress(object):
zf.close() zf.close()
except zipfile.LargeZipFile as e: except zipfile.LargeZipFile as e:
raise PappyException("Project zipfile too large. Error: ", e) raise PappyException("Project zipfile too large. Error: ", e)
def unzip_project(self): def unzip_project(self):
""" """
Extract project files from decrypted zip archive. Extract project files from decrypted zip archive.
Initially checks the zip archive's magic number and Initially checks the zip archive's magic number and
attempts to extract pappy.json to validate integrity attempts to extract pappy.json to validate integrity
of the zipfile. of the zipfile.
""" """
if not zipfile.is_zipfile(self.zip_archive): if not zipfile.is_zipfile(self.zip_archive):
raise PappyException("Project archive corrupted.") raise PappyException("Project archive corrupted.")
zf = zipfile.ZipFile(self.zip_archive) zf = zipfile.ZipFile(self.zip_archive)
try: try:
zf.extract("config.json") zf.extract("config.json")
except zipfile.BadZipfile as e: except zipfile.BadZipfile as e:
raise PappyException("Project archive contents corrupted. Error: ", e) raise PappyException("Zip archive corrupted. Error: ", e)
zf.extractall() zf.extractall()
def tar_project(self): def tar_project(self):
archive = tarfile.open(self.bz2_archive, 'w:bz2') archive = tarfile.open(self.bz2_archive, 'w:bz2')
archive.add(self.config.crypt_dir) archive.add(self.config.crypt_dir)
archive.close() archive.close()
def untar_project(self): def untar_project(self):
if tarfile.is_tarfile(self.bz2_archive): if tarfile.is_tarfile(self.bz2_archive):
# Raise exception if there is a failure # Raise exception if there is a failure
@ -80,4 +81,4 @@ class Compress(object):
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 tarfile.ExtractError as e: except tarfile.ExtractError as e:
raise PappyException("Project archive contents corrupted. Error: ", e) raise PappyException("Tar archive corrupted. Error: ", e)

@ -14,53 +14,55 @@ from base64 import b64encode, b64decode
from cryptography.fernet import Fernet, InvalidToken from cryptography.fernet import Fernet, InvalidToken
from twisted.internet import reactor, defer from twisted.internet import reactor, defer
class Crypto(object): class Crypto(object):
def __init__(self, sessconfig): def __init__(self, sessconfig):
self.config = sessconfig self.config = sessconfig
self.archive = sessconfig.archive self.archive = sessconfig.archive
self.compressor = compress.Compress(sessconfig) self.compressor = compress.Compress(sessconfig)
self.key = None self.key = None
self.password = None self.password = None
self.salt = None self.salt = None
def encrypt_project(self): def encrypt_project(self):
""" """
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 # Leave the crypto working directory
os.chdir('../') os.chdir('../')
self.compressor.compress_project() self.compressor.compress_project()
# Create project and crypto archive # Create project and crypto archive
archive_file = open(self.archive, 'rb') archive_file = open(self.archive, 'rb')
archive_crypt = open(self.config.crypt_file, 'wb') archive_crypt = open(self.config.crypt_file, 'wb')
# 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()
# Encrypt the archive read as a bytestring # Encrypt the archive read as a bytestring
fern = Fernet(self.key) fern = Fernet(self.key)
crypt_token = fern.encrypt(archive_file.read()) crypt_token = fern.encrypt(archive_file.read())
archive_crypt.write(crypt_token) archive_crypt.write(crypt_token)
# Store the salt for the next decryption # Store the salt for the next decryption
self.create_salt_file() self.create_salt_file()
archive_file.close() archive_file.close()
archive_crypt.close() archive_crypt.close()
# Delete clear-text files # Delete clear-text files
self.delete_clear_files() self.delete_clear_files()
def decrypt_project(self): def decrypt_project(self):
""" """
Decrypt and decompress the project files Decrypt and decompress the project files
""" """
# If project hasn't been encrypted before, setup crypt working directory # If project hasn't been encrypted before,
# setup crypt working directory
if not os.path.isfile(self.config.crypt_file): if not os.path.isfile(self.config.crypt_file):
os.mkdir(self.config.crypt_dir) os.mkdir(self.config.crypt_dir)
@ -69,17 +71,17 @@ class Crypto(object):
shutil.copy2(pf, self.config.crypt_dir) shutil.copy2(pf, self.config.crypt_dir)
os.chdir(self.config.crypt_dir) os.chdir(self.config.crypt_dir)
return True return True
# Otherwise, decrypt and decompress the project # Otherwise, decrypt and decompress the project
else: else:
archive_crypt = open(self.config.crypt_file, 'rb').read() archive_crypt = open(self.config.crypt_file, 'rb').read()
archive_file = open(self.config.archive, 'wb') archive_file = open(self.config.archive, 'wb')
retries = 3 retries = 3
while True: while True:
try: try:
self.crypto_ramp_up() self.crypto_ramp_up()
fern = Fernet(self.key) fern = Fernet(self.key)
archive = fern.decrypt(archive_crypt) archive = fern.decrypt(archive_crypt)
break break
except InvalidToken: except InvalidToken:
@ -93,35 +95,35 @@ class Crypto(object):
self.password = None self.password = None
self.key = None self.key = None
self.salt = None self.salt = None
pass pass
archive_file.write(archive) archive_file.write(archive)
archive_file.close() archive_file.close()
self.compressor.decompress_project() self.compressor.decompress_project()
# Force generation of new salt and crypt archive # Force generation of new salt and crypt archive
self.delete_crypt_files() self.delete_crypt_files()
os.chdir(self.config.crypt_dir) os.chdir(self.config.crypt_dir)
return True return True
def confirm_password_retry(self): def confirm_password_retry(self):
answer = raw_input("Would you like to re-enter your password? (y/n)").strip() answer = raw_input("Re-enter your password? (y/n)").strip()
if answer[0] == "y" or answer[0] == "Y": if answer[0] == "y" or answer[0] == "Y":
return True return True
else: else:
return False return False
def crypto_ramp_up(self): def crypto_ramp_up(self):
if not self.password: if not self.password:
self.get_password() self.get_password()
self.set_salt() self.set_salt()
self.derive_key() self.derive_key()
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
password is not capable of utf-8 encoding. password is not capable of utf-8 encoding.
""" """
encoded_passwd = "" encoded_passwd = ""
@ -130,75 +132,75 @@ class Crypto(object):
self.password = passwd.encode("utf-8") self.password = passwd.encode("utf-8")
except: except:
raise PappyException("Invalid password, try again") 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 set_salt_from_file(self): def set_salt_from_file(self):
try: try:
salt_file = open(self.config.salt_file, 'rb') salt_file = open(self.config.salt_file, 'rb')
self.salt = salt_file.readline().strip() self.salt = salt_file.readline().strip()
except: except:
raise PappyException("Unable to read project.salt") raise PappyException("Unable to read project.salt")
def create_salt_file(self): def create_salt_file(self):
salt_file = open(self.config.salt_file, 'wb') salt_file = open(self.config.salt_file, 'wb')
salt_file.write(self.salt) salt_file.write(self.salt)
salt_file.close() 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
used to encrypt the project (currently: cryptography.Fernet). used to encrypt the project (currently: cryptography.Fernet).
cryptography.Fernet utilizes AES-CBC-128, requiring a 32-byte key. cryptography.Fernet utilizes AES-CBC-128, requiring a 32-byte key.
Parameter notes from the py-scrypt source-code: Parameter notes from the py-scrypt source-code:
https://bitbucket.org/mhallin/py-scrypt/ https://bitbucket.org/mhallin/py-scrypt/
Compute scrypt(password, salt, N, r, p, buflen). Compute scrypt(password, salt, N, r, p, buflen).
The parameters r, p, and buflen must satisfy r * p < 2^30 and 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 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. greater than 1. N, r and p must all be positive.
Notes for Python 2: Notes for Python 2:
- `password` and `salt` must be str instances - `password` and `salt` must be str instances
- The result will be a str instance - The result will be a str instance
Notes for Python 3: Notes for Python 3:
- `password` and `salt` can be both str and bytes. If they are str - `password` and `salt` can be both str and bytes. If they are str
instances, they wil be encoded with utf-8. instances, they wil be encoded with utf-8.
- The result will be a bytes instance - The result will be a bytes instance
Exceptions raised: Exceptions raised:
- TypeError on invalid input - TypeError on invalid input
- scrypt.error if scrypt failed - scrypt.error if scrypt failed
""" """
try: try:
if not self.key: if not self.key:
self.key = b64encode(scrypt.hash(self.password, self.salt, buflen=32)) shash = scrypt.hash(self.password, self.salt, buflen=32)
self.key = b64encode(shash)
except TypeError as e: except TypeError as e:
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): def delete_clear_files(self):
""" """
Deletes all clear-text files left in the project directory. Deletes all clear-text files left in the project directory.
""" """
shutil.rmtree(self.config.crypt_dir) shutil.rmtree(self.config.crypt_dir)
os.remove(self.config.archive) os.remove(self.config.archive)
def delete_crypt_files(self): def delete_crypt_files(self):
""" """
Deletes all encrypted-text files in the project directory. Deletes all encrypted-text files in the project directory.
Forces generation of new salt after opening and closing the project. Forces generation of new salt after opening and closing the project.
Adds security in the case of a one-time compromise of the system. 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) os.remove(self.config.crypt_file)

@ -159,9 +159,9 @@ class PappySession(object):
yield True yield True
# Quit pappy on failure # Quit pappy on failure
else: else:
reactor.stop() reactor.stop()
defer.returnValue(None) defer.returnValue(None)
@defer.inlineCallbacks @defer.inlineCallbacks
def cleanup(self, ignored=None): def cleanup(self, ignored=None):
for port in self.ports: for port in self.ports:
@ -181,10 +181,13 @@ def parse_args():
parser = argparse.ArgumentParser(description='An intercepting proxy for testing web applications.') parser = argparse.ArgumentParser(description='An intercepting proxy for testing web applications.')
parser.add_argument('-l', '--lite', help='Run the proxy in "lite" mode', action='store_true') parser.add_argument('-l', '--lite', help='Run the proxy in "lite" mode', action='store_true')
try: try:
parser.add_argument('-c', '--crypt', type=str, nargs=1, help='Start pappy in "crypto" mode, must supply a name for the encrypted project archive [CRYPT]') hlpmsg = 'Start pappy in "crypto" mode,'+
'must supply a name for the encrypted'+
'project archive [CRYPT]'
parser.add_argument('-c', '--crypt', type=str, nargs=1, help=hlpmsg)
except: except:
print 'Must supply a project name: pappy -c <project_name>' print 'Must supply a project name: pappy -c <project_name>'
reactor.stop() reactor.stop()
defer.returnValue(None) defer.returnValue(None)
args = parser.parse_args(sys.argv[1:]) args = parser.parse_args(sys.argv[1:])
@ -197,7 +200,7 @@ def parse_args():
if args.crypt: if args.crypt:
# Convert from single-item list produced by argparse `nargs=1` # Convert from single-item list produced by argparse `nargs=1`
settings['crypt'] = args.crypt[0].encode('utf-8') settings['crypt'] = args.crypt[0].encode('utf-8')
else: else:
settings['crypt'] = None settings['crypt'] = None

Loading…
Cancel
Save