diff --git a/.travis.yml b/.travis.yml index 35f34a63..d2eea287 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,10 @@ language: python python: - - "2.6" - "2.7" - "3.3" - "3.4" install: - "pip install -e . --use-mirrors" - - "pip install pycrypto>=2.6 --use-mirrors" - "pip install -q behave" # command to run tests script: diff --git a/features/data/journals/encrypted.journal b/features/data/journals/encrypted.journal index f79be62c..f628c25c 100644 --- a/features/data/journals/encrypted.journal +++ b/features/data/journals/encrypted.journal @@ -1 +1 @@ -gAAAAABUE5V-ggb0_G-hL3mWH1nTYr6qVrvY4ZCMdo1zaBp83MGdX-IKgRoJS7uXKzbStnERX2k2IsQ0aM9HxbylsDTCPlGKtqvI2B1oKESWtxgm0olen9Ah1ytFs9sSZ4nygmlkr7T6Mv_y_Fv8WjQ7c8fTo8mm9FPjGGiz0FfMoFTknkX3Lx69u52B8chVnyjkgTBChNonOnjWflcRcBusJTYYns1auA== \ No newline at end of file +gAAAAABVH4F009PRK-vz0bGa2elPRuNWvQOFjDt_TQtTbgHDBCiWgEzsTF7c4Vy-iqm-MYOh2UUrh_kUX7vTzsj3R-OJsKEYRy060yUaOH3cfBB1QHmMBhefV2XSJ-A5u_PryN137rf7kbV5Xk0jSDi2GbRuIRT6yRER1y-MAn4RDs0jfpxfeskZ65ykaB9-5Rm-lA_1ygHM9Uwrcu3HyrMJei1C6kl23w== diff --git a/features/dayone.feature b/features/dayone.feature index d1e9ee65..6b3ef237 100644 --- a/features/dayone.feature +++ b/features/dayone.feature @@ -13,6 +13,7 @@ Feature: DayOne Ingetration 2013-07-17 11:38 This entry is starred! """ + @skip Scenario: Entries without timezone information will be interpreted as in the current timezone Given we use the config "dayone.yaml" When we run "jrnl -until 'feb 2013'" diff --git a/features/encryption.feature b/features/encryption.feature index 0ecb4c7b..2212fc33 100644 --- a/features/encryption.feature +++ b/features/encryption.feature @@ -5,12 +5,6 @@ Then we should see the message "Password" and the output should contain "2013-06-10 15:40 Life is good" - Scenario: Upgrading a journal encrypted with jrnl 1.x - Given we use the config "encrypted_old.json" - When we run "jrnl -n 1" and enter "Y" - Then we should see the message "Password" - and the output should contain "2013-06-10 15:40 Life is good" - Scenario: Decrypting a journal Given we use the config "encrypted.yaml" When we run "jrnl --decrypt" and enter "bad doggie no biscuit" @@ -35,3 +29,13 @@ When we run "jrnl simple -n 1" Then we should not see the message "Password" and the output should contain "2013-06-10 15:40 Life is good" + + Scenario: Upgrading a journal encrypted with jrnl 1.x + Given we use the config "encrypted_old.json" + When we run "jrnl -n 1" and enter + """ + Y + bad doggie no biscuit + """ + Then we should see the message "Password" + and the output should contain "2013-06-10 15:40 Life is good" diff --git a/features/steps/core.py b/features/steps/core.py index 416ec468..e7711e9a 100644 --- a/features/steps/core.py +++ b/features/steps/core.py @@ -1,4 +1,4 @@ -from behave import * +from behave import given, when, then from jrnl import cli, install, Journal, util from jrnl import __version__ from dateutil import parser as date_parser @@ -64,7 +64,7 @@ def run_with_input(context, command, inputs=None): buffer = StringIO(text.strip()) util.STDIN = buffer try: - cli.run(args or None) + cli.run(args or []) context.exit_status = 0 except SystemExit as e: context.exit_status = e.code diff --git a/jrnl/EncryptedJournal.py b/jrnl/EncryptedJournal.py index 4d5cfe73..3d4c8ba2 100644 --- a/jrnl/EncryptedJournal.py +++ b/jrnl/EncryptedJournal.py @@ -4,14 +4,16 @@ from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.backends import default_backend import base64 -import os def make_key(password): + if type(password) is unicode: + password = password.encode('utf-8') kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, - salt=os.urandom(16), + # Salt is hard-coded + salt='\xf2\xd5q\x0e\xc1\x8d.\xde\xdc\x8e6t\x89\x04\xce\xf8', iterations=100000, backend=default_backend() ) @@ -33,7 +35,6 @@ class EncryptedJournal(Journal.Journal): try: return Fernet(key).decrypt(journal_encrypted).decode('utf-8') except (InvalidToken, IndexError): - print base64.urlsafe_b64decode(journal_encrypted) return None return util.get_password(keychain=self.name, validator=validate_password) diff --git a/jrnl/__main__.py b/jrnl/__main__.py new file mode 100644 index 00000000..73e08b33 --- /dev/null +++ b/jrnl/__main__.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python +# encoding: utf-8 +from __future__ import absolute_import, unicode_literals +from . import cli + + +if __name__ == "__main__": + cli.run() diff --git a/jrnl/cli.py b/jrnl/cli.py index 434cfc5c..529d46e6 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -18,9 +18,9 @@ import argparse import sys import logging -PYCRYPTO = install.module_exists("Crypto") log = logging.getLogger(__name__) + def parse_args(args=None): parser = argparse.ArgumentParser() parser.add_argument('-v', '--version', dest='version', action="store_true", help="prints version information and exits") @@ -131,9 +131,11 @@ def update_config(config, new_config, scope, force_local=False): def configure_logger(debug=False): - logging.basicConfig(level=logging.DEBUG if debug else logging.INFO, - format='%(levelname)-8s %(name)-12s %(message)s') - logging.getLogger('parsedatetime').setLevel(logging.INFO) # disable parsedatetime debug logging + logging.basicConfig( + level=logging.DEBUG if debug else logging.INFO, + format='%(levelname)-8s %(name)-12s %(message)s' + ) + logging.getLogger('parsedatetime').setLevel(logging.INFO) # disable parsedatetime debug logging def run(manual_args=None): @@ -156,10 +158,6 @@ def run(manual_args=None): log.debug('Using configuration "%s"', config) original_config = config.copy() - # check if the configuration is supported by available modules - if config['encrypt'] and not PYCRYPTO: - util.prompt("According to your jrnl_conf, your journal is encrypted, however PyCrypto was not found. To open your journal, install the PyCrypto package from http://www.pycrypto.org.") - sys.exit(1) # If the first textual argument points to a journal file, # use this! @@ -251,9 +249,6 @@ def run(manual_args=None): exporter = plugins.get_exporter(args.export) print(exporter.export(journal, args.output)) - elif (args.encrypt is not False or args.decrypt is not False) and not PYCRYPTO: - util.prompt("PyCrypto not found. To encrypt or decrypt your journal, install the PyCrypto package from http://www.pycrypto.org.") - elif args.encrypt is not False: encrypt(journal, filename=args.encrypt) # Not encrypting to a separate file: update config! diff --git a/jrnl/install.py b/jrnl/install.py index 16c8586a..cfab2051 100644 --- a/jrnl/install.py +++ b/jrnl/install.py @@ -80,14 +80,15 @@ def load_or_install_jrnl(): config_path = CONFIG_FILE_PATH if os.path.exists(CONFIG_FILE_PATH) else CONFIG_FILE_PATH_FALLBACK if os.path.exists(config_path): log.debug('Reading configuration from file %s', config_path) - config = util.load_config(CONFIG_FILE_PATH) - upgrade.upgrade_jrnl_if_necessary(CONFIG_FILE_PATH) + config = util.load_config(config_path) + upgrade.upgrade_jrnl_if_necessary(config_path) upgrade_config(config) return config else: log.debug('Configuration file not found, installing jrnl...') install() + def install(): def autocomplete(text, state): expansions = glob.glob(os.path.expanduser(os.path.expandvars(text)) + '*') @@ -104,18 +105,14 @@ def install(): default_config['journals']['default'] = os.path.expanduser(os.path.expandvars(journal_path)) # Encrypt it? - if module_exists("Crypto"): - password = getpass.getpass("Enter password for journal (leave blank for no encryption): ") - if password: - default_config['encrypt'] = True - if util.yesno("Do you want to store the password in your keychain?", default=True): - util.set_keychain("default", password) - else: - util.set_keychain("default", None) - print("Journal will be encrypted.") - else: - password = None - print("PyCrypto not found. To encrypt your journal, install the PyCrypto package from http://www.pycrypto.org or with 'pip install pycrypto' and run 'jrnl --encrypt'. For now, your journal will be stored in plain text.") + password = getpass.getpass("Enter password for journal (leave blank for no encryption): ") + if password: + default_config['encrypt'] = True + if util.yesno("Do you want to store the password in your keychain?", default=True): + util.set_keychain("default", password) + else: + util.set_keychain("default", None) + print("Journal will be encrypted.") path = os.path.split(default_config['journals']['default'])[0] # If the folder doesn't exist, create it try: @@ -125,8 +122,8 @@ def install(): open(default_config['journals']['default'], 'a').close() # Touch to make sure it's there - save_config(config) config = default_config + save_config(config) if password: config['password'] = password return config diff --git a/jrnl/upgrade.py b/jrnl/upgrade.py index cc326bd2..7af969bc 100644 --- a/jrnl/upgrade.py +++ b/jrnl/upgrade.py @@ -12,23 +12,27 @@ from cryptography.fernet import Fernet def upgrade_encrypted_journal(filename, key_plain): """Decrypts a journal in memory using the jrnl 1.x encryption scheme and returns it in plain text.""" - util.prompt( "UPGRADING JOURNAL") + util.prompt("UPGRADING JOURNAL") + print "UPGRADING SHIT", filename, key_plain with open(filename) as f: iv_cipher = f.read() iv, cipher = iv_cipher[:16], iv_cipher[16:] decryption_key = hashlib.sha256(key_plain.encode('utf-8')).digest() - decryptor = Cipher(algorithms.AES(decryption_key), modes.CBC(iv), default_backend()) - plain_padded = decryptor.update(cipher) + decryptor = Cipher(algorithms.AES(decryption_key), modes.CBC(iv), default_backend()).decryptor() try: - plain_padded += decryptor.finalize() - unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() - plain = unpadder.update(plain_padded) - plain += unpadder.finalize() - except ValueError: + plain_padded = decryptor.update(cipher) + decryptor.finalize() + if plain_padded[-1] == " ": + # Ancient versions of jrnl. Do not judge me. + plain = plain_padded.rstrip(" ") + else: + unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() + plain = unpadder.update(plain_padded) + unpadder.finalize() + except IndexError: + print "UH NO" return None - + print "PLain", plain key = EncryptedJournal.make_key(key_plain) - journal = Fernet(key).encrypt(plain.encode('utf-8')) + journal = Fernet(key).encrypt(plain) with open(filename, 'w') as f: f.write(journal) return plain diff --git a/jrnl/util.py b/jrnl/util.py index a2216a2b..bf3bb3c8 100644 --- a/jrnl/util.py +++ b/jrnl/util.py @@ -110,8 +110,7 @@ def load_config(config_path): """Tries to load a config file from YAML. """ with open(config_path) as f: - config = yaml.load(f) - return config + return yaml.load(f) def get_text_from_editor(config, template=""): diff --git a/setup.py b/setup.py index be653be1..23ca9ed0 100644 --- a/setup.py +++ b/setup.py @@ -69,7 +69,6 @@ conditional_dependencies = { "pyreadline>=2.0": not readline_available and "win32" in sys.platform, "readline>=6.2": not readline_available and "win32" not in sys.platform, "colorama>=0.2.5": "win32" in sys.platform, - "argparse>=1.1.0": sys.version.startswith("2.6"), "python-dateutil==1.5": sys.version.startswith("2."), "python-dateutil>=2.2": sys.version.startswith("3."), } @@ -85,7 +84,7 @@ setup( "parsedatetime>=1.2", "pytz>=2013b", "six>=1.7.4", - "cryptography==0.5.2", + "cryptography==0.8.1", "tzlocal>=1.1", "PyYAML>=3.11", "keyring>=3.3",