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() | 
