From 4c74a91ad905e7d59260ccec789930c16b29a62d Mon Sep 17 00:00:00 2001 From: drebs Date: Tue, 23 Apr 2013 12:52:06 -0300 Subject: Add encryption_scheme property to LeapDocument. --- src/leap/soledad/backends/couch.py | 9 +++- src/leap/soledad/backends/leap_backend.py | 72 ++++++++++++++++++++++++++++--- src/leap/soledad/backends/sqlcipher.py | 41 +++++++++++------- 3 files changed, 98 insertions(+), 24 deletions(-) (limited to 'src/leap/soledad/backends') 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) -- cgit v1.2.3