diff options
-rw-r--r-- | CHANGELOG | 6 | ||||
-rw-r--r-- | debian/changelog | 2 | ||||
-rw-r--r-- | debian/pydist-overrides | 2 | ||||
-rw-r--r-- | soledad/setup.py | 2 | ||||
-rw-r--r-- | soledad/src/leap/soledad/__init__.py | 65 | ||||
-rw-r--r-- | soledad/src/leap/soledad/tests/test_soledad.py | 34 | ||||
-rw-r--r-- | soledad_server/pkg/soledad | 2 | ||||
-rw-r--r-- | soledad_server/setup.py | 3 |
8 files changed, 110 insertions, 6 deletions
@@ -1,3 +1,9 @@ +0.2.2 Jul 12: +Client: + o Add method for password change. +Server: + o Use the right name as the WSGI server + 0.2.1 Jun 28: Client: o Do not list the backends in the __init__'s __all__ to allow not diff --git a/debian/changelog b/debian/changelog index 92e47ba1..f18b54ea 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -soledad (0.2.1) unstable; urgency=low +soledad (0.2.2) unstable; urgency=low * Initial debian package diff --git a/debian/pydist-overrides b/debian/pydist-overrides new file mode 100644 index 00000000..59e30938 --- /dev/null +++ b/debian/pydist-overrides @@ -0,0 +1,2 @@ +pysqlcipher python-sqlcipher +leap.soledad soledad-common
\ No newline at end of file diff --git a/soledad/setup.py b/soledad/setup.py index cda7f9f7..747b02bd 100644 --- a/soledad/setup.py +++ b/soledad/setup.py @@ -62,7 +62,7 @@ trove_classifiers = ( setup( name='leap.soledad', - version='0.2.1', + version='0.2.2', url='https://leap.se/', license='GPLv3+', description='Synchronization of locally encrypted data among devices.', diff --git a/soledad/src/leap/soledad/__init__.py b/soledad/src/leap/soledad/__init__.py index 2e1155f9..956f47a7 100644 --- a/soledad/src/leap/soledad/__init__.py +++ b/soledad/src/leap/soledad/__init__.py @@ -164,6 +164,20 @@ SECRETS_DOC_ID_HASH_PREFIX = 'uuid-' # Soledad: local encrypted storage and remote encrypted sync. # +class NoStorageSecret(Exception): + """ + Raised when trying to use a storage secret but none is available. + """ + pass + + +class PassphraseTooShort(Exception): + """ + Raised when trying to change the passphrase but the provided passphrase is + too short. + """ + + class Soledad(object): """ Soledad provides encrypted data storage and sync. @@ -232,6 +246,12 @@ class Soledad(object): encryption. """ + MINIMUM_PASSPHRASE_LENGTH = 6 + """ + The minimum length for a passphrase. The passphrase length is only checked + when the user changes her passphras, not when she instantiates Soledad. + """ + IV_SEPARATOR = ":" """ A separator used for storing the encryption initial value prepended to the @@ -246,6 +266,8 @@ class Soledad(object): KDF_KEY = 'kdf' KDF_SALT_KEY = 'kdf_salt' KDF_LENGTH_KEY = 'kdf_length' + KDF_SCRYPT = 'scrypt' + CIPHER_AES256 = 'aes256' """ Keys used to access storage secrets in recovery documents. """ @@ -563,10 +585,10 @@ class Soledad(object): self._secrets[secret_id] = { # leap.soledad.crypto submodule uses AES256 for symmetric # encryption. - self.KDF_KEY: 'scrypt', # TODO: remove hard coded kdf + self.KDF_KEY: self.KDF_SCRYPT, self.KDF_SALT_KEY: binascii.b2a_base64(salt), self.KDF_LENGTH_KEY: len(key), - self.CIPHER_KEY: 'aes256', # TODO: remove hard coded cipher + self.CIPHER_KEY: self.CIPHER_AES256, self.LENGTH_KEY: len(secret), self.SECRET_KEY: '%s%s%s' % ( str(iv), self.IV_SEPARATOR, binascii.b2a_base64(ciphertext)), @@ -600,6 +622,45 @@ class Soledad(object): with open(self._secrets_path, 'w') as f: f.write(json.dumps(data)) + def change_passphrase(self, new_passphrase): + """ + Change the passphrase that encrypts the storage secret. + + @param new_passphrase: The new passphrase. + @type new_passphrase: str + + @raise NoStorageSecret: Raised if there's no storage secret available. + """ + # maybe we want to add more checks to guarantee passphrase is + # reasonable? + soledad_assert_type(new_passphrase, str) + if len(new_passphrase) < self.MINIMUM_PASSPHRASE_LENGTH: + raise PassphraseTooShort( + 'Passphrase must be at least %d characters long!' % + self.MINIMUM_PASSPHRASE_LENGTH) + # ensure there's a secret for which the passphrase will be changed. + if not self._has_secret(): + raise NoStorageSecret() + secret = self._get_storage_secret() + # generate random salt + new_salt = os.urandom(self.SALT_LENGTH) + # get a 256-bit key + key = scrypt.hash(new_passphrase, new_salt, buflen=32) + iv, ciphertext = self._crypto.encrypt_sym(secret, key) + self._secrets[self._secret_id] = { + # leap.soledad.crypto submodule uses AES256 for symmetric + # encryption. + self.KDF_KEY: self.KDF_SCRYPT, # TODO: remove hard coded kdf + self.KDF_SALT_KEY: binascii.b2a_base64(new_salt), + self.KDF_LENGTH_KEY: len(key), + self.CIPHER_KEY: self.CIPHER_AES256, + self.LENGTH_KEY: len(secret), + self.SECRET_KEY: '%s%s%s' % ( + str(iv), self.IV_SEPARATOR, binascii.b2a_base64(ciphertext)), + } + self._store_secrets() + self._passphrase = new_passphrase + # # General crypto utility methods. # diff --git a/soledad/src/leap/soledad/tests/test_soledad.py b/soledad/src/leap/soledad/tests/test_soledad.py index 281b7a0f..875ecc56 100644 --- a/soledad/src/leap/soledad/tests/test_soledad.py +++ b/soledad/src/leap/soledad/tests/test_soledad.py @@ -28,6 +28,7 @@ import simplejson as json from mock import Mock +from pysqlcipher.dbapi2 import DatabaseError from leap.common.testing.basetest import BaseLeapTest from leap.common.events import events_pb2 as proto from leap.soledad.tests import ( @@ -106,6 +107,39 @@ class AuxMethodsTestCase(BaseSoledadTest): sol.local_db_path) self.assertEqual('value_1', sol.server_url) + def test_change_passphrase(self): + """ + Test if passphrase can be changed. + """ + sol = self._soledad_instance( + 'leap@leap.se', + passphrase='123') + doc = sol.create_doc({'simple': 'doc'}) + doc_id = doc.doc_id + # change the passphrase + sol.change_passphrase('654321') + # assert we can not use the old passphrase anymore + self.assertRaises( + DatabaseError, + self._soledad_instance, 'leap@leap.se', '123') + # use new passphrase and retrieve doc + sol2 = self._soledad_instance('leap@leap.se', '654321') + doc2 = sol2.get_doc(doc_id) + self.assertEqual(doc, doc2) + + def test_change_passphrase_with_short_passphrase_raises(self): + """ + Test if attempt to change passphrase passing a short passphrase + raises. + """ + sol = self._soledad_instance( + 'leap@leap.se', + passphrase='123') + # check that soledad complains about new passphrase length + self.assertRaises( + soledad.PassphraseTooShort, + sol.change_passphrase, '54321') + class SoledadSharedDBTestCase(BaseSoledadTest): """ diff --git a/soledad_server/pkg/soledad b/soledad_server/pkg/soledad index c640a94d..b1d898cc 100644 --- a/soledad_server/pkg/soledad +++ b/soledad_server/pkg/soledad @@ -12,7 +12,7 @@ PATH=/sbin:/bin:/usr/sbin:/usr/bin PIDFILE=/var/run/soledad.pid RUNDIR=/var/lib/soledad/ -OBJ=leap.soledad.server.application +OBJ=leap.soledad_server.application LOGFILE=/var/log/soledad.log HTTPS_PORT=2424 PLAIN_PORT=65534 diff --git a/soledad_server/setup.py b/soledad_server/setup.py index 5e5fa058..f9cdd07f 100644 --- a/soledad_server/setup.py +++ b/soledad_server/setup.py @@ -34,6 +34,7 @@ install_requirements = [ 'six==1.1.0', 'routes', 'PyOpenSSL', + 'leap.soledad>=0.2.1', ] @@ -59,7 +60,7 @@ trove_classifiers = ( setup( name='leap.soledad_server', - version='0.2.1', + version='0.2.2', url='https://leap.se/', license='GPLv3+', description='Synchronization of locally encrypted data among devices.', |