diff options
-rw-r--r-- | client/src/leap/soledad/client/__init__.py | 7 | ||||
-rw-r--r-- | client/src/leap/soledad/client/crypto.py | 6 | ||||
-rw-r--r-- | client/src/leap/soledad/client/secrets.py | 82 | ||||
-rw-r--r-- | common/src/leap/soledad/common/tests/test_crypto.py | 20 |
4 files changed, 88 insertions, 27 deletions
diff --git a/client/src/leap/soledad/client/__init__.py b/client/src/leap/soledad/client/__init__.py index 0fd6672a..e66055e0 100644 --- a/client/src/leap/soledad/client/__init__.py +++ b/client/src/leap/soledad/client/__init__.py @@ -751,6 +751,13 @@ class Soledad(object): return self._secrets.storage_secret @property + def remote_storage_secret(self): + """ + Return the secret used for encryption of remotelly stored data. + """ + return self._secrets.remote_storage_secret + + @property def secrets(self): return self._secrets diff --git a/client/src/leap/soledad/client/crypto.py b/client/src/leap/soledad/client/crypto.py index 4a64b5a8..1b01913d 100644 --- a/client/src/leap/soledad/client/crypto.py +++ b/client/src/leap/soledad/client/crypto.py @@ -240,9 +240,7 @@ class SoledadCrypto(object): if self.secret is None: raise NoSymmetricSecret() return hmac.new( - self.secret[ - MAC_KEY_LENGTH: - self._soledad.secrets.REMOTE_STORAGE_SECRET_LENGTH], + self.secret[MAC_KEY_LENGTH:], doc_id, hashlib.sha256).digest() @@ -251,7 +249,7 @@ class SoledadCrypto(object): # def _get_secret(self): - return self._soledad.storage_secret + return self._soledad.secrets.remote_storage_secret secret = property( _get_secret, doc='The secret used for symmetric encryption') diff --git a/client/src/leap/soledad/client/secrets.py b/client/src/leap/soledad/client/secrets.py index 621e2d99..55580692 100644 --- a/client/src/leap/soledad/client/secrets.py +++ b/client/src/leap/soledad/client/secrets.py @@ -123,6 +123,14 @@ class SoledadSecrets(object): encryption. """ + GEN_SECRET_LENGTH = LOCAL_STORAGE_SECRET_LENGTH \ + + REMOTE_STORAGE_SECRET_LENGTH \ + + SALT_LENGTH # for sync db + """ + The length of the secret to be generated. This includes local and remote + secrets, and the salt for deriving the sync db secret. + """ + MINIMUM_PASSPHRASE_LENGTH = 6 """ The minimum length for a passphrase. The passphrase length is only checked @@ -268,12 +276,21 @@ class SoledadSecrets(object): with open(self._secrets_path, 'r') as f: content = json.loads(f.read()) _, mac = self._import_recovery_document(content) - if mac is False: - self._store_secrets() - self._put_secrets_in_shared_db() # choose first secret if no secret_id was given if self._secret_id is None: self.set_secret_id(self._secrets.items()[0][0]) + # enlarge secret if needed + enlarged = False + if len(self._secrets[self._secret_id]) < self.GEN_SECRET_LENGTH: + gen_len = self.GEN_SECRET_LENGTH \ + - len(self._secrets[self._secret_id]) + new_piece = os.urandom(gen_len) + self._secrets[self._secret_id] += new_piece + enlarged = True + # store and save in shared db if needed + if mac is False or enlarged is True: + self._store_secrets() + self._put_secrets_in_shared_db() def _get_or_gen_crypto_secrets(self): """ @@ -596,9 +613,7 @@ class SoledadSecrets(object): """ signal(SOLEDAD_CREATING_KEYS, self._uuid) # generate random secret - secret = os.urandom( - self.LOCAL_STORAGE_SECRET_LENGTH - + self.REMOTE_STORAGE_SECRET_LENGTH) + secret = os.urandom(self.GEN_SECRET_LENGTH) secret_id = sha256(secret).hexdigest() self._secrets[secret_id] = secret self._store_secrets() @@ -667,24 +682,29 @@ class SoledadSecrets(object): def _passphrase_as_string(self): return self._passphrase.encode('utf-8') - def get_syncdb_secret(self): - """ - Return the secret for sync db. - """ - # TODO: implement. - pass + # + # remote storage secret + # - def _get_remote_storage_secret(self): + @property + def remote_storage_secret(self): """ Return the secret for remote storage. """ - # TODO: implement - pass + key_start = 0 + key_end = self.REMOTE_STORAGE_SECRET_LENGTH + return self.storage_secret[key_start:key_end] + # + # local storage key + # def _get_local_storage_secret(self): """ Return the local storage secret. + + :return: The local storage secret. + :rtype: str """ pwd_start = self.REMOTE_STORAGE_SECRET_LENGTH + self.SALT_LENGTH pwd_end = self.REMOTE_STORAGE_SECRET_LENGTH + self.LOCAL_STORAGE_SECRET_LENGTH @@ -693,6 +713,9 @@ class SoledadSecrets(object): def _get_local_storage_salt(self): """ Return the local storage salt. + + :return: The local storage salt. + :rtype: str """ salt_start = self.REMOTE_STORAGE_SECRET_LENGTH salt_end = salt_start + self.SALT_LENGTH @@ -701,9 +724,38 @@ class SoledadSecrets(object): def get_local_storage_key(self): """ Return the local storage key derived from the local storage secret. + + :return: The key for protecting the local database. + :rtype: str """ return scrypt.hash( self._get_local_storage_secret(), # the password self._get_local_storage_salt(), # the salt buflen=32, # we need a key with 256 bits (32 bytes) ) + + # + # sync db key + # + + def _get_sync_db_salt(self): + """ + Return the salt for sync db. + """ + salt_start = self.LOCAL_STORAGE_SECRET_LENGTH \ + + self.REMOTE_STORAGE_SECRET_LENGTH + salt_end = salt_start + self.SALT_LENGTH + return self.storage_secret[salt_start:salt_end] + + def get_sync_db_key(self): + """ + Return the key for protecting the sync database. + + :return: The key for protecting the sync database. + :rtype: str + """ + return scrypt.hash( + self._get_local_storage_secret(), # the password + self._get_sync_db_salt(), # the salt + buflen=32, # we need a key with 256 bits (32 bytes) + ) diff --git a/common/src/leap/soledad/common/tests/test_crypto.py b/common/src/leap/soledad/common/tests/test_crypto.py index ccff5e46..0302a268 100644 --- a/common/src/leap/soledad/common/tests/test_crypto.py +++ b/common/src/leap/soledad/common/tests/test_crypto.py @@ -59,13 +59,18 @@ class RecoveryDocumentTestCase(BaseSoledadTest): def test_export_recovery_document_raw(self): rd = self._soledad.secrets._export_recovery_document() secret_id = rd[self._soledad.secrets.STORAGE_SECRETS_KEY].items()[0][0] - secret = rd[self._soledad.secrets.STORAGE_SECRETS_KEY][secret_id] + # assert exported secret is the same + secret = self._soledad.secrets._decrypt_storage_secret( + rd[self._soledad.secrets.STORAGE_SECRETS_KEY][secret_id]) self.assertEqual(secret_id, self._soledad.secrets._secret_id) self.assertEqual(secret, self._soledad.secrets._secrets[secret_id]) - self.assertTrue(self._soledad.secrets.CIPHER_KEY in secret) - self.assertTrue(secret[self._soledad.secrets.CIPHER_KEY] == 'aes256') - self.assertTrue(self._soledad.secrets.LENGTH_KEY in secret) - self.assertTrue(self._soledad.secrets.SECRET_KEY in secret) + # assert recovery document structure + encrypted_secret = rd[self._soledad.secrets.STORAGE_SECRETS_KEY][secret_id] + self.assertTrue(self._soledad.secrets.CIPHER_KEY in encrypted_secret) + self.assertTrue( + encrypted_secret[self._soledad.secrets.CIPHER_KEY] == 'aes256') + self.assertTrue(self._soledad.secrets.LENGTH_KEY in encrypted_secret) + self.assertTrue(self._soledad.secrets.SECRET_KEY in encrypted_secret) def test_import_recovery_document(self): rd = self._soledad.secrets._export_recovery_document() @@ -103,8 +108,7 @@ class SoledadSecretsTestCase(BaseSoledadTest): # assert format of secret 1 self.assertTrue(sol.storage_secret is not None) self.assertIsInstance(sol.storage_secret, str) - secret_length = sol.secrets.LOCAL_STORAGE_SECRET_LENGTH \ - + sol.secrets.REMOTE_STORAGE_SECRET_LENGTH + secret_length = sol.secrets.GEN_SECRET_LENGTH self.assertTrue(len(sol.storage_secret) == secret_length) # assert format of secret 2 sol.set_secret_id(secret_id_2) @@ -129,7 +133,7 @@ class SoledadSecretsTestCase(BaseSoledadTest): sol.secrets._has_secret(), "Should have a secret at this point") # but not being able to decrypt correctly should - sol.secrets._secrets[sol.secret_id][sol.secrets.SECRET_KEY] = None + sol.secrets._secrets[sol.secret_id] = None self.assertFalse(sol.secrets._has_secret()) sol.close() |