summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--client/src/leap/soledad/client/__init__.py7
-rw-r--r--client/src/leap/soledad/client/crypto.py6
-rw-r--r--client/src/leap/soledad/client/secrets.py82
-rw-r--r--common/src/leap/soledad/common/tests/test_crypto.py20
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()