summaryrefslogtreecommitdiff
path: root/src/leap/soledad/backends
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap/soledad/backends')
-rw-r--r--src/leap/soledad/backends/couch.py9
-rw-r--r--src/leap/soledad/backends/leap_backend.py72
-rw-r--r--src/leap/soledad/backends/sqlcipher.py41
3 files changed, 98 insertions, 24 deletions
diff --git a/src/leap/soledad/backends/couch.py b/src/leap/soledad/backends/couch.py
index 6440232d..13b6733c 100644
--- a/src/leap/soledad/backends/couch.py
+++ b/src/leap/soledad/backends/couch.py
@@ -35,6 +35,8 @@ from u1db.remote.server_state import ServerState
from u1db.errors import DatabaseDoesNotExist
from couchdb.client import Server, Document as CouchDocument
from couchdb.http import ResourceNotFound
+
+
from leap.soledad.backends.objectstore import (
ObjectStoreDatabase,
ObjectStoreSyncTarget,
@@ -142,7 +144,8 @@ class CouchDatabase(ObjectStoreDatabase):
doc = self._factory(
doc_id=doc_id,
rev=cdoc['u1db_rev'],
- has_conflicts=has_conflicts)
+ has_conflicts=has_conflicts,
+ encryption_scheme=cdoc['encryption_scheme'])
contents = self._database.get_attachment(cdoc, 'u1db_json')
if contents:
doc.content = json.loads(contents.read())
@@ -192,12 +195,14 @@ class CouchDatabase(ObjectStoreDatabase):
# prepare couch's Document
cdoc = CouchDocument()
cdoc['_id'] = doc.doc_id
- # we have to guarantee that couch's _rev is cosistent
+ # we have to guarantee that couch's _rev is consistent
old_cdoc = self._database.get(doc.doc_id)
if old_cdoc is not None:
cdoc['_rev'] = old_cdoc['_rev']
# store u1db's rev
cdoc['u1db_rev'] = doc.rev
+ # store document's encryption scheme
+ cdoc['encryption_scheme'] = doc.encryption_scheme
# save doc in db
self._database.save(cdoc)
# store u1db's content as json string
diff --git a/src/leap/soledad/backends/leap_backend.py b/src/leap/soledad/backends/leap_backend.py
index 687b59ef..dfec9e85 100644
--- a/src/leap/soledad/backends/leap_backend.py
+++ b/src/leap/soledad/backends/leap_backend.py
@@ -56,6 +56,16 @@ class DocumentNotEncrypted(Exception):
pass
+class EncryptionSchemes(object):
+ """
+ Representation of encryption schemes used to encrypt documents.
+ """
+
+ NONE = 'none'
+ SYMKEY = 'symkey'
+ PUBKEY = 'pubkey'
+
+
class LeapDocument(Document):
"""
Encryptable and syncable document.
@@ -69,7 +79,8 @@ class LeapDocument(Document):
"""
def __init__(self, doc_id=None, rev=None, json='{}', has_conflicts=False,
- encrypted_json=None, crypto=None, syncable=True):
+ encrypted_json=None, crypto=None, syncable=True,
+ encryption_scheme=EncryptionSchemes.NONE):
"""
Container for handling an encryptable document.
@@ -89,10 +100,14 @@ class LeapDocument(Document):
@type crypto: soledad.Soledad
@param syncable: Should this document be synced with remote replicas?
@type syncable: bool
+ @param encryption_scheme: The encryption scheme for this objects'
+ contents.
+ @type encryption_scheme: str
"""
Document.__init__(self, doc_id, rev, json, has_conflicts)
self._crypto = crypto
self._syncable = syncable
+ self._encryption_scheme = encryption_scheme
if encrypted_json:
self.set_encrypted_json(encrypted_json)
@@ -118,6 +133,7 @@ class LeapDocument(Document):
cyphertext,
self._crypto._hash_passphrase(self.doc_id))
self.set_json(plaintext)
+ self.encryption_scheme = EncryptionSchemes.NONE
def get_encrypted_json(self):
"""
@@ -163,6 +179,31 @@ class LeapDocument(Document):
doc="Determine if document should be synced with server."
)
+ def _get_encryption_scheme(self):
+ """
+ Return the encryption scheme used to encrypt this document's contents.
+
+ @return: The encryption scheme used to encrypt this document's
+ contents.
+ @rtype: str
+ """
+ return self._encryption_scheme
+
+ def _set_encryption_scheme(self, encryption_scheme=True):
+ """
+ Set the encryption scheme used to encrypt this document's contents.
+
+ @param encryption_scheme: The encryption scheme.
+ @type encryption_scheme: str
+ """
+ self._encryption_scheme = encryption_scheme
+
+ encryption_scheme = property(
+ _get_encryption_scheme,
+ _set_encryption_scheme,
+ doc="The encryption scheme used to encrypt this document's contents."
+ )
+
def _get_rev(self):
"""
Get the document revision.
@@ -249,13 +290,31 @@ class LeapSyncTarget(HTTPSyncTarget):
# decrypt after receiving from server.
if not self._crypto:
raise NoSoledadCryptoInstance()
+ # all content arriving should be encrypted either with the
+ # user's symmetric key or with the user's public key.
enc_json = json.loads(entry['content'])['_encrypted_json']
- if not self._crypto.is_encrypted_sym(enc_json):
+ plain_json = None
+ if entry['encryption_scheme'] == EncryptionScheme.SYMKEY:
+ if not self._crypto.is_encrypted_sym(enc_json):
+ raise DocumentNotEncrypted(
+ 'Incoming document\'s contents should be '
+ 'encrypted with a symmetric key.')
+ plain_json = self._crypto.decrypt_symmetric(
+ enc_json, self._crypto._symkey)
+ elif entry['encryption_scheme'] == EncryptionScheme.PUBKEY:
+ if not self._crypto.is_encrypted_asym(enc_json):
+ raise DocumentNotEncrypted(
+ 'Incoming document\'s contents should be '
+ 'encrypted to the user\'s public key.')
+ plain_json = self._crypto.decrypt(enc_json)
+ else:
raise DocumentNotEncrypted(
"Incoming document from sync is not encrypted.")
+ # if decryption was OK, then create the document.
doc = LeapDocument(entry['id'], entry['rev'],
- encrypted_json=entry['content'],
- crypto=self._crypto)
+ json=plain_json,
+ crypto=self._crypto,
+ encryption_scheme=EncryptionScheme.NONE)
return_doc_cb(doc, entry['gen'], entry['trans_id'])
if parts[-1] != ']':
try:
@@ -307,8 +366,9 @@ class LeapSyncTarget(HTTPSyncTarget):
raise DocumentNotEncrypted(
"Could not encrypt document before sync.")
size += prepare(id=doc.doc_id, rev=doc.rev,
- content=doc.get_encrypted_json(),
- gen=gen, trans_id=trans_id)
+ content=enc_json,
+ gen=gen, trans_id=trans_id,
+ encryption_scheme=EncryptionSchemes.SYMKEY)
entries.append('\r\n]')
size += len(entries[-1])
self._conn.putheader('content-length', str(size))
diff --git a/src/leap/soledad/backends/sqlcipher.py b/src/leap/soledad/backends/sqlcipher.py
index 288680d4..fb5c3e79 100644
--- a/src/leap/soledad/backends/sqlcipher.py
+++ b/src/leap/soledad/backends/sqlcipher.py
@@ -19,23 +19,22 @@
"""A U1DB backend that uses SQLCipher as its persistence layer."""
import os
-from pysqlcipher import dbapi2
import time
-# TODO: uncomment imports below after solving circular dependency issue
-# between leap_client and soledad.
-#from leap import util
-from u1db.backends import sqlite_backend
-#util.logger.debug(
-# "Monkey-patching u1db.backends.sqlite_backend with pysqlcipher.dbapi2..."
-#)
-sqlite_backend.dbapi2 = dbapi2
+from u1db.backends import sqlite_backend
+from pysqlcipher import dbapi2
from u1db import (
errors,
)
+from leap.soledad.backends.leap_backend import (
+ LeapDocument,
+ EncryptionSchemes,
+)
+
-from leap.soledad.backends.leap_backend import LeapDocument
+# Monkey-patch u1db.backends.sqlite_backend with pysqlcipher.dbapi2
+sqlite_backend.dbapi2 = dbapi2
def open(path, password, create=True, document_factory=None, crypto=None):
@@ -101,12 +100,14 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase):
self._crypto = crypto
def factory(doc_id=None, rev=None, json='{}', has_conflicts=False,
- encrypted_json=None, syncable=True):
+ encrypted_json=None, syncable=True,
+ encryption_scheme=EncryptionSchemes.NONE):
return LeapDocument(doc_id=doc_id, rev=rev, json=json,
has_conflicts=has_conflicts,
encrypted_json=encrypted_json,
+ crypto=self._crypto,
syncable=syncable,
- crypto=self._crypto)
+ encryption_scheme=encryption_scheme)
self.set_document_factory(factory)
def _check_if_db_is_encrypted(self, sqlcipher_file):
@@ -247,6 +248,10 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase):
c.execute(
'ALTER TABLE document '
'ADD COLUMN syncable BOOL NOT NULL DEFAULT TRUE')
+ c.execute(
+ 'ALTER TABLE document '
+ 'ADD COLUMN encryption_scheme TEXT NOT NULL DEFAULT \'%s\'' %
+ EncryptionSchemes.NONE)
def _put_and_update_indexes(self, old_doc, doc):
"""
@@ -260,8 +265,9 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase):
sqlite_backend.SQLitePartialExpandDatabase._put_and_update_indexes(
self, old_doc, doc)
c = self._db_handle.cursor()
- c.execute('UPDATE document SET syncable=? WHERE doc_id=?',
- (doc.syncable, doc.doc_id))
+ c.execute('UPDATE document SET syncable=?, encryption_scheme=? '
+ 'WHERE doc_id=?',
+ (doc.syncable, doc.encryption_scheme, doc.doc_id))
def _get_doc(self, doc_id, check_for_conflicts=False):
"""
@@ -281,9 +287,12 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase):
self, doc_id, check_for_conflicts)
if doc:
c = self._db_handle.cursor()
- c.execute('SELECT syncable FROM document WHERE doc_id=?',
+ c.execute('SELECT syncable, encryption_scheme FROM document '
+ 'WHERE doc_id=?',
(doc.doc_id,))
- doc.syncable = bool(c.fetchone()[0])
+ result = c.fetchone()
+ doc.syncable = bool(result[0])
+ doc.encryption_scheme = result[1]
return doc
sqlite_backend.SQLiteDatabase.register_implementation(SQLCipherDatabase)