diff options
author | Kali Kaneko <kali@leap.se> | 2014-09-22 20:03:45 -0500 |
---|---|---|
committer | Kali Kaneko <kali@leap.se> | 2015-02-11 14:03:17 -0400 |
commit | 9c56adfd27e96c44c12ad5295c42e6b8d9bcad98 (patch) | |
tree | b8cd06b7fb2efd4bb0427a6180b9a09fa4af0570 /client/src/leap/soledad/client/sqlcipher.py | |
parent | 091c2034ee08956541c1111673ebe2f69673f9f8 (diff) |
move public api to its own file
Diffstat (limited to 'client/src/leap/soledad/client/sqlcipher.py')
-rw-r--r-- | client/src/leap/soledad/client/sqlcipher.py | 323 |
1 files changed, 169 insertions, 154 deletions
diff --git a/client/src/leap/soledad/client/sqlcipher.py b/client/src/leap/soledad/client/sqlcipher.py index 613903f7..fcef592d 100644 --- a/client/src/leap/soledad/client/sqlcipher.py +++ b/client/src/leap/soledad/client/sqlcipher.py @@ -53,7 +53,7 @@ from contextlib import contextmanager from collections import defaultdict from httplib import CannotSendRequest -from pysqlcipher import dbapi2 +from pysqlcipher import dbapi2 as sqlcipher_dbapi2 from u1db.backends import sqlite_backend from u1db import errors as u1db_errors from taskthread import TimerTask @@ -71,7 +71,7 @@ from leap.soledad.common.document import SoledadDocument logger = logging.getLogger(__name__) # Monkey-patch u1db.backends.sqlite_backend with pysqlcipher.dbapi2 -sqlite_backend.dbapi2 = dbapi2 +sqlite_backend.dbapi2 = sqlcipher_dbapi2 # It seems that, as long as we are not using old sqlite versions, serialized # mode is enabled by default at compile time. So accessing db connections from @@ -91,11 +91,12 @@ SQLITE_CHECK_SAME_THREAD = False SQLITE_ISOLATION_LEVEL = None +# TODO accept cyrpto object too.... or pass it along.. class SQLCipherOptions(object): def __init__(self, path, key, create=True, is_raw_key=False, cipher='aes-256-cbc', kdf_iter=4000, cipher_page_size=1024, - document_factory=None, defer_encryption=False, - sync_db_key=None): + document_factory=None, + defer_encryption=False, sync_db_key=None): """ Options for the initialization of an SQLCipher database. @@ -140,39 +141,39 @@ class SQLCipherOptions(object): # XXX Use SQLCIpherOptions instead -def open(path, password, create=True, document_factory=None, crypto=None, - raw_key=False, cipher='aes-256-cbc', kdf_iter=4000, - cipher_page_size=1024, defer_encryption=False, sync_db_key=None): - """ - Open a database at the given location. - - *** IMPORTANT *** - - Don't forget to close the database after use by calling the close() - method otherwise some resources might not be freed and you may experience - several kinds of leakages. - - *** IMPORTANT *** - - Will raise u1db.errors.DatabaseDoesNotExist if create=False and the - database does not already exist. - - :return: An instance of Database. - :rtype SQLCipherDatabase - """ - args = (path, password) - kwargs = { - 'create': create, - 'document_factory': document_factory, - 'crypto': crypto, - 'raw_key': raw_key, - 'cipher': cipher, - 'kdf_iter': kdf_iter, - 'cipher_page_size': cipher_page_size, - 'defer_encryption': defer_encryption, - 'sync_db_key': sync_db_key} +#def open(path, password, create=True, document_factory=None, crypto=None, + #raw_key=False, cipher='aes-256-cbc', kdf_iter=4000, + #cipher_page_size=1024, defer_encryption=False, sync_db_key=None): + #""" + #Open a database at the given location. +# + #*** IMPORTANT *** +# + #Don't forget to close the database after use by calling the close() + #method otherwise some resources might not be freed and you may experience + #several kinds of leakages. +# + #*** IMPORTANT *** +# + #Will raise u1db.errors.DatabaseDoesNotExist if create=False and the + #database does not already exist. +# + #:return: An instance of Database. + #:rtype SQLCipherDatabase + #""" + #args = (path, password) + #kwargs = { + #'create': create, + #'document_factory': document_factory, + #'crypto': crypto, + #'raw_key': raw_key, + #'cipher': cipher, + #'kdf_iter': kdf_iter, + #'cipher_page_size': cipher_page_size, + #'defer_encryption': defer_encryption, + #'sync_db_key': sync_db_key} # XXX pass only a CryptoOptions object around - return SQLCipherDatabase.open_database(*args, **kwargs) + #return SQLCipherDatabase.open_database(*args, **kwargs) # @@ -216,9 +217,7 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): """ # XXX Use SQLCIpherOptions instead - def __init__(self, sqlcipher_file, password, document_factory=None, - crypto=None, raw_key=False, cipher='aes-256-cbc', - kdf_iter=4000, cipher_page_size=1024, sync_db_key=None): + def __init__(self, opts): """ Connect to an existing SQLCipher database, creating a new sqlcipher database file if needed. @@ -230,23 +229,28 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): experience several kinds of leakages. *** IMPORTANT *** + + :param opts: + :type opts: SQLCipherOptions """ # ensure the db is encrypted if the file already exists - if os.path.exists(sqlcipher_file): - # XXX pass only a CryptoOptions object around - self.assert_db_is_encrypted( - sqlcipher_file, password, raw_key, cipher, kdf_iter, - cipher_page_size) + if os.path.exists(opts.sqlcipher_file): + self.assert_db_is_encrypted(opts) # connect to the sqlcipher database + # XXX this lock should not be needed ----------------- + # u1db holds a mutex over sqlite internally for the initialization. with self.k_lock: - self._db_handle = dbapi2.connect( - sqlcipher_file, + self._db_handle = sqlcipher_dbapi2.connect( + + # TODO ----------------------------------------------- + # move the init to a single function + opts.sqlcipher_file, isolation_level=SQLITE_ISOLATION_LEVEL, check_same_thread=SQLITE_CHECK_SAME_THREAD) # set SQLCipher cryptographic parameters - # XXX allow optiona deferredChain here ? + # XXX allow optional deferredChain here ? pragmas.set_crypto_pragmas( self._db_handle, password, raw_key, cipher, kdf_iter, cipher_page_size) @@ -260,8 +264,11 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): self._real_replica_uid = None self._ensure_schema() - self._crypto = crypto + self._crypto = opts.crypto + + # TODO ------------------------------------------------ + # Move syncdb to another class ------------------------ # define sync-db attrs self._sqlcipher_file = sqlcipher_file self._sync_db_key = sync_db_key @@ -294,103 +301,122 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): # self._syncers = {'<url>': ('<auth_hash>', syncer), ...} self._syncers = {} - @classmethod - # XXX Use SQLCIpherOptions instead - def _open_database(cls, sqlcipher_file, password, document_factory=None, - crypto=None, raw_key=False, cipher='aes-256-cbc', - kdf_iter=4000, cipher_page_size=1024, - defer_encryption=False, sync_db_key=None): + def _extra_schema_init(self, c): """ - Open a SQLCipher database. + Add any extra fields, etc to the basic table definitions. - :return: The database object. - :rtype: SQLCipherDatabase + This method is called by u1db.backends.sqlite_backend._initialize() + method, which is executed when the database schema is created. Here, + we use it to include the "syncable" property for LeapDocuments. + + :param c: The cursor for querying the database. + :type c: dbapi2.cursor """ - cls.defer_encryption = defer_encryption - if not os.path.isfile(sqlcipher_file): - raise u1db_errors.DatabaseDoesNotExist() + c.execute( + 'ALTER TABLE document ' + 'ADD COLUMN syncable BOOL NOT NULL DEFAULT TRUE') + - tries = 2 + # TODO ---- rescue the fix for the windows case from here... + #@classmethod + # XXX Use SQLCIpherOptions instead + #def _open_database(cls, sqlcipher_file, password, document_factory=None, + #crypto=None, raw_key=False, cipher='aes-256-cbc', + #kdf_iter=4000, cipher_page_size=1024, + #defer_encryption=False, sync_db_key=None): + #""" + #Open a SQLCipher database. +# + #:return: The database object. + #:rtype: SQLCipherDatabase + #""" + #cls.defer_encryption = defer_encryption + #if not os.path.isfile(sqlcipher_file): + #raise u1db_errors.DatabaseDoesNotExist() +# + #tries = 2 # Note: There seems to be a bug in sqlite 3.5.9 (with python2.6) # where without re-opening the database on Windows, it # doesn't see the transaction that was just committed - while True: - - with cls.k_lock: - db_handle = dbapi2.connect( - sqlcipher_file, - check_same_thread=SQLITE_CHECK_SAME_THREAD) - - try: + #while True: +# + #with cls.k_lock: + #db_handle = dbapi2.connect( + #sqlcipher_file, + #check_same_thread=SQLITE_CHECK_SAME_THREAD) +# + #try: # set cryptographic params - +# # XXX pass only a CryptoOptions object around - pragmas.set_crypto_pragmas( - db_handle, password, raw_key, cipher, kdf_iter, - cipher_page_size) - c = db_handle.cursor() + #pragmas.set_crypto_pragmas( + #db_handle, password, raw_key, cipher, kdf_iter, + #cipher_page_size) + #c = db_handle.cursor() # XXX if we use it here, it should be public - v, err = cls._which_index_storage(c) - except Exception as exc: - logger.warning("ERROR OPENING DATABASE!") - logger.debug("error was: %r" % exc) - v, err = None, exc - finally: - db_handle.close() - if v is not None: - break + #v, err = cls._which_index_storage(c) + #except Exception as exc: + #logger.warning("ERROR OPENING DATABASE!") + #logger.debug("error was: %r" % exc) + #v, err = None, exc + #finally: + #db_handle.close() + #if v is not None: + #break # possibly another process is initializing it, wait for it to be # done - if tries == 0: - raise err # go for the richest error? - tries -= 1 - time.sleep(cls.WAIT_FOR_PARALLEL_INIT_HALF_INTERVAL) - return SQLCipherDatabase._sqlite_registry[v]( - sqlcipher_file, password, document_factory=document_factory, - crypto=crypto, raw_key=raw_key, cipher=cipher, kdf_iter=kdf_iter, - cipher_page_size=cipher_page_size, sync_db_key=sync_db_key) - - @classmethod - def open_database(cls, sqlcipher_file, password, create, - document_factory=None, crypto=None, raw_key=False, - cipher='aes-256-cbc', kdf_iter=4000, - cipher_page_size=1024, defer_encryption=False, - sync_db_key=None): + #if tries == 0: + #raise err # go for the richest error? + #tries -= 1 + #time.sleep(cls.WAIT_FOR_PARALLEL_INIT_HALF_INTERVAL) + #return SQLCipherDatabase._sqlite_registry[v]( + #sqlcipher_file, password, document_factory=document_factory, + #crypto=crypto, raw_key=raw_key, cipher=cipher, kdf_iter=kdf_iter, + #cipher_page_size=cipher_page_size, sync_db_key=sync_db_key) + + #@classmethod + #def open_database(cls, sqlcipher_file, password, create, + #document_factory=None, crypto=None, raw_key=False, + #cipher='aes-256-cbc', kdf_iter=4000, + #cipher_page_size=1024, defer_encryption=False, + #sync_db_key=None): # XXX pass only a CryptoOptions object around - """ - Open a SQLCipher database. - - *** IMPORTANT *** - - Don't forget to close the database after use by calling the close() - method otherwise some resources might not be freed and you may - experience several kinds of leakages. - - *** IMPORTANT *** - - :return: The database object. - :rtype: SQLCipherDatabase - """ - cls.defer_encryption = defer_encryption - args = sqlcipher_file, password - kwargs = { - 'crypto': crypto, - 'raw_key': raw_key, - 'cipher': cipher, - 'kdf_iter': kdf_iter, - 'cipher_page_size': cipher_page_size, - 'defer_encryption': defer_encryption, - 'sync_db_key': sync_db_key, - 'document_factory': document_factory, - } - try: - return cls._open_database(*args, **kwargs) - except u1db_errors.DatabaseDoesNotExist: - if not create: - raise - + #""" + #Open a SQLCipher database. +# + #*** IMPORTANT *** +# + #Don't forget to close the database after use by calling the close() + #method otherwise some resources might not be freed and you may + #experience several kinds of leakages. +# + #*** IMPORTANT *** +# + #:return: The database object. + #:rtype: SQLCipherDatabase + #""" + #cls.defer_encryption = defer_encryption + #args = sqlcipher_file, password + #kwargs = { + #'crypto': crypto, + #'raw_key': raw_key, + #'cipher': cipher, + #'kdf_iter': kdf_iter, + #'cipher_page_size': cipher_page_size, + #'defer_encryption': defer_encryption, + #'sync_db_key': sync_db_key, + #'document_factory': document_factory, + #} + #try: + #return cls._open_database(*args, **kwargs) + #except u1db_errors.DatabaseDoesNotExist: + #if not create: + #raise +# # XXX here we were missing sync_db_key, intentional? - return SQLCipherDatabase(*args, **kwargs) + #return SQLCipherDatabase(*args, **kwargs) + + # BEGIN SYNC FOO ---------------------------------------------------------- def sync(self, url, creds=None, autocreate=True, defer_decryption=True): """ @@ -471,7 +497,7 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): def _get_syncer(self, url, creds=None): """ - Get a synchronizer for C{url} using C{creds}. + Get a synchronizer for ``url`` using ``creds``. :param url: The url of the target replica to sync with. :type url: str @@ -504,20 +530,7 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): syncer.num_inserted = 0 return syncer - def _extra_schema_init(self, c): - """ - Add any extra fields, etc to the basic table definitions. - - This method is called by u1db.backends.sqlite_backend._initialize() - method, which is executed when the database schema is created. Here, - we use it to include the "syncable" property for LeapDocuments. - - :param c: The cursor for querying the database. - :type c: dbapi2.cursor - """ - c.execute( - 'ALTER TABLE document ' - 'ADD COLUMN syncable BOOL NOT NULL DEFAULT TRUE') + # END SYNC FOO ---------------------------------------------------------- def _init_sync_db(self): """ @@ -601,8 +614,7 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): :return: The new document revision. :rtype: str """ - doc_rev = sqlite_backend.SQLitePartialExpandDatabase.put_doc( - self, doc) + doc_rev = sqlite_backend.SQLitePartialExpandDatabase.put_doc(self, doc) if self.defer_encryption: self.sync_queue.put_nowait(doc) return doc_rev @@ -644,8 +656,7 @@ 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 FROM document WHERE doc_id=?', (doc.doc_id,)) result = c.fetchone() doc.syncable = bool(result[0]) @@ -691,11 +702,11 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): # backend should raise a DatabaseError exception. sqlite_backend.SQLitePartialExpandDatabase(sqlcipher_file) raise DatabaseIsNotEncrypted() - except dbapi2.DatabaseError: + except sqlcipher_dbapi2.DatabaseError: # assert that we can access it using SQLCipher with the given # key with cls.k_lock: - db_handle = dbapi2.connect( + db_handle = sqlcipher_dbapi2.connect( sqlcipher_file, isolation_level=SQLITE_ISOLATION_LEVEL, check_same_thread=SQLITE_CHECK_SAME_THREAD) @@ -750,8 +761,8 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): )) try: c.execute(statement, tuple(args)) - except dbapi2.OperationalError, e: - raise dbapi2.OperationalError( + except sqlcipher_dbapi2.OperationalError, e: + raise sqlcipher_dbapi2.OperationalError( str(e) + '\nstatement: %s\nargs: %s\n' % (statement, args)) res = c.fetchall() return res[0][0] @@ -760,6 +771,8 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): """ Close db_handle and close syncer. """ + # TODO separate db from syncers -------------- + if logger is not None: # logger might be none if called from __del__ logger.debug("Sqlcipher backend: closing") # stop the sync watcher for deferred encryption @@ -780,6 +793,8 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): if self._db_handle is not None: self._db_handle.close() self._db_handle = None + + # --------------------------------------- # close the sync database if self._sync_db is not None: self._sync_db.close() |