summaryrefslogtreecommitdiff
path: root/client/src
diff options
context:
space:
mode:
Diffstat (limited to 'client/src')
-rw-r--r--client/src/leap/soledad/client/_version.py3
-rw-r--r--client/src/leap/soledad/client/api.py78
-rw-r--r--client/src/leap/soledad/client/crypto.py16
-rw-r--r--client/src/leap/soledad/client/encdecpool.py7
-rw-r--r--client/src/leap/soledad/client/http_target/__init__.py1
-rw-r--r--client/src/leap/soledad/client/http_target/api.py5
-rw-r--r--client/src/leap/soledad/client/http_target/fetch.py14
-rw-r--r--client/src/leap/soledad/client/http_target/send.py15
-rw-r--r--client/src/leap/soledad/client/secrets.py22
9 files changed, 134 insertions, 27 deletions
diff --git a/client/src/leap/soledad/client/_version.py b/client/src/leap/soledad/client/_version.py
index 889fba38..23749c7c 100644
--- a/client/src/leap/soledad/client/_version.py
+++ b/client/src/leap/soledad/client/_version.py
@@ -1,4 +1,3 @@
-
# This file was generated by the `freeze_debianver` command in setup.py
# Using 'versioneer.py' (0.7+) from
# revision-control system data, or from the parent directory name of an
@@ -10,4 +9,4 @@ version_full = 'aa6a34bc4ac5962dacaa5908778e444fe5aae3d7'
def get_versions(default={}, verbose=False):
- return {'version': version_version, 'full': version_full}
+ return {'version': version_version, 'full': version_full}
diff --git a/client/src/leap/soledad/client/api.py b/client/src/leap/soledad/client/api.py
index 8c5f7f1b..e657c939 100644
--- a/client/src/leap/soledad/client/api.py
+++ b/client/src/leap/soledad/client/api.py
@@ -32,6 +32,7 @@ import logging
import os
import socket
import ssl
+import uuid
import urlparse
try:
@@ -44,11 +45,11 @@ from StringIO import StringIO
from collections import defaultdict
from u1db.remote import http_client
from u1db.remote.ssl_match_hostname import match_hostname
+from twisted.internet.defer import DeferredLock, returnValue, inlineCallbacks
from zope.interface import implements
from leap.common.config import get_path_prefix
from leap.common.plugins import collect_plugins
-from twisted.internet.defer import DeferredLock
from leap.soledad.common import SHARED_DB_NAME
from leap.soledad.common import soledad_assert
@@ -102,11 +103,11 @@ class Soledad(object):
soledad starts to retrieve keys from server.
SOLEDAD_DONE_DOWNLOADING_KEYS: emitted during bootstrap sequence when
soledad finishes downloading keys from server.
- SOLEDAD_NEW_DATA_TO_SYNC: emitted upon call to C{need_sync()} when
- there's indeed new data to be synchronized between local database
- replica and server's replica.
SOLEDAD_DONE_DATA_SYNC: emitted inside C{sync()} method when it has
finished synchronizing with remote replica.
+ SOLEDAD_NEW_DATA_TO_SYNC: emitted upon call to C{need_sync()} when
+ there's indeed new data to be synchronized between local database
+ replica and server's replica. --- not used right now.
"""
implements(soledad_interfaces.ILocalStorage,
soledad_interfaces.ISyncableStorage,
@@ -125,7 +126,8 @@ class Soledad(object):
def __init__(self, uuid, passphrase, secrets_path, local_db_path,
server_url, cert_file, shared_db=None,
- auth_token=None, defer_encryption=False, syncable=True):
+ auth_token=None, defer_encryption=False, syncable=True,
+ userid=None):
"""
Initialize configuration, cryptographic keys and dbs.
@@ -179,12 +181,14 @@ class Soledad(object):
"""
# store config params
self._uuid = uuid
+ self._userid = userid
self._passphrase = passphrase
self._local_db_path = local_db_path
self._server_url = server_url
self._defer_encryption = defer_encryption
self._secrets_path = None
self._sync_enc_pool = None
+ self._dbsyncer = None
self.shared_db = shared_db
@@ -215,6 +219,7 @@ class Soledad(object):
#
# initialization/destruction methods
#
+
def _init_config_with_defaults(self):
"""
Initialize configuration using default values for missing params.
@@ -250,7 +255,7 @@ class Soledad(object):
"""
self._secrets = SoledadSecrets(
self.uuid, self._passphrase, self._secrets_path,
- self.shared_db)
+ self.shared_db, userid=self._userid)
self._secrets.bootstrap()
def _init_u1db_sqlcipher_backend(self):
@@ -648,10 +653,29 @@ class Soledad(object):
def uuid(self):
return self._uuid
+ @property
+ def userid(self):
+ return self._userid
+
#
# ISyncableStorage
#
+ def set_syncable(self, syncable):
+ """
+ Toggle the syncable state for this database.
+
+ This can be used to start a database with offline state and switch it
+ online afterwards. Or the opposite: stop syncs when connection is lost.
+
+ :param syncable: new status for syncable.
+ :type syncable: bool
+ """
+ # TODO should check that we've got a token!
+ self.shared_db.syncable = syncable
+ if syncable and not self._dbsyncer:
+ self._init_u1db_syncer()
+
def sync(self, defer_decryption=True):
"""
Synchronize documents with the server replica.
@@ -718,8 +742,9 @@ class Soledad(object):
return failure
def _emit_done_data_sync(passthrough):
+ user_data = {'uuid': self.uuid, 'userid': self.userid}
soledad_events.emit_async(
- soledad_events.SOLEDAD_DONE_DATA_SYNC, self.uuid)
+ soledad_events.SOLEDAD_DONE_DATA_SYNC, user_data)
return passthrough
d.addCallbacks(_sync_callback, _sync_errback)
@@ -747,6 +772,13 @@ class Soledad(object):
"""
return self.sync_lock.locked
+ @property
+ def syncable(self):
+ if self.shared_db:
+ return self.shared_db.syncable
+ else:
+ return False
+
def _set_token(self, token):
"""
Set the authentication token for remote database access.
@@ -911,6 +943,38 @@ class Soledad(object):
"""
return self._dbpool.runOperation(*args, **kw)
+ #
+ # Service authentication
+ #
+
+ @inlineCallbacks
+ def get_or_create_service_token(self, service):
+ """
+ Return the stored token for a given service, or generates and stores a
+ random one if it does not exist.
+
+ These tokens can be used to authenticate services.
+ """
+ # FIXME this could use the local sqlcipher database, to avoid
+ # problems with different replicas creating different tokens.
+
+ yield self.create_index('by-servicetoken', 'type', 'service')
+ docs = yield self._get_token_for_service(service)
+ if docs:
+ doc = docs[0]
+ returnValue(doc.content['token'])
+ else:
+ token = str(uuid.uuid4()).replace('-', '')[-24:]
+ yield self._set_token_for_service(service, token)
+ returnValue(token)
+
+ def _get_token_for_service(self, service):
+ return self.get_from_index('by-servicetoken', 'servicetoken', service)
+
+ def _set_token_for_service(self, service, token):
+ doc = {'type': 'servicetoken', 'service': service, 'token': token}
+ return self.create_doc(doc)
+
def _convert_to_unicode(content):
"""
diff --git a/client/src/leap/soledad/client/crypto.py b/client/src/leap/soledad/client/crypto.py
index 90ad656e..363d71b9 100644
--- a/client/src/leap/soledad/client/crypto.py
+++ b/client/src/leap/soledad/client/crypto.py
@@ -24,7 +24,9 @@ import hashlib
import json
import logging
-from pycryptopp.cipher.aes import AES
+from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+from cryptography.hazmat.backends.multibackend import MultiBackend
+from cryptography.hazmat.backends.openssl.backend import Backend as OpenSSLBackend
from leap.soledad.common import soledad_assert
from leap.soledad.common import soledad_assert_type
@@ -56,7 +58,10 @@ def encrypt_sym(data, key):
(len(key) * 8))
iv = os.urandom(16)
- ciphertext = AES(key=key, iv=iv).process(data)
+ backend = MultiBackend([OpenSSLBackend()])
+ cipher = Cipher(algorithms.AES(key), modes.CTR(iv), backend=backend)
+ encryptor = cipher.encryptor()
+ ciphertext = encryptor.update(data) + encryptor.finalize()
return binascii.b2a_base64(iv), ciphertext
@@ -81,8 +86,11 @@ def decrypt_sym(data, key, iv):
soledad_assert(
len(key) == 32, # 32 x 8 = 256 bits.
'Wrong key size: %s (must be 256 bits long).' % len(key))
- return AES(
- key=key, iv=binascii.a2b_base64(iv)).process(data)
+ backend = MultiBackend([OpenSSLBackend()])
+ iv = binascii.a2b_base64(iv)
+ cipher = Cipher(algorithms.AES(key), modes.CTR(iv), backend=backend)
+ decryptor = cipher.decryptor()
+ return decryptor.update(data) + decryptor.finalize()
def doc_mac_key(doc_id, secret):
diff --git a/client/src/leap/soledad/client/encdecpool.py b/client/src/leap/soledad/client/encdecpool.py
index 0954c1df..34667a1e 100644
--- a/client/src/leap/soledad/client/encdecpool.py
+++ b/client/src/leap/soledad/client/encdecpool.py
@@ -28,6 +28,7 @@ import json
import logging
from twisted.internet import reactor
+from twisted.internet import threads
from twisted.internet import defer
from twisted.python import log
@@ -687,7 +688,11 @@ class SyncDecrypterPool(SyncEncryptDecryptPool):
"""
insertable = yield self._get_insertable_docs()
for doc_fields in insertable:
- self._insert_decrypted_local_doc(*doc_fields)
+ method = self._insert_decrypted_local_doc
+ # FIXME: This is used only because SQLCipherU1DBSync is synchronous
+ # When adbapi is used there is no need for an external thread
+ # Without this the reactor can freeze and fail docs download
+ yield threads.deferToThread(method, *doc_fields)
defer.returnValue(insertable)
def _delete_processed_docs(self, inserted):
diff --git a/client/src/leap/soledad/client/http_target/__init__.py b/client/src/leap/soledad/client/http_target/__init__.py
index 498fb6e7..a16531ef 100644
--- a/client/src/leap/soledad/client/http_target/__init__.py
+++ b/client/src/leap/soledad/client/http_target/__init__.py
@@ -79,6 +79,7 @@ class SoledadHTTPSyncTarget(SyncTargetAPI, HTTPDocSender, HTTPDocFetcher):
self._url = str(url) + "/sync-from/" + str(source_replica_uid)
self.source_replica_uid = source_replica_uid
self._auth_header = None
+ self._uuid = None
self.set_creds(creds)
self._crypto = crypto
self._sync_db = sync_db
diff --git a/client/src/leap/soledad/client/http_target/api.py b/client/src/leap/soledad/client/http_target/api.py
index dcc762f6..94354092 100644
--- a/client/src/leap/soledad/client/http_target/api.py
+++ b/client/src/leap/soledad/client/http_target/api.py
@@ -40,6 +40,10 @@ class SyncTargetAPI(SyncTarget):
self._sync_decr_pool.stop()
yield self._http.close()
+ @property
+ def uuid(self):
+ return self._uuid
+
def set_creds(self, creds):
"""
Update credentials.
@@ -49,6 +53,7 @@ class SyncTargetAPI(SyncTarget):
"""
uuid = creds['token']['uuid']
token = creds['token']['token']
+ self._uuid = uuid
auth = '%s:%s' % (uuid, token)
b64_token = base64.b64encode(auth)
self._auth_header = {'Authorization': ['Token %s' % b64_token]}
diff --git a/client/src/leap/soledad/client/http_target/fetch.py b/client/src/leap/soledad/client/http_target/fetch.py
index 65e576d9..9f7a4193 100644
--- a/client/src/leap/soledad/client/http_target/fetch.py
+++ b/client/src/leap/soledad/client/http_target/fetch.py
@@ -39,6 +39,13 @@ class HTTPDocFetcher(object):
So we parse, decrypt and insert locally as they arrive.
"""
+ # The uuid of the local replica.
+ # Any class inheriting from this one should provide a meaningful attribute
+ # if the sync status event is meant to be used somewhere else.
+
+ uuid = 'undefined'
+ userid = 'undefined'
+
@defer.inlineCallbacks
def _receive_docs(self, last_known_generation, last_known_trans_id,
ensure_callback, sync_id, defer_decryption):
@@ -176,7 +183,8 @@ class HTTPDocFetcher(object):
# end of symmetric decryption
# -------------------------------------------------------------
self._received_docs += 1
- _emit_receive_status(self._received_docs, total)
+ user_data = {'uuid': self.uuid, 'userid': self.userid}
+ _emit_receive_status(user_data, self._received_docs, total)
return number_of_changes, new_generation, new_transaction_id
def _parse_received_doc_response(self, response):
@@ -243,9 +251,9 @@ class HTTPDocFetcher(object):
source_replica_uid=self.source_replica_uid)
-def _emit_receive_status(received_docs, total):
+def _emit_receive_status(user_data, received_docs, total):
content = {'received': received_docs, 'total': total}
- emit_async(SOLEDAD_SYNC_RECEIVE_STATUS, content)
+ emit_async(SOLEDAD_SYNC_RECEIVE_STATUS, user_data, content)
if received_docs % 20 == 0:
msg = "%d/%d" % (received_docs, total)
diff --git a/client/src/leap/soledad/client/http_target/send.py b/client/src/leap/soledad/client/http_target/send.py
index e8abf35b..89288779 100644
--- a/client/src/leap/soledad/client/http_target/send.py
+++ b/client/src/leap/soledad/client/http_target/send.py
@@ -31,6 +31,13 @@ class HTTPDocSender(object):
MAX_BATCH_SIZE = 0 # disabled by now, this is being tested yet
+ # The uuid of the local replica.
+ # Any class inheriting from this one should provide a meaningful attribute
+ # if the sync status event is meant to be used somewhere else.
+
+ uuid = 'undefined'
+ userid = 'undefined'
+
@defer.inlineCallbacks
def _send_docs(self, docs_by_generation, last_known_generation,
last_known_trans_id, sync_id):
@@ -71,7 +78,9 @@ class HTTPDocSender(object):
result = yield self._send_request(body.pop())
if self._defer_encryption:
self._delete_sent(sent)
- _emit_send_status(body.consumed, total)
+
+ user_data = {'uuid': self.uuid, 'userid': self.userid}
+ _emit_send_status(self.uuid, body.consumed, total)
defer.returnValue(result)
def _send_request(self, body):
@@ -112,9 +121,9 @@ class HTTPDocSender(object):
return d
-def _emit_send_status(idx, total):
+def _emit_send_status(user_data, idx, total):
content = {'sent': idx, 'total': total}
- emit_async(SOLEDAD_SYNC_SEND_STATUS, content)
+ emit_async(SOLEDAD_SYNC_SEND_STATUS, user_data, content)
msg = "%d/%d" % (idx, total)
logger.debug("Sync send status: %s" % msg)
diff --git a/client/src/leap/soledad/client/secrets.py b/client/src/leap/soledad/client/secrets.py
index c3c3dff5..e2a5a1d7 100644
--- a/client/src/leap/soledad/client/secrets.py
+++ b/client/src/leap/soledad/client/secrets.py
@@ -147,7 +147,7 @@ class SoledadSecrets(object):
Keys used to access storage secrets in recovery documents.
"""
- def __init__(self, uuid, passphrase, secrets_path, shared_db):
+ def __init__(self, uuid, passphrase, secrets_path, shared_db, userid=None):
"""
Initialize the secrets manager.
@@ -167,6 +167,7 @@ class SoledadSecrets(object):
# param secret_id: The id of the storage secret to be used.
self._uuid = uuid
+ self._userid = userid
self._passphrase = passphrase
self._secrets_path = secrets_path
self._shared_db = shared_db
@@ -433,13 +434,15 @@ class SoledadSecrets(object):
:return: a document with encrypted key material in its contents
:rtype: document.SoledadDocument
"""
- events.emit_async(events.SOLEDAD_DOWNLOADING_KEYS, self._uuid)
+ user_data = self._get_user_data()
+ events.emit_async(events.SOLEDAD_DOWNLOADING_KEYS, user_data)
db = self._shared_db
if not db:
logger.warning('No shared db found')
return
doc = db.get_doc(self._shared_db_doc_id())
- events.emit_async(events.SOLEDAD_DONE_DOWNLOADING_KEYS, self._uuid)
+ user_data = {'userid': self._userid, 'uuid': self._uuid}
+ events.emit_async(events.SOLEDAD_DONE_DOWNLOADING_KEYS, user_data)
return doc
def _put_secrets_in_shared_db(self):
@@ -462,13 +465,14 @@ class SoledadSecrets(object):
# fill doc with encrypted secrets
doc.content = self._export_recovery_document()
# upload secrets to server
- events.emit_async(events.SOLEDAD_UPLOADING_KEYS, self._uuid)
+ user_data = self._get_user_data()
+ events.emit_async(events.SOLEDAD_UPLOADING_KEYS, user_data)
db = self._shared_db
if not db:
logger.warning('No shared db found')
return
db.put_doc(doc)
- events.emit_async(events.SOLEDAD_DONE_UPLOADING_KEYS, self._uuid)
+ events.emit_async(events.SOLEDAD_DONE_UPLOADING_KEYS, user_data)
#
# Management of secret for symmetric encryption.
@@ -588,13 +592,14 @@ class SoledadSecrets(object):
:return: The id of the generated secret.
:rtype: str
"""
- events.emit_async(events.SOLEDAD_CREATING_KEYS, self._uuid)
+ user_data = self._get_user_data()
+ events.emit_async(events.SOLEDAD_CREATING_KEYS, user_data)
# generate random secret
secret = os.urandom(self.GEN_SECRET_LENGTH)
secret_id = sha256(secret).hexdigest()
self._secrets[secret_id] = secret
self._store_secrets()
- events.emit_async(events.SOLEDAD_DONE_CREATING_KEYS, self._uuid)
+ events.emit_async(events.SOLEDAD_DONE_CREATING_KEYS, user_data)
return secret_id
def _store_secrets(self):
@@ -738,3 +743,6 @@ class SoledadSecrets(object):
salt=self._get_sync_db_salt(),
buflen=32, # we need a key with 256 bits (32 bytes)
)
+
+ def _get_user_data(self):
+ return {'uuid': self._uuid, 'userid': self._userid}