diff options
-rw-r--r-- | CHANGELOG | 4 | ||||
-rw-r--r-- | client/pkg/requirements.pip | 2 | ||||
-rw-r--r-- | client/src/leap/soledad/client/__init__.py | 290 | ||||
-rw-r--r-- | client/src/leap/soledad/client/auth.py | 24 | ||||
-rw-r--r-- | client/src/leap/soledad/client/crypto.py | 62 | ||||
-rw-r--r-- | client/src/leap/soledad/client/shared_db.py | 58 | ||||
-rw-r--r-- | client/src/leap/soledad/client/sqlcipher.py | 290 | ||||
-rw-r--r-- | client/src/leap/soledad/client/target.py | 132 |
8 files changed, 452 insertions, 410 deletions
@@ -1,3 +1,7 @@ +0.4.1 Oct 4: +Client: + o Save only UTF8 strings. Related to #3660. + 0.4.0 Sep 20: Client: o Remove redundant logging when creating data dirs. diff --git a/client/pkg/requirements.pip b/client/pkg/requirements.pip index df6ddb94..8733f320 100644 --- a/client/pkg/requirements.pip +++ b/client/pkg/requirements.pip @@ -20,3 +20,5 @@ oauth # pysqlite should not be a dep, see #2945 pysqlite + +cchardet
\ No newline at end of file diff --git a/client/src/leap/soledad/client/__init__.py b/client/src/leap/soledad/client/__init__.py index 4c6a41fc..13a3b68f 100644 --- a/client/src/leap/soledad/client/__init__.py +++ b/client/src/leap/soledad/client/__init__.py @@ -32,6 +32,8 @@ import socket import ssl import urlparse +import cchardet + from hashlib import sha256 from u1db.remote import http_client @@ -226,25 +228,26 @@ class Soledad(object): """ Initialize configuration, cryptographic keys and dbs. - @param uuid: User's uuid. - @type uuid: str - @param passphrase: The passphrase for locking and unlocking encryption - secrets for local and remote storage. - @type passphrase: str - @param secrets_path: Path for storing encrypted key used for - symmetric encryption. - @type secrets_path: str - @param local_db_path: Path for local encrypted storage db. - @type local_db_path: str - @param server_url: URL for Soledad server. This is used either to sync + :param uuid: User's uuid. + :type uuid: str + :param passphrase: The passphrase for locking and unlocking encryption + secrets for local and remote storage. + :type passphrase: str + :param secrets_path: Path for storing encrypted key used for + symmetric encryption. + :type secrets_path: str + :param local_db_path: Path for local encrypted storage db. + :type local_db_path: str + :param server_url: URL for Soledad server. This is used either to sync with the user's remote db and to interact with the shared recovery database. - @type server_url: str - @param cert_file: Path to the SSL certificate to use in the - connection to the server_url. - @type cert_file: str - @param auth_token: Authorization token for accessing remote databases. - @type auth_token: str + :type server_url: str + :param cert_file: Path to the certificate of the ca used + to validate the SSL certificate used by the remote + soledad server. + :type cert_file: str + :param auth_token: Authorization token for accessing remote databases. + :type auth_token: str """ # get config params self._uuid = uuid @@ -339,7 +342,7 @@ class Soledad(object): """ Create work directories. - @raise OSError: in case file exists and is not a dir. + :raise OSError: in case file exists and is not a dir. """ paths = map( lambda x: os.path.dirname(x), @@ -418,8 +421,8 @@ class Soledad(object): Storage secret is encrypted before being stored. This method decrypts and returns the stored secret. - @return: The storage secret. - @rtype: str + :return: The storage secret. + :rtype: str """ # calculate the encryption key key = scrypt.hash( @@ -478,8 +481,8 @@ class Soledad(object): """ Return whether there is a storage secret available for use or not. - @return: Whether there's a storage secret for symmetric encryption. - @rtype: bool + :return: Whether there's a storage secret for symmetric encryption. + :rtype: bool """ if self._secret_id is None or self._secret_id not in self._secrets: try: @@ -515,8 +518,8 @@ class Soledad(object): } } - @return: The id of the generated secret. - @rtype: str + :return: The id of the generated secret. + :rtype: str """ signal(SOLEDAD_CREATING_KEYS, self._uuid) # generate random secret @@ -571,10 +574,10 @@ class Soledad(object): """ Change the passphrase that encrypts the storage secret. - @param new_passphrase: The new passphrase. - @type new_passphrase: str + :param new_passphrase: The new passphrase. + :type new_passphrase: str - @raise NoStorageSecret: Raised if there's no storage secret available. + :raise NoStorageSecret: Raised if there's no storage secret available. """ # maybe we want to add more checks to guarantee passphrase is # reasonable? @@ -616,8 +619,8 @@ class Soledad(object): Calculate a hash for storing/retrieving key material on shared database, based on user's uuid. - @return: the hash - @rtype: str + :return: the hash + :rtype: str """ return sha256( '%s%s' % ( @@ -639,8 +642,8 @@ class Soledad(object): Retrieve the document with encrypted key material from the shared database. - @return: a document with encrypted key material in its contents - @rtype: SoledadDocument + :return: a document with encrypted key material in its contents + :rtype: SoledadDocument """ signal(SOLEDAD_DOWNLOADING_KEYS, self._uuid) db = self._shared_db() @@ -687,11 +690,11 @@ class Soledad(object): """ Update a document in the local encrypted database. - @param doc: the document to update - @type doc: SoledadDocument + :param doc: the document to update + :type doc: SoledadDocument - @return: the new revision identifier for the document - @rtype: str + :return: the new revision identifier for the document + :rtype: str """ return self._db.put_doc(doc) @@ -699,11 +702,11 @@ class Soledad(object): """ Delete a document from the local encrypted database. - @param doc: the document to delete - @type doc: SoledadDocument + :param doc: the document to delete + :type doc: SoledadDocument - @return: the new revision identifier for the document - @rtype: str + :return: the new revision identifier for the document + :rtype: str """ return self._db.delete_doc(doc) @@ -711,15 +714,15 @@ class Soledad(object): """ Retrieve a document from the local encrypted database. - @param doc_id: the unique document identifier - @type doc_id: str - @param include_deleted: if True, deleted documents will be - returned with empty content; otherwise asking for a deleted - document will return None - @type include_deleted: bool + :param doc_id: the unique document identifier + :type doc_id: str + :param include_deleted: if True, deleted documents will be + returned with empty content; otherwise asking + for a deleted document will return None + :type include_deleted: bool - @return: the document object or None - @rtype: SoledadDocument + :return: the document object or None + :rtype: SoledadDocument """ return self._db.get_doc(doc_id, include_deleted=include_deleted) @@ -728,15 +731,15 @@ class Soledad(object): """ Get the content for many documents. - @param doc_ids: a list of document identifiers - @type doc_ids: list - @param check_for_conflicts: if set False, then the conflict check will + :param doc_ids: a list of document identifiers + :type doc_ids: list + :param check_for_conflicts: if set False, then the conflict check will be skipped, and 'None' will be returned instead of True/False - @type check_for_conflicts: bool + :type check_for_conflicts: bool - @return: iterable giving the Document object for each document id + :return: iterable giving the Document object for each document id in matching doc_ids order. - @rtype: generator + :rtype: generator """ return self._db.get_docs(doc_ids, check_for_conflicts=check_for_conflicts, @@ -745,28 +748,59 @@ class Soledad(object): def get_all_docs(self, include_deleted=False): """Get the JSON content for all documents in the database. - @param include_deleted: If set to True, deleted documents will be - returned with empty content. Otherwise deleted documents will not - be included in the results. - @return: (generation, [Document]) - The current generation of the database, followed by a list of all - the documents in the database. + :param include_deleted: If set to True, deleted documents will be + returned with empty content. Otherwise deleted + documents will not be included in the results. + :return: (generation, [Document]) + The current generation of the database, followed by a list of + all the documents in the database. """ return self._db.get_all_docs(include_deleted) + def _convert_to_utf8(self, content): + """ + Converts content to utf8 (or all the strings in content) + + NOTE: Even though this method supports any type, it will + currently ignore contents of lists, tuple or any other + iterable than dict. We don't need support for these at the + moment + + :param content: content to convert + :type content: object + + :rtype: object + """ + + if isinstance(content, unicode): + return content + elif isinstance(content, str): + try: + result = cchardet.detect(content) + content = content.decode(result["encoding"]).encode("utf-8")\ + .decode("utf-8") + except UnicodeError: + pass + return content + else: + if isinstance(content, dict): + for key in content.keys(): + content[key] = self._convert_to_utf8(content[key]) + return content + def create_doc(self, content, doc_id=None): """ Create a new document in the local encrypted database. - @param content: the contents of the new document - @type content: dict - @param doc_id: an optional identifier specifying the document id - @type doc_id: str + :param content: the contents of the new document + :type content: dict + :param doc_id: an optional identifier specifying the document id + :type doc_id: str - @return: the new document - @rtype: SoledadDocument + :return: the new document + :rtype: SoledadDocument """ - return self._db.create_doc(content, doc_id=doc_id) + return self._db.create_doc(self._convert_to_utf8(content), doc_id=doc_id) def create_doc_from_json(self, json, doc_id=None): """ @@ -778,12 +812,12 @@ class Soledad(object): If the database specifies a maximum document size and the document exceeds it, create will fail and raise a DocumentTooBig exception. - @param json: The JSON document string - @type json: str - @param doc_id: An optional identifier specifying the document id. - @type doc_id: - @return: The new cocument - @rtype: SoledadDocument + :param json: The JSON document string + :type json: str + :param doc_id: An optional identifier specifying the document id. + :type doc_id: + :return: The new cocument + :rtype: SoledadDocument """ return self._db.create_doc_from_json(json, doc_id=doc_id) @@ -796,11 +830,11 @@ class Soledad(object): Creating an index will block until the expressions have been evaluated and the index generated. - @param index_name: A unique name which can be used as a key prefix - @type index_name: str - @param index_expressions: index expressions defining the index - information. - @type index_expressions: dict + :param index_name: A unique name which can be used as a key prefix + :type index_name: str + :param index_expressions: index expressions defining the index + information. + :type index_expressions: dict Examples: @@ -816,8 +850,8 @@ class Soledad(object): """ Remove a named index. - @param index_name: The name of the index we are removing - @type index_name: str + :param index_name: The name of the index we are removing + :type index_name: str """ if self._db: return self._db.delete_index(index_name) @@ -826,8 +860,8 @@ class Soledad(object): """ List the definitions of all known indexes. - @return: A list of [('index-name', ['field', 'field2'])] definitions. - @rtype: list + :return: A list of [('index-name', ['field', 'field2'])] definitions. + :rtype: list """ if self._db: return self._db.list_indexes() @@ -843,14 +877,14 @@ class Soledad(object): It is also possible to append a '*' to the last supplied value (eg 'val*', '*', '*' or 'val', 'val*', '*', but not 'val*', 'val', '*') - @param index_name: The index to query - @type index_name: str - @param key_values: values to match. eg, if you have - an index with 3 fields then you would have: - get_from_index(index_name, val1, val2, val3) - @type key_values: tuple - @return: List of [Document] - @rtype: list + :param index_name: The index to query + :type index_name: str + :param key_values: values to match. eg, if you have + an index with 3 fields then you would have: + get_from_index(index_name, val1, val2, val3) + :type key_values: tuple + :return: List of [Document] + :rtype: list """ if self._db: return self._db.get_from_index(index_name, *key_values) @@ -869,18 +903,18 @@ class Soledad(object): possible to append a '*' to the last supplied value (eg 'val*', '*', '*' or 'val', 'val*', '*', but not 'val*', 'val', '*') - @param index_name: The index to query - @type index_name: str - @param start_values: tuples of values that define the lower bound of + :param index_name: The index to query + :type index_name: str + :param start_values: tuples of values that define the lower bound of the range. eg, if you have an index with 3 fields then you would have: (val1, val2, val3) - @type start_values: tuple - @param end_values: tuples of values that define the upper bound of the + :type start_values: tuple + :param end_values: tuples of values that define the upper bound of the range. eg, if you have an index with 3 fields then you would have: (val1, val2, val3) - @type end_values: tuple - @return: List of [Document] - @rtype: list + :type end_values: tuple + :return: List of [Document] + :rtype: list """ if self._db: return self._db.get_range_from_index( @@ -890,10 +924,10 @@ class Soledad(object): """ Return all keys under which documents are indexed in this index. - @param index_name: The index to query - @type index_name: str - @return: [] A list of tuples of indexed keys. - @rtype: list + :param index_name: The index to query + :type index_name: str + :return: [] A list of tuples of indexed keys. + :rtype: list """ if self._db: return self._db.get_index_keys(index_name) @@ -902,11 +936,11 @@ class Soledad(object): """ Get the list of conflicts for the given document. - @param doc_id: the document id - @type doc_id: str + :param doc_id: the document id + :type doc_id: str - @return: a list of the document entries that are conflicted - @rtype: list + :return: a list of the document entries that are conflicted + :rtype: list """ if self._db: return self._db.get_doc_conflicts(doc_id) @@ -915,11 +949,11 @@ class Soledad(object): """ Mark a document as no longer conflicted. - @param doc: a document with the new content to be inserted. - @type doc: SoledadDocument - @param conflicted_doc_revs: a list of revisions that the new content - supersedes. - @type conflicted_doc_revs: list + :param doc: a document with the new content to be inserted. + :type doc: SoledadDocument + :param conflicted_doc_revs: a list of revisions that the new content + supersedes. + :type conflicted_doc_revs: list """ if self._db: return self._db.resolve_doc(doc, conflicted_doc_revs) @@ -928,12 +962,12 @@ class Soledad(object): """ Synchronize the local encrypted replica with a remote replica. - @param url: the url of the target replica to sync with - @type url: str + :param url: the url of the target replica to sync with + :type url: str - @return: the local generation before the synchronisation was + :return: the local generation before the synchronisation was performed. - @rtype: str + :rtype: str """ if self._db: local_gen = self._db.sync( @@ -946,11 +980,11 @@ class Soledad(object): """ Return if local db replica differs from remote url's replica. - @param url: The remote replica to compare with local replica. - @type url: str + :param url: The remote replica to compare with local replica. + :type url: str - @return: Whether remote replica and local replica differ. - @rtype: bool + :return: Whether remote replica and local replica differ. + :rtype: bool """ target = SoledadSyncTarget(url, creds=self._creds, crypto=self._crypto) info = target.get_sync_info(self._db._get_replica_uid()) @@ -972,8 +1006,8 @@ class Soledad(object): 'token': '<token>' } - @param token: The authentication token. - @type token: str + :param token: The authentication token. + :type token: str """ self._creds = { 'token': { @@ -1004,11 +1038,11 @@ class Soledad(object): self.UUID_KEY: '<uuid>', # (optional) } - @param include_uuid: Should the uuid be included? - @type include_uuid: bool + :param include_uuid: Should the uuid be included? + :type include_uuid: bool - @return: The recovery document. - @rtype: dict + :return: The recovery document. + :rtype: dict """ data = {self.STORAGE_SECRETS_KEY: self._secrets} if include_uuid: @@ -1027,8 +1061,8 @@ class Soledad(object): self.UUID_KEY: '<uuid>', # (optional) } - @param data: The recovery document. - @type data: dict + :param data: The recovery document. + :type data: dict """ # include new secrets in our secret pool. for secret_id, secret_data in data[self.STORAGE_SECRETS_KEY].items(): @@ -1101,7 +1135,9 @@ SOLEDAD_TIMEOUT = 10 class VerifiedHTTPSConnection(httplib.HTTPSConnection): - """HTTPSConnection verifying server side certificates.""" + """ + HTTPSConnection verifying server side certificates. + """ # derived from httplib.py def connect(self): diff --git a/client/src/leap/soledad/client/auth.py b/client/src/leap/soledad/client/auth.py index 3cd6dabe..d85e3ba6 100644 --- a/client/src/leap/soledad/client/auth.py +++ b/client/src/leap/soledad/client/auth.py @@ -37,10 +37,10 @@ class TokenBasedAuth(object): """ Store given credentials so we can sign the request later. - @param uuid: The user's uuid. - @type uuid: str - @param token: The authentication token. - @type token: str + :param uuid: The user's uuid. + :type uuid: str + :param token: The authentication token. + :type token: str """ self._creds = {'token': (uuid, token)} @@ -51,15 +51,15 @@ class TokenBasedAuth(object): [('Authorization', 'Token <base64 encoded creds')] - @param method: The HTTP method. - @type method: str - @param url_query: The URL query string. - @type url_query: str - @param params: A list with encoded query parameters. - @type param: list + :param method: The HTTP method. + :type method: str + :param url_query: The URL query string. + :type url_query: str + :param params: A list with encoded query parameters. + :type param: list - @return: The Authorization header. - @rtype: list of tuple + :return: The Authorization header. + :rtype: list of tuple """ if 'token' in self._creds: uuid, token = self._creds['token'] diff --git a/client/src/leap/soledad/client/crypto.py b/client/src/leap/soledad/client/crypto.py index 9fcff8e9..a6372107 100644 --- a/client/src/leap/soledad/client/crypto.py +++ b/client/src/leap/soledad/client/crypto.py @@ -70,8 +70,8 @@ class SoledadCrypto(object): """ Initialize the crypto object. - @param soledad: A Soledad instance for key lookup. - @type soledad: leap.soledad.Soledad + :param soledad: A Soledad instance for key lookup. + :type soledad: leap.soledad.Soledad """ self._soledad = soledad @@ -82,15 +82,15 @@ class SoledadCrypto(object): Currently, the only encryption method supported is AES-256 CTR mode. - @param data: The data to be encrypted. - @type data: str - @param key: The key used to encrypt C{data} (must be 256 bits long). - @type key: str - @param method: The encryption method to use. - @type method: str + :param data: The data to be encrypted. + :type data: str + :param key: The key used to encrypt C{data} (must be 256 bits long). + :type key: str + :param method: The encryption method to use. + :type method: str - @return: A tuple with the initial value and the encrypted data. - @rtype: (long, str) + :return: A tuple with the initial value and the encrypted data. + :rtype: (long, str) """ soledad_assert_type(key, str) @@ -119,17 +119,17 @@ class SoledadCrypto(object): Currently, the only encryption method supported is AES-256 CTR mode. - @param data: The data to be decrypted. - @type data: str - @param key: The key used to decrypt C{data} (must be 256 bits long). - @type key: str - @param method: The encryption method to use. - @type method: str - @param kwargs: Other parameters specific to each encryption method. - @type kwargs: dict - - @return: The decrypted data. - @rtype: str + :param data: The data to be decrypted. + :type data: str + :param key: The key used to decrypt C{data} (must be 256 bits long). + :type key: str + :param method: The encryption method to use. + :type method: str + :param kwargs: Other parameters specific to each encryption method. + :type kwargs: dict + + :return: The decrypted data. + :rtype: str """ soledad_assert_type(key, str) # assert params @@ -160,14 +160,14 @@ class SoledadCrypto(object): secret stripped from the first MAC_KEY_LENGTH characters. The HMAC message is C{doc_id}. - @param doc_id: The id of the document that will be encrypted using + :param doc_id: The id of the document that will be encrypted using this passphrase. - @type doc_id: str + :type doc_id: str - @return: The passphrase. - @rtype: str + :return: The passphrase. + :rtype: str - @raise NoSymmetricSecret: if no symmetric secret was supplied. + :raise NoSymmetricSecret: if no symmetric secret was supplied. """ if self.secret is None: raise NoSymmetricSecret() @@ -187,13 +187,13 @@ class SoledadCrypto(object): function. The key used for HMAC is the first MAC_KEY_LENGTH characters of Soledad's storage secret. The HMAC message is C{doc_id}. - @param doc_id: The id of the document. - @type doc_id: str + :param doc_id: The id of the document. + :type doc_id: str - @return: The key. - @rtype: str + :return: The key. + :rtype: str - @raise NoSymmetricSecret: if no symmetric secret was supplied. + :raise NoSymmetricSecret: if no symmetric secret was supplied. """ if self.secret is None: raise NoSymmetricSecret() diff --git a/client/src/leap/soledad/client/shared_db.py b/client/src/leap/soledad/client/shared_db.py index a6ca504d..adcde4e2 100644 --- a/client/src/leap/soledad/client/shared_db.py +++ b/client/src/leap/soledad/client/shared_db.py @@ -61,10 +61,10 @@ class SoledadSharedDatabase(http_database.HTTPDatabase, TokenBasedAuth): """ Store given credentials so we can sign the request later. - @param uuid: The user's uuid. - @type uuid: str - @param token: The authentication token. - @type token: str + :param uuid: The user's uuid. + :type uuid: str + :param token: The authentication token. + :type token: str """ TokenBasedAuth.set_token_credentials(self, uuid, token) @@ -72,15 +72,15 @@ class SoledadSharedDatabase(http_database.HTTPDatabase, TokenBasedAuth): """ Return an authorization header to be included in the HTTP request. - @param method: The HTTP method. - @type method: str - @param url_query: The URL query string. - @type url_query: str - @param params: A list with encoded query parameters. - @type param: list + :param method: The HTTP method. + :type method: str + :param url_query: The URL query string. + :type url_query: str + :param params: A list with encoded query parameters. + :type param: list - @return: The Authorization header. - @rtype: list of tuple + :return: The Authorization header. + :rtype: list of tuple """ return TokenBasedAuth._sign_request(self, method, url_query, params) @@ -95,16 +95,16 @@ class SoledadSharedDatabase(http_database.HTTPDatabase, TokenBasedAuth): """ Open a Soledad shared database. - @param url: URL of the remote database. - @type url: str - @param create: Should the database be created if it does not already + :param url: URL of the remote database. + :type url: str + :param create: Should the database be created if it does not already exist? - @type create: bool - @param token: An authentication token for accessing the shared db. - @type token: str + :type create: bool + :param token: An authentication token for accessing the shared db. + :type token: str - @return: The shared database in the given url. - @rtype: SoledadSharedDatabase + :return: The shared database in the given url. + :rtype: SoledadSharedDatabase """ db = SoledadSharedDatabase(url, creds=creds) db.open(create) @@ -115,10 +115,10 @@ class SoledadSharedDatabase(http_database.HTTPDatabase, TokenBasedAuth): """ Dummy method that prevents from deleting shared database. - @raise: This will always raise an Unauthorized exception. + :raise: This will always raise an Unauthorized exception. - @param url: The database URL. - @type url: str + :param url: The database URL. + :type url: str """ raise Unauthorized("Can't delete shared database.") @@ -126,13 +126,13 @@ class SoledadSharedDatabase(http_database.HTTPDatabase, TokenBasedAuth): """ Initialize database with auth token and encryption powers. - @param url: URL of the remote database. - @type url: str - @param document_factory: A factory for U1BD documents. - @type document_factory: u1db.Document - @param creds: A tuple containing the authentication method and + :param url: URL of the remote database. + :type url: str + :param document_factory: A factory for U1BD documents. + :type document_factory: u1db.Document + :param creds: A tuple containing the authentication method and credentials. - @type creds: tuple + :type creds: tuple """ http_database.HTTPDatabase.__init__(self, url, document_factory, creds) diff --git a/client/src/leap/soledad/client/sqlcipher.py b/client/src/leap/soledad/client/sqlcipher.py index c605c28c..3e01a4fb 100644 --- a/client/src/leap/soledad/client/sqlcipher.py +++ b/client/src/leap/soledad/client/sqlcipher.py @@ -79,29 +79,29 @@ def open(path, password, create=True, document_factory=None, crypto=None, Will raise u1db.errors.DatabaseDoesNotExist if create=False and the database does not already exist. - @param path: The filesystem path for the database to open. - @param type: str - @param create: True/False, should the database be created if it doesn't + :param path: The filesystem path for the database to open. + :param type: str + :param create: True/False, should the database be created if it doesn't already exist? - @param type: bool - @param document_factory: A function that will be called with the same + :param type: bool + :param document_factory: A function that will be called with the same parameters as Document.__init__. - @type document_factory: callable - @param crypto: An instance of SoledadCrypto so we can encrypt/decrypt + :type document_factory: callable + :param crypto: An instance of SoledadCrypto so we can encrypt/decrypt document contents when syncing. - @type crypto: soledad.crypto.SoledadCrypto - @param raw_key: Whether C{password} is a raw 64-char hex string or a + :type crypto: soledad.crypto.SoledadCrypto + :param raw_key: Whether C{password} is a raw 64-char hex string or a passphrase that should be hashed to obtain the encyrption key. - @type raw_key: bool - @param cipher: The cipher and mode to use. - @type cipher: str - @param kdf_iter: The number of iterations to use. - @type kdf_iter: int - @param cipher_page_size: The page size. - @type cipher_page_size: int - - @return: An instance of Database. - @rtype SQLCipherDatabase + :type raw_key: bool + :param cipher: The cipher and mode to use. + :type cipher: str + :param kdf_iter: The number of iterations to use. + :type kdf_iter: int + :param cipher_page_size: The page size. + :type cipher_page_size: int + + :return: An instance of Database. + :rtype SQLCipherDatabase """ return SQLCipherDatabase.open_database( path, password, create=create, document_factory=document_factory, @@ -143,25 +143,25 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): """ Create a new sqlcipher file. - @param sqlcipher_file: The path for the SQLCipher file. - @type sqlcipher_file: str - @param password: The password that protects the SQLCipher db. - @type password: str - @param document_factory: A function that will be called with the same + :param sqlcipher_file: The path for the SQLCipher file. + :type sqlcipher_file: str + :param password: The password that protects the SQLCipher db. + :type password: str + :param document_factory: A function that will be called with the same parameters as Document.__init__. - @type document_factory: callable - @param crypto: An instance of SoledadCrypto so we can encrypt/decrypt + :type document_factory: callable + :param crypto: An instance of SoledadCrypto so we can encrypt/decrypt document contents when syncing. - @type crypto: soledad.crypto.SoledadCrypto - @param raw_key: Whether C{password} is a raw 64-char hex string or a + :type crypto: soledad.crypto.SoledadCrypto + :param raw_key: Whether C{password} is a raw 64-char hex string or a passphrase that should be hashed to obtain the encyrption key. - @type raw_key: bool - @param cipher: The cipher and mode to use. - @type cipher: str - @param kdf_iter: The number of iterations to use. - @type kdf_iter: int - @param cipher_page_size: The page size. - @type cipher_page_size: int + :type raw_key: bool + :param cipher: The cipher and mode to use. + :type cipher: str + :param kdf_iter: The number of iterations to use. + :type kdf_iter: int + :param cipher_page_size: The page size. + :type cipher_page_size: int """ # ensure the db is encrypted if the file already exists if os.path.exists(sqlcipher_file): @@ -195,28 +195,28 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): """ Open a SQLCipher database. - @param sqlcipher_file: The path for the SQLCipher file. - @type sqlcipher_file: str - @param password: The password that protects the SQLCipher db. - @type password: str - @param document_factory: A function that will be called with the same + :param sqlcipher_file: The path for the SQLCipher file. + :type sqlcipher_file: str + :param password: The password that protects the SQLCipher db. + :type password: str + :param document_factory: A function that will be called with the same parameters as Document.__init__. - @type document_factory: callable - @param crypto: An instance of SoledadCrypto so we can encrypt/decrypt + :type document_factory: callable + :param crypto: An instance of SoledadCrypto so we can encrypt/decrypt document contents when syncing. - @type crypto: soledad.crypto.SoledadCrypto - @param raw_key: Whether C{password} is a raw 64-char hex string or a + :type crypto: soledad.crypto.SoledadCrypto + :param raw_key: Whether C{password} is a raw 64-char hex string or a passphrase that should be hashed to obtain the encyrption key. - @type raw_key: bool - @param cipher: The cipher and mode to use. - @type cipher: str - @param kdf_iter: The number of iterations to use. - @type kdf_iter: int - @param cipher_page_size: The page size. - @type cipher_page_size: int + :type raw_key: bool + :param cipher: The cipher and mode to use. + :type cipher: str + :param kdf_iter: The number of iterations to use. + :type kdf_iter: int + :param cipher_page_size: The page size. + :type cipher_page_size: int - @return: The database object. - @rtype: SQLCipherDatabase + :return: The database object. + :rtype: SQLCipherDatabase """ if not os.path.isfile(sqlcipher_file): raise u1db_errors.DatabaseDoesNotExist() @@ -267,33 +267,33 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): """ Open a SQLCipher database. - @param sqlcipher_file: The path for the SQLCipher file. - @type sqlcipher_file: str - @param password: The password that protects the SQLCipher db. - @type password: str - @param create: Should the datbase be created if it does not already + :param sqlcipher_file: The path for the SQLCipher file. + :type sqlcipher_file: str + :param password: The password that protects the SQLCipher db. + :type password: str + :param create: Should the datbase be created if it does not already exist? - @type: bool - @param backend_cls: A class to use as backend. - @type backend_cls: type - @param document_factory: A function that will be called with the same + :type: bool + :param backend_cls: A class to use as backend. + :type backend_cls: type + :param document_factory: A function that will be called with the same parameters as Document.__init__. - @type document_factory: callable - @param crypto: An instance of SoledadCrypto so we can encrypt/decrypt + :type document_factory: callable + :param crypto: An instance of SoledadCrypto so we can encrypt/decrypt document contents when syncing. - @type crypto: soledad.crypto.SoledadCrypto - @param raw_key: Whether C{password} is a raw 64-char hex string or a + :type crypto: soledad.crypto.SoledadCrypto + :param raw_key: Whether C{password} is a raw 64-char hex string or a passphrase that should be hashed to obtain the encyrption key. - @type raw_key: bool - @param cipher: The cipher and mode to use. - @type cipher: str - @param kdf_iter: The number of iterations to use. - @type kdf_iter: int - @param cipher_page_size: The page size. - @type cipher_page_size: int + :type raw_key: bool + :param cipher: The cipher and mode to use. + :type cipher: str + :param kdf_iter: The number of iterations to use. + :type kdf_iter: int + :param cipher_page_size: The page size. + :type cipher_page_size: int - @return: The database object. - @rtype: SQLCipherDatabase + :return: The database object. + :rtype: SQLCipherDatabase """ try: return cls._open_database( @@ -316,16 +316,16 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): """ Synchronize documents with remote replica exposed at url. - @param url: The url of the target replica to sync with. - @type url: str - @param creds: optional dictionary giving credentials. + :param url: The url of the target replica to sync with. + :type url: str + :param creds: optional dictionary giving credentials. to authorize the operation with the server. - @type creds: dict - @param autocreate: Ask the target to create the db if non-existent. - @type autocreate: bool + :type creds: dict + :param autocreate: Ask the target to create the db if non-existent. + :type autocreate: bool - @return: The local generation before the synchronisation was performed. - @rtype: int + :return: The local generation before the synchronisation was performed. + :rtype: int """ from u1db.sync import Synchronizer from leap.soledad.client.target import SoledadSyncTarget @@ -343,8 +343,8 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): 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 + :param c: The cursor for querying the database. + :type c: dbapi2.cursor """ c.execute( 'ALTER TABLE document ' @@ -354,10 +354,10 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): """ Update a document and all indexes related to it. - @param old_doc: The old version of the document. - @type old_doc: u1db.Document - @param doc: The new version of the document. - @type doc: u1db.Document + :param old_doc: The old version of the document. + :type old_doc: u1db.Document + :param doc: The new version of the document. + :type doc: u1db.Document """ sqlite_backend.SQLitePartialExpandDatabase._put_and_update_indexes( self, old_doc, doc) @@ -370,15 +370,15 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): """ Get just the document content, without fancy handling. - @param doc_id: The unique document identifier - @type doc_id: str - @param include_deleted: If set to True, deleted documents will be + :param doc_id: The unique document identifier + :type doc_id: str + :param include_deleted: If set to True, deleted documents will be returned with empty content. Otherwise asking for a deleted document will return None. - @type include_deleted: bool + :type include_deleted: bool - @return: a Document object. - @type: u1db.Document + :return: a Document object. + :type: u1db.Document """ doc = sqlite_backend.SQLitePartialExpandDatabase._get_doc( self, doc_id, check_for_conflicts) @@ -411,19 +411,19 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): which will attempt to read the first page of the database and will parse the schema. - @param sqlcipher_file: The path for the SQLCipher file. - @type sqlcipher_file: str - @param key: The key that protects the SQLCipher db. - @type key: str - @param raw_key: Whether C{key} is a raw 64-char hex string or a + :param sqlcipher_file: The path for the SQLCipher file. + :type sqlcipher_file: str + :param key: The key that protects the SQLCipher db. + :type key: str + :param raw_key: Whether C{key} is a raw 64-char hex string or a passphrase that should be hashed to obtain the encyrption key. - @type raw_key: bool - @param cipher: The cipher and mode to use. - @type cipher: str - @param kdf_iter: The number of iterations to use. - @type kdf_iter: int - @param cipher_page_size: The page size. - @type cipher_page_size: int + :type raw_key: bool + :param cipher: The cipher and mode to use. + :type cipher: str + :param kdf_iter: The number of iterations to use. + :type kdf_iter: int + :param cipher_page_size: The page size. + :type cipher_page_size: int """ try: # try to open an encrypted database with the regular u1db @@ -473,11 +473,11 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): * PRAGMA key should generally be called as the first operation on a database. - @param key: The key for use with the database. - @type key: str - @param raw_key: Whether C{key} is a raw 64-char hex string or a + :param key: The key for use with the database. + :type key: str + :param raw_key: Whether C{key} is a raw 64-char hex string or a passphrase that should be hashed to obtain the encyrption key. - @type raw_key: bool + :type raw_key: bool """ if raw_key: cls._pragma_key_raw(db_handle, key) @@ -495,10 +495,10 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): if you want to do so you should use a raw key instead and derive the key using your own KDF. - @param db_handle: A handle to the SQLCipher database. - @type db_handle: pysqlcipher.Connection - @param passphrase: The passphrase used to derive the encryption key. - @type passphrase: str + :param db_handle: A handle to the SQLCipher database. + :type db_handle: pysqlcipher.Connection + :param passphrase: The passphrase used to derive the encryption key. + :type passphrase: str """ db_handle.cursor().execute("PRAGMA key = '%s'" % passphrase) @@ -512,10 +512,10 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): ensure that the data provided is a 64 character hex string, which will be converted directly to 32 bytes (256 bits) of key data. - @param db_handle: A handle to the SQLCipher database. - @type db_handle: pysqlcipher.Connection - @param key: A 64 character hex string. - @type key: str + :param db_handle: A handle to the SQLCipher database. + :type db_handle: pysqlcipher.Connection + :param key: A 64 character hex string. + :type key: str """ if not all(c in string.hexdigits for c in key): raise NotAnHexString(key) @@ -546,10 +546,10 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): widely available and peer-reviewed OpenSSL libcrypto for all cryptographic functions. - @param db_handle: A handle to the SQLCipher database. - @type db_handle: pysqlcipher.Connection - @param cipher: The cipher and mode to use. - @type cipher: str + :param db_handle: A handle to the SQLCipher database. + :type db_handle: pysqlcipher.Connection + :param cipher: The cipher and mode to use. + :type cipher: str """ db_handle.cursor().execute("PRAGMA cipher = '%s'" % cipher) @@ -575,10 +575,10 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): * It is not recommended to reduce the number of iterations if a passphrase is in use. - @param db_handle: A handle to the SQLCipher database. - @type db_handle: pysqlcipher.Connection - @param kdf_iter: The number of iterations to use. - @type kdf_iter: int + :param db_handle: A handle to the SQLCipher database. + :type db_handle: pysqlcipher.Connection + :param kdf_iter: The number of iterations to use. + :type kdf_iter: int """ db_handle.cursor().execute("PRAGMA kdf_iter = '%d'" % kdf_iter) @@ -608,10 +608,10 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): * If a non-default value is used PRAGMA cipher_page_size to create a database, it must also be called every time that database is opened. - @param db_handle: A handle to the SQLCipher database. - @type db_handle: pysqlcipher.Connection - @param cipher_page_size: The page size. - @type cipher_page_size: int + :param db_handle: A handle to the SQLCipher database. + :type db_handle: pysqlcipher.Connection + :param cipher_page_size: The page size. + :type cipher_page_size: int """ db_handle.cursor().execute( "PRAGMA cipher_page_size = '%d'" % cipher_page_size) @@ -637,13 +637,13 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): code>PRAGMA rekey_kdf_iter. These are deprecated and should not be used. Instead, use sqlcipher_export(). - @param db_handle: A handle to the SQLCipher database. - @type db_handle: pysqlcipher.Connection - @param new_key: The new key. - @type new_key: str - @param raw_key: Whether C{password} is a raw 64-char hex string or a + :param db_handle: A handle to the SQLCipher database. + :type db_handle: pysqlcipher.Connection + :param new_key: The new key. + :type new_key: str + :param raw_key: Whether C{password} is a raw 64-char hex string or a passphrase that should be hashed to obtain the encyrption key. - @type raw_key: bool + :type raw_key: bool """ if raw_key: cls._pragma_rekey_raw(db_handle, key) @@ -659,10 +659,10 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): PBKDF2 key derivation. The result is used as the encryption key for the database. - @param db_handle: A handle to the SQLCipher database. - @type db_handle: pysqlcipher.Connection - @param passphrase: The passphrase used to derive the encryption key. - @type passphrase: str + :param db_handle: A handle to the SQLCipher database. + :type db_handle: pysqlcipher.Connection + :param passphrase: The passphrase used to derive the encryption key. + :type passphrase: str """ db_handle.cursor().execute("PRAGMA rekey = '%s'" % passphrase) @@ -676,10 +676,10 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase): ensure that the data provided is a 64 character hex string, which will be converted directly to 32 bytes (256 bits) of key data. - @param db_handle: A handle to the SQLCipher database. - @type db_handle: pysqlcipher.Connection - @param key: A 64 character hex string. - @type key: str + :param db_handle: A handle to the SQLCipher database. + :type db_handle: pysqlcipher.Connection + :param key: A 64 character hex string. + :type key: str """ if not all(c in string.hexdigits for c in key): raise NotAnHexString(key) diff --git a/client/src/leap/soledad/client/target.py b/client/src/leap/soledad/client/target.py index d0bc3706..65639887 100644 --- a/client/src/leap/soledad/client/target.py +++ b/client/src/leap/soledad/client/target.py @@ -98,19 +98,19 @@ def mac_doc(crypto, doc_id, doc_rev, ciphertext, mac_method): * msg: doc_id + doc_rev + ciphertext * digestmod: sha256 - @param crypto: A SoledadCryto instance used to perform the encryption. - @type crypto: leap.soledad.crypto.SoledadCrypto - @param doc_id: The id of the document. - @type doc_id: str - @param doc_rev: The revision of the document. - @type doc_rev: str - @param ciphertext: The content of the document. - @type ciphertext: str - @param mac_method: The MAC method to use. - @type mac_method: str - - @return: The calculated MAC. - @rtype: str + :param crypto: A SoledadCryto instance used to perform the encryption. + :type crypto: leap.soledad.crypto.SoledadCrypto + :param doc_id: The id of the document. + :type doc_id: str + :param doc_rev: The revision of the document. + :type doc_rev: str + :param ciphertext: The content of the document. + :type ciphertext: str + :param mac_method: The MAC method to use. + :type mac_method: str + + :return: The calculated MAC. + :rtype: str """ if mac_method == MacMethods.HMAC: return hmac.new( @@ -137,14 +137,14 @@ def encrypt_doc(crypto, doc): MAC_METHOD_KEY: 'hmac' } - @param crypto: A SoledadCryto instance used to perform the encryption. - @type crypto: leap.soledad.crypto.SoledadCrypto - @param doc: The document with contents to be encrypted. - @type doc: SoledadDocument + :param crypto: A SoledadCryto instance used to perform the encryption. + :type crypto: leap.soledad.crypto.SoledadCrypto + :param doc: The document with contents to be encrypted. + :type doc: SoledadDocument - @return: The JSON serialization of the dict representing the encrypted + :return: The JSON serialization of the dict representing the encrypted content. - @rtype: str + :rtype: str """ soledad_assert(doc.is_tombstone() is False) # encrypt content using AES-256 CTR mode @@ -191,13 +191,13 @@ def decrypt_doc(crypto, doc): EncryptionSchemes.SYMKEY and C{enc_method} is EncryptionMethods.AES_256_CTR. - @param crypto: A SoledadCryto instance to perform the encryption. - @type crypto: leap.soledad.crypto.SoledadCrypto - @param doc: The document to be decrypted. - @type doc: SoledadDocument + :param crypto: A SoledadCryto instance to perform the encryption. + :type crypto: leap.soledad.crypto.SoledadCrypto + :param doc: The document to be decrypted. + :type doc: SoledadDocument - @return: The JSON serialization of the decrypted content. - @rtype: str + :return: The JSON serialization of the decrypted content. + :rtype: str """ soledad_assert(doc.is_tombstone() is False) soledad_assert(ENC_JSON_KEY in doc.content) @@ -258,10 +258,10 @@ class SoledadSyncTarget(HTTPSyncTarget, TokenBasedAuth): """ Store given credentials so we can sign the request later. - @param uuid: The user's uuid. - @type uuid: str - @param token: The authentication token. - @type token: str + :param uuid: The user's uuid. + :type uuid: str + :param token: The authentication token. + :type token: str """ TokenBasedAuth.set_token_credentials(self, uuid, token) @@ -269,15 +269,15 @@ class SoledadSyncTarget(HTTPSyncTarget, TokenBasedAuth): """ Return an authorization header to be included in the HTTP request. - @param method: The HTTP method. - @type method: str - @param url_query: The URL query string. - @type url_query: str - @param params: A list with encoded query parameters. - @type param: list + :param method: The HTTP method. + :type method: str + :param url_query: The URL query string. + :type url_query: str + :param params: A list with encoded query parameters. + :type param: list - @return: The Authorization header. - @rtype: list of tuple + :return: The Authorization header. + :rtype: list of tuple """ return TokenBasedAuth._sign_request(self, method, url_query, params) @@ -293,14 +293,14 @@ class SoledadSyncTarget(HTTPSyncTarget, TokenBasedAuth): """ Initialize the SoledadSyncTarget. - @param url: The url of the target replica to sync with. - @type url: str - @param creds: optional dictionary giving credentials. + :param url: The url of the target replica to sync with. + :type url: str + :param creds: optional dictionary giving credentials. to authorize the operation with the server. - @type creds: dict - @param soledad: An instance of Soledad so we can encrypt/decrypt + :type creds: dict + :param soledad: An instance of Soledad so we can encrypt/decrypt document contents when syncing. - @type soledad: soledad.Soledad + :type soledad: soledad.Soledad """ HTTPSyncTarget.__init__(self, url, creds) self._crypto = crypto @@ -314,19 +314,19 @@ class SoledadSyncTarget(HTTPSyncTarget, TokenBasedAuth): EncryptionSchemes.SYMKEY, then this method will decrypt it with Soledad's symmetric key. - @param data: The body of the HTTP response. - @type data: str - @param return_doc_cb: A callback to insert docs from target. - @type return_doc_cb: function - @param ensure_callback: A callback to ensure we have the correct + :param data: The body of the HTTP response. + :type data: str + :param return_doc_cb: A callback to insert docs from target. + :type return_doc_cb: function + :param ensure_callback: A callback to ensure we have the correct target_replica_uid, if it was just created. - @type ensure_callback: function + :type ensure_callback: function - @raise BrokenSyncStream: If C{data} is malformed. + :raise BrokenSyncStream: If C{data} is malformed. - @return: A dictionary representing the first line of the response got + :return: A dictionary representing the first line of the response got from remote replica. - @rtype: list of str + :rtype: list of str """ parts = data.splitlines() # one at a time if not parts or parts[0] != '[': @@ -381,25 +381,25 @@ class SoledadSyncTarget(HTTPSyncTarget, TokenBasedAuth): This does the same as the parent's method but encrypts content before syncing. - @param docs_by_generations: A list of (doc_id, generation, trans_id) + :param docs_by_generations: A list of (doc_id, generation, trans_id) of local documents that were changed since the last local generation the remote replica knows about. - @type docs_by_generations: list of tuples - @param source_replica_uid: The uid of the source replica. - @type source_replica_uid: str - @param last_known_generation: Target's last known generation. - @type last_known_generation: int - @param last_known_trans_id: Target's last known transaction id. - @type last_known_trans_id: str - @param return_doc_cb: A callback for inserting received documents from + :type docs_by_generations: list of tuples + :param source_replica_uid: The uid of the source replica. + :type source_replica_uid: str + :param last_known_generation: Target's last known generation. + :type last_known_generation: int + :param last_known_trans_id: Target's last known transaction id. + :type last_known_trans_id: str + :param return_doc_cb: A callback for inserting received documents from target. - @type return_doc_cb: function - @param ensure_callback: A callback that ensures we know the target + :type return_doc_cb: function + :param ensure_callback: A callback that ensures we know the target replica uid if the target replica was just created. - @type ensure_callback: function + :type ensure_callback: function - @return: The new generation and transaction id of the target replica. - @rtype: tuple + :return: The new generation and transaction id of the target replica. + :rtype: tuple """ self._ensure_connection() if self._trace_hook: # for tests |