summaryrefslogtreecommitdiff
path: root/client/src/leap/soledad/client/_secrets/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/leap/soledad/client/_secrets/__init__.py')
-rw-r--r--client/src/leap/soledad/client/_secrets/__init__.py129
1 files changed, 129 insertions, 0 deletions
diff --git a/client/src/leap/soledad/client/_secrets/__init__.py b/client/src/leap/soledad/client/_secrets/__init__.py
new file mode 100644
index 00000000..b6c81cda
--- /dev/null
+++ b/client/src/leap/soledad/client/_secrets/__init__.py
@@ -0,0 +1,129 @@
+# -*- coding: utf-8 -*-
+# _secrets/__init__.py
+# Copyright (C) 2016 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import scrypt
+
+from leap.soledad.common.log import getLogger
+
+from leap.soledad.client._secrets.storage import SecretsStorage
+from leap.soledad.client._secrets.crypto import SecretsCrypto
+from leap.soledad.client._secrets.util import emit, UserDataMixin
+
+
+logger = getLogger(__name__)
+
+
+class Secrets(UserDataMixin):
+
+ lengths = {
+ 'remote_secret': 512, # remote_secret is used to encrypt remote data.
+ 'local_salt': 64, # local_salt is used in conjunction with
+ 'local_secret': 448, # local_secret to derive a local_key for storage
+ }
+
+ def __init__(self, soledad):
+ self._soledad = soledad
+ self._secrets = {}
+ self.crypto = SecretsCrypto(soledad)
+ self.storage = SecretsStorage(soledad)
+ self._bootstrap()
+
+ #
+ # bootstrap
+ #
+
+ def _bootstrap(self):
+
+ # attempt to load secrets from local storage
+ encrypted = self.storage.load_local()
+ if encrypted:
+ self._secrets = self.crypto.decrypt(encrypted)
+ # maybe update the format of storage of local secret.
+ if encrypted['version'] < self.crypto.VERSION:
+ self.store_secrets()
+ return
+
+ # no secret was found in local storage, so this is a first run of
+ # soledad for this user in this device. It is mandatory that we check
+ # if there's a secret stored in server.
+ encrypted = self.storage.load_remote()
+ if encrypted:
+ self._secrets = self.crypto.decrypt(encrypted)
+ self.store_secrets()
+ return
+
+ # we have *not* found a secret neither in local nor in remote storage,
+ # so we have to generate a new one, and then store it.
+ self._secrets = self._generate()
+ self.store_secrets()
+
+ #
+ # generation
+ #
+
+ @emit('creating')
+ def _generate(self):
+ logger.info("generating new set of secrets...")
+ secrets = {}
+ for name, length in self.lengths.iteritems():
+ secret = os.urandom(length)
+ secrets[name] = secret
+ logger.info("new set of secrets successfully generated")
+ return secrets
+
+ #
+ # crypto
+ #
+
+ def store_secrets(self):
+ # TODO: we have to improve the logic here, as we want to make sure that
+ # whatever is stored locally should only be used after remote storage
+ # is successful. Otherwise, this soledad could start encrypting with a
+ # secret while another soledad in another device could start encrypting
+ # with another secret, which would lead to decryption failures during
+ # sync.
+ encrypted = self.crypto.encrypt(self._secrets)
+ self.storage.save_local(encrypted)
+ self.storage.save_remote(encrypted)
+
+ #
+ # secrets
+ #
+
+ @property
+ def remote_secret(self):
+ return self._secrets.get('remote_secret')
+
+ @property
+ def local_salt(self):
+ return self._secrets.get('local_salt')
+
+ @property
+ def local_secret(self):
+ return self._secrets.get('local_secret')
+
+ @property
+ def local_key(self):
+ # local storage key is scrypt-derived from `local_secret` and
+ # `local_salt` above
+ secret = scrypt.hash(
+ password=self.local_secret,
+ salt=self.local_salt,
+ buflen=32, # we need a key with 256 bits (32 bytes)
+ )
+ return secret