From ea2f5e31c5754b71b2cb5aea9d9b36f4d2b2ac31 Mon Sep 17 00:00:00 2001 From: drebs Date: Wed, 28 Nov 2012 20:05:27 -0200 Subject: add u1db openstack backend base files --- src/leap/soledad/__init__.py | 164 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 src/leap/soledad/__init__.py (limited to 'src/leap/soledad/__init__.py') diff --git a/src/leap/soledad/__init__.py b/src/leap/soledad/__init__.py new file mode 100644 index 00000000..b4284c84 --- /dev/null +++ b/src/leap/soledad/__init__.py @@ -0,0 +1,164 @@ +# License? + +"""A U1DB implementation that uses OpenStack Swift as its persistence layer.""" + +import errno +import os +try: + import simplejson as json +except ImportError: + import json # noqa +import sys +import time +import uuid + +from u1db.backends import CommonBackend, CommonSyncTarget +from u1db import ( + Document, + errors, + query_parser, + vectorclock, + ) + + +class OpenStackDatabase(CommonBackend): + """A U1DB implementation that uses OpenStack as its persistence layer.""" + + def __init__(self, sqlite_file, document_factory=None): + """Create a new OpenStack data container.""" + raise NotImplementedError(self.__init__) + + def set_document_factory(self, factory): + self._factory = factory + + def get_sync_target(self): + return OpenStackSyncTarget(self) + + @classmethod + def open_database(cls, sqlite_file, create, backend_cls=None, + document_factory=None): + raise NotImplementedError(open_database) + + @staticmethod + def delete_database(sqlite_file): + raise NotImplementedError(delete_database) + + + def close(self): + raise NotImplementedError(self.close) + + def _is_initialized(self, c): + raise NotImplementedError(self._is_initialized) + + def _initialize(self, c): + raise NotImplementedError(self._initialize) + + def _ensure_schema(self): + raise NotImplementedError(self._ensure_schema) + + def _set_replica_uid(self, replica_uid): + """Force the replica_uid to be set.""" + raise NotImplementedError(self._set_replica_uid) + + def _set_replica_uid_in_transaction(self, replica_uid): + """Set the replica_uid. A transaction should already be held.""" + raise NotImplementedError(self._set_replica_uid_in_transaction) + + def _get_replica_uid(self): + raise NotImplementedError(self._get_replica_uid) + + _replica_uid = property(_get_replica_uid) + + def _get_generation(self): + raise NotImplementedError(self._get_generation) + + def _get_generation_info(self): + raise NotImplementedError(self._get_generation_info) + + def _get_trans_id_for_gen(self, generation): + raise NotImplementedError(self._get_trans_id_for_gen) + + def _get_transaction_log(self): + raise NotImplementedError(self._get_transaction_log) + + def _get_doc(self, doc_id, check_for_conflicts=False): + """Get just the document content, without fancy handling.""" + raise NotImplementedError(self._get_doc) + + def _has_conflicts(self, doc_id): + raise NotImplementedError(self._has_conflicts) + + def get_doc(self, doc_id, include_deleted=False): + raise NotImplementedError(self.get_doc) + + def get_all_docs(self, include_deleted=False): + """Get all documents from the database.""" + raise NotImplementedError(self.get_all_docs) + + def put_doc(self, doc): + raise NotImplementedError(self.put_doc) + + def whats_changed(self, old_generation=0): + raise NotImplementedError(self.whats_changed) + + def delete_doc(self, doc): + raise NotImplementedError(self.delete_doc) + + def _get_conflicts(self, doc_id): + return [] + + def get_doc_conflicts(self, doc_id): + return [] + + def _get_replica_gen_and_trans_id(self, other_replica_uid): + raise NotImplementedError(self._get_replica_gen_and_trans_id) + + def _set_replica_gen_and_trans_id(self, other_replica_uid, + other_generation, other_transaction_id): + raise NotImplementedError(self._set_replica_gen_and_trans_id) + + def _do_set_replica_gen_and_trans_id(self, other_replica_uid, + other_generation, + other_transaction_id): + raise NotImplementedError(self._do_set_replica_gen_and_trans_id) + + def _put_doc_if_newer(self, doc, save_conflict, replica_uid=None, + replica_gen=None, replica_trans_id=None): + raise NotImplementedError(self._put_doc_if_newer) + + def resolve_doc(self, doc, conflicted_doc_revs): + raise NotImplementedError(self.resolve_doc) + + def list_indexes(self): + return [] + + def get_from_index(self, index_name, *key_values): + return [] + + def get_range_from_index(self, index_name, start_value=None, + end_value=None): + return [] + + def get_index_keys(self, index_name): + return [] + + def delete_index(self, index_name): + return False + +class LeapDocument(Document): + + def get_content_encrypted(self): + raise NotImplementedError(self.get_content_encrypted) + + def set_content_encrypted(self): + raise NotImplementedError(self.set_content_encrypted) + + +class OpenStackSyncTarget(CommonSyncTarget): + + def get_sync_info(self, source_replica_uid): + raise NotImplementedError(self.get_sync_info) + + def record_sync_info(self, source_replica_uid, source_replica_generation, + source_replica_transaction_id): + raise NotImplementedError(self.record_sync_info) -- cgit v1.2.3 From 0f1f9474e7ea6b52dc3ae18444cfaaca56ff3070 Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 29 Nov 2012 10:56:06 -0200 Subject: organize methods for openstack backend --- src/leap/soledad/__init__.py | 172 +++++++++++++++++++++---------------------- 1 file changed, 86 insertions(+), 86 deletions(-) (limited to 'src/leap/soledad/__init__.py') diff --git a/src/leap/soledad/__init__.py b/src/leap/soledad/__init__.py index b4284c84..3d685635 100644 --- a/src/leap/soledad/__init__.py +++ b/src/leap/soledad/__init__.py @@ -2,15 +2,10 @@ """A U1DB implementation that uses OpenStack Swift as its persistence layer.""" -import errno -import os try: import simplejson as json except ImportError: import json # noqa -import sys -import time -import uuid from u1db.backends import CommonBackend, CommonSyncTarget from u1db import ( @@ -20,73 +15,33 @@ from u1db import ( vectorclock, ) +from swiftclient import client + class OpenStackDatabase(CommonBackend): """A U1DB implementation that uses OpenStack as its persistence layer.""" - def __init__(self, sqlite_file, document_factory=None): + def __init__(self, auth_url, user, auth_key): """Create a new OpenStack data container.""" - raise NotImplementedError(self.__init__) + self._auth_url = auth_url + self._user = user + self._auth_key = auth_key + self.set_document_factory(LeapDocument) + self._connection = swiftclient.Connection(self._auth_url, self._user, + self._auth_key) + + #------------------------------------------------------------------------- + # implemented methods from Database + #------------------------------------------------------------------------- def set_document_factory(self, factory): self._factory = factory - def get_sync_target(self): - return OpenStackSyncTarget(self) - - @classmethod - def open_database(cls, sqlite_file, create, backend_cls=None, - document_factory=None): - raise NotImplementedError(open_database) - - @staticmethod - def delete_database(sqlite_file): - raise NotImplementedError(delete_database) - - - def close(self): - raise NotImplementedError(self.close) - - def _is_initialized(self, c): - raise NotImplementedError(self._is_initialized) - - def _initialize(self, c): - raise NotImplementedError(self._initialize) - - def _ensure_schema(self): - raise NotImplementedError(self._ensure_schema) - - def _set_replica_uid(self, replica_uid): - """Force the replica_uid to be set.""" - raise NotImplementedError(self._set_replica_uid) - - def _set_replica_uid_in_transaction(self, replica_uid): - """Set the replica_uid. A transaction should already be held.""" - raise NotImplementedError(self._set_replica_uid_in_transaction) - - def _get_replica_uid(self): - raise NotImplementedError(self._get_replica_uid) - - _replica_uid = property(_get_replica_uid) + def set_document_size_limit(self, limit): + raise NotImplementedError(self.set_document_size_limit) - def _get_generation(self): - raise NotImplementedError(self._get_generation) - - def _get_generation_info(self): - raise NotImplementedError(self._get_generation_info) - - def _get_trans_id_for_gen(self, generation): - raise NotImplementedError(self._get_trans_id_for_gen) - - def _get_transaction_log(self): - raise NotImplementedError(self._get_transaction_log) - - def _get_doc(self, doc_id, check_for_conflicts=False): - """Get just the document content, without fancy handling.""" - raise NotImplementedError(self._get_doc) - - def _has_conflicts(self, doc_id): - raise NotImplementedError(self._has_conflicts) + def whats_changed(self, old_generation=0): + raise NotImplementedError(self.whats_changed) def get_doc(self, doc_id, include_deleted=False): raise NotImplementedError(self.get_doc) @@ -98,18 +53,47 @@ class OpenStackDatabase(CommonBackend): def put_doc(self, doc): raise NotImplementedError(self.put_doc) - def whats_changed(self, old_generation=0): - raise NotImplementedError(self.whats_changed) - def delete_doc(self, doc): raise NotImplementedError(self.delete_doc) - def _get_conflicts(self, doc_id): + # start of index-related methods: these are not supported by this backend. + + def create_index(self, index_name, *index_expressions): + return False + + def delete_index(self, index_name): + return False + + def list_indexes(self): + return [] + + def get_from_index(self, index_name, *key_values): return [] + def get_range_from_index(self, index_name, start_value=None, + end_value=None): + return [] + + def get_index_keys(self, index_name): + return [] + + # end of index-related methods: these are not supported by this backend. + def get_doc_conflicts(self, doc_id): return [] + def resolve_doc(self, doc, conflicted_doc_revs): + raise NotImplementedError(self.resolve_doc) + + def get_sync_target(self): + return OpenStackSyncTarget(self) + + def close(self): + raise NotImplementedError(self.close) + + def sync(self, url, creds=None, autocreate=True): + raise NotImplementedError(self.close) + def _get_replica_gen_and_trans_id(self, other_replica_uid): raise NotImplementedError(self._get_replica_gen_and_trans_id) @@ -117,33 +101,49 @@ class OpenStackDatabase(CommonBackend): other_generation, other_transaction_id): raise NotImplementedError(self._set_replica_gen_and_trans_id) - def _do_set_replica_gen_and_trans_id(self, other_replica_uid, - other_generation, - other_transaction_id): - raise NotImplementedError(self._do_set_replica_gen_and_trans_id) + #------------------------------------------------------------------------- + # implemented methods from CommonBackend + #------------------------------------------------------------------------- - def _put_doc_if_newer(self, doc, save_conflict, replica_uid=None, - replica_gen=None, replica_trans_id=None): - raise NotImplementedError(self._put_doc_if_newer) + def _get_generation(self): + raise NotImplementedError(self._get_generation) - def resolve_doc(self, doc, conflicted_doc_revs): - raise NotImplementedError(self.resolve_doc) + def _get_generation_info(self): + raise NotImplementedError(self._get_generation_info) - def list_indexes(self): - return [] + def _get_doc(self, doc_id, check_for_conflicts=False): + """Get just the document content, without fancy handling.""" + raise NotImplementedError(self._get_doc) - def get_from_index(self, index_name, *key_values): - return [] + def _has_conflicts(self, doc_id): + raise NotImplementedError(self._has_conflicts) - def get_range_from_index(self, index_name, start_value=None, - end_value=None): - return [] + def _get_transaction_log(self): + raise NotImplementedError(self._get_transaction_log) - def get_index_keys(self, index_name): - return [] + def _put_and_update_indexes(self, doc_id, old_doc, new_rev, content): + raise NotImplementedError(self._put_and_update_indexes) + + + def _get_trans_id_for_gen(self, generation): + raise NotImplementedError(self._get_trans_id_for_gen) + + #------------------------------------------------------------------------- + # OpenStack specific methods + #------------------------------------------------------------------------- + + def _is_initialized(self, c): + raise NotImplementedError(self._is_initialized) + + def _initialize(self, c): + raise NotImplementedError(self._initialize) + + def _get_auth(self): + self._url, self._auth_token = self._connection.get_auth(self._auth_url, + self._user, + self._auth_key) + return self._url, self.auth_token - def delete_index(self, index_name): - return False class LeapDocument(Document): -- cgit v1.2.3 From 8febf0c6f71395bbc8a24440beb28dfb719ba01c Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 3 Dec 2012 10:32:54 -0200 Subject: Add LeapDocument methods for encrypting/decrypting --- src/leap/soledad/__init__.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'src/leap/soledad/__init__.py') diff --git a/src/leap/soledad/__init__.py b/src/leap/soledad/__init__.py index 3d685635..94286370 100644 --- a/src/leap/soledad/__init__.py +++ b/src/leap/soledad/__init__.py @@ -16,6 +16,7 @@ from u1db import ( ) from swiftclient import client +import base64 class OpenStackDatabase(CommonBackend): @@ -148,10 +149,20 @@ class OpenStackDatabase(CommonBackend): class LeapDocument(Document): def get_content_encrypted(self): - raise NotImplementedError(self.get_content_encrypted) + """ + Returns document's json serialization encrypted with user's public key. + """ + # TODO: replace for openpgp encryption with users's pub key. + return base64.b64encode(self.get_json()) def set_content_encrypted(self): - raise NotImplementedError(self.set_content_encrypted) + """ + Set document's content based on encrypted version of json string. + """ + # TODO: + # - replace for openpgp decryption using user's priv key. + # - raise error if unsuccessful. + return self.set_json(base64.b64decode(self.get_json())) class OpenStackSyncTarget(CommonSyncTarget): -- cgit v1.2.3 From dae0dacb59e2b06b681fab88ddefb038b7e16bb6 Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 3 Dec 2012 11:06:23 -0200 Subject: LeapSyncTarget encodes/decodes before/after syncing --- src/leap/soledad/__init__.py | 87 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 2 deletions(-) (limited to 'src/leap/soledad/__init__.py') diff --git a/src/leap/soledad/__init__.py b/src/leap/soledad/__init__.py index 94286370..5174d818 100644 --- a/src/leap/soledad/__init__.py +++ b/src/leap/soledad/__init__.py @@ -14,6 +14,7 @@ from u1db import ( query_parser, vectorclock, ) +from u1db.remote.http_target import HTTPSyncTarget from swiftclient import client import base64 @@ -148,14 +149,20 @@ class OpenStackDatabase(CommonBackend): class LeapDocument(Document): - def get_content_encrypted(self): + def __init__(self, doc_id=None, rev=None, json='{}', has_conflicts=False, + encrypted_json=None): + super(Document, self).__init__(doc_id, rev, json, has_conflicts) + if encrypted_json: + self.set_encrypted_json(encrypted_json) + + def get_encrypted_json(self): """ Returns document's json serialization encrypted with user's public key. """ # TODO: replace for openpgp encryption with users's pub key. return base64.b64encode(self.get_json()) - def set_content_encrypted(self): + def set_encrypted_json(self): """ Set document's content based on encrypted version of json string. """ @@ -165,6 +172,82 @@ class LeapDocument(Document): return self.set_json(base64.b64decode(self.get_json())) +class LeapSyncTarget(HTTPSyncTarget): + + def _parse_sync_stream(self, data, return_doc_cb, ensure_callback=None): + parts = data.splitlines() # one at a time + if not parts or parts[0] != '[': + raise BrokenSyncStream + data = parts[1:-1] + comma = False + if data: + line, comma = utils.check_and_strip_comma(data[0]) + res = json.loads(line) + if ensure_callback and 'replica_uid' in res: + ensure_callback(res['replica_uid']) + for entry in data[1:]: + if not comma: # missing in between comma + raise BrokenSyncStream + line, comma = utils.check_and_strip_comma(entry) + entry = json.loads(line) + doc = LeapDocument(entry['id'], entry['rev'], + encrypted_json=entry['content']) + return_doc_cb(doc, entry['gen'], entry['trans_id']) + if parts[-1] != ']': + try: + partdic = json.loads(parts[-1]) + except ValueError: + pass + else: + if isinstance(partdic, dict): + self._error(partdic) + raise BrokenSyncStream + if not data or comma: # no entries or bad extra comma + raise BrokenSyncStream + return res + + def sync_exchange(self, docs_by_generations, source_replica_uid, + last_known_generation, last_known_trans_id, + return_doc_cb, ensure_callback=None): + self._ensure_connection() + if self._trace_hook: # for tests + self._trace_hook('sync_exchange') + url = '%s/sync-from/%s' % (self._url.path, source_replica_uid) + self._conn.putrequest('POST', url) + self._conn.putheader('content-type', 'application/x-u1db-sync-stream') + for header_name, header_value in self._sign_request('POST', url, {}): + self._conn.putheader(header_name, header_value) + entries = ['['] + size = 1 + + def prepare(**dic): + entry = comma + '\r\n' + json.dumps(dic) + entries.append(entry) + return len(entry) + + comma = '' + size += prepare( + last_known_generation=last_known_generation, + last_known_trans_id=last_known_trans_id, + ensure=ensure_callback is not None) + comma = ',' + for doc, gen, trans_id in docs_by_generations: + size += prepare(id=doc.doc_id, rev=doc.rev, + content=doc.get_encrypted_json(), + gen=gen, trans_id=trans_id) + entries.append('\r\n]') + size += len(entries[-1]) + self._conn.putheader('content-length', str(size)) + self._conn.endheaders() + for entry in entries: + self._conn.send(entry) + entries = None + data, _ = self._response() + res = self._parse_sync_stream(data, return_doc_cb, ensure_callback) + data = None + return res['new_generation'], res['new_transaction_id'] + + class OpenStackSyncTarget(CommonSyncTarget): def get_sync_info(self, source_replica_uid): -- cgit v1.2.3 From 7a932811c018bb30b584451d4fe114cf69ab420c Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 3 Dec 2012 11:13:51 -0200 Subject: Split leap infrastructure and openstack backend in different files. --- src/leap/soledad/__init__.py | 256 +------------------------------------------ 1 file changed, 2 insertions(+), 254 deletions(-) (limited to 'src/leap/soledad/__init__.py') diff --git a/src/leap/soledad/__init__.py b/src/leap/soledad/__init__.py index 5174d818..6ba64a61 100644 --- a/src/leap/soledad/__init__.py +++ b/src/leap/soledad/__init__.py @@ -2,257 +2,5 @@ """A U1DB implementation that uses OpenStack Swift as its persistence layer.""" -try: - import simplejson as json -except ImportError: - import json # noqa - -from u1db.backends import CommonBackend, CommonSyncTarget -from u1db import ( - Document, - errors, - query_parser, - vectorclock, - ) -from u1db.remote.http_target import HTTPSyncTarget - -from swiftclient import client -import base64 - - -class OpenStackDatabase(CommonBackend): - """A U1DB implementation that uses OpenStack as its persistence layer.""" - - def __init__(self, auth_url, user, auth_key): - """Create a new OpenStack data container.""" - self._auth_url = auth_url - self._user = user - self._auth_key = auth_key - self.set_document_factory(LeapDocument) - self._connection = swiftclient.Connection(self._auth_url, self._user, - self._auth_key) - - #------------------------------------------------------------------------- - # implemented methods from Database - #------------------------------------------------------------------------- - - def set_document_factory(self, factory): - self._factory = factory - - def set_document_size_limit(self, limit): - raise NotImplementedError(self.set_document_size_limit) - - def whats_changed(self, old_generation=0): - raise NotImplementedError(self.whats_changed) - - def get_doc(self, doc_id, include_deleted=False): - raise NotImplementedError(self.get_doc) - - def get_all_docs(self, include_deleted=False): - """Get all documents from the database.""" - raise NotImplementedError(self.get_all_docs) - - def put_doc(self, doc): - raise NotImplementedError(self.put_doc) - - def delete_doc(self, doc): - raise NotImplementedError(self.delete_doc) - - # start of index-related methods: these are not supported by this backend. - - def create_index(self, index_name, *index_expressions): - return False - - def delete_index(self, index_name): - return False - - def list_indexes(self): - return [] - - def get_from_index(self, index_name, *key_values): - return [] - - def get_range_from_index(self, index_name, start_value=None, - end_value=None): - return [] - - def get_index_keys(self, index_name): - return [] - - # end of index-related methods: these are not supported by this backend. - - def get_doc_conflicts(self, doc_id): - return [] - - def resolve_doc(self, doc, conflicted_doc_revs): - raise NotImplementedError(self.resolve_doc) - - def get_sync_target(self): - return OpenStackSyncTarget(self) - - def close(self): - raise NotImplementedError(self.close) - - def sync(self, url, creds=None, autocreate=True): - raise NotImplementedError(self.close) - - def _get_replica_gen_and_trans_id(self, other_replica_uid): - raise NotImplementedError(self._get_replica_gen_and_trans_id) - - def _set_replica_gen_and_trans_id(self, other_replica_uid, - other_generation, other_transaction_id): - raise NotImplementedError(self._set_replica_gen_and_trans_id) - - #------------------------------------------------------------------------- - # implemented methods from CommonBackend - #------------------------------------------------------------------------- - - def _get_generation(self): - raise NotImplementedError(self._get_generation) - - def _get_generation_info(self): - raise NotImplementedError(self._get_generation_info) - - def _get_doc(self, doc_id, check_for_conflicts=False): - """Get just the document content, without fancy handling.""" - raise NotImplementedError(self._get_doc) - - def _has_conflicts(self, doc_id): - raise NotImplementedError(self._has_conflicts) - - def _get_transaction_log(self): - raise NotImplementedError(self._get_transaction_log) - - def _put_and_update_indexes(self, doc_id, old_doc, new_rev, content): - raise NotImplementedError(self._put_and_update_indexes) - - - def _get_trans_id_for_gen(self, generation): - raise NotImplementedError(self._get_trans_id_for_gen) - - #------------------------------------------------------------------------- - # OpenStack specific methods - #------------------------------------------------------------------------- - - def _is_initialized(self, c): - raise NotImplementedError(self._is_initialized) - - def _initialize(self, c): - raise NotImplementedError(self._initialize) - - def _get_auth(self): - self._url, self._auth_token = self._connection.get_auth(self._auth_url, - self._user, - self._auth_key) - return self._url, self.auth_token - - -class LeapDocument(Document): - - def __init__(self, doc_id=None, rev=None, json='{}', has_conflicts=False, - encrypted_json=None): - super(Document, self).__init__(doc_id, rev, json, has_conflicts) - if encrypted_json: - self.set_encrypted_json(encrypted_json) - - def get_encrypted_json(self): - """ - Returns document's json serialization encrypted with user's public key. - """ - # TODO: replace for openpgp encryption with users's pub key. - return base64.b64encode(self.get_json()) - - def set_encrypted_json(self): - """ - Set document's content based on encrypted version of json string. - """ - # TODO: - # - replace for openpgp decryption using user's priv key. - # - raise error if unsuccessful. - return self.set_json(base64.b64decode(self.get_json())) - - -class LeapSyncTarget(HTTPSyncTarget): - - def _parse_sync_stream(self, data, return_doc_cb, ensure_callback=None): - parts = data.splitlines() # one at a time - if not parts or parts[0] != '[': - raise BrokenSyncStream - data = parts[1:-1] - comma = False - if data: - line, comma = utils.check_and_strip_comma(data[0]) - res = json.loads(line) - if ensure_callback and 'replica_uid' in res: - ensure_callback(res['replica_uid']) - for entry in data[1:]: - if not comma: # missing in between comma - raise BrokenSyncStream - line, comma = utils.check_and_strip_comma(entry) - entry = json.loads(line) - doc = LeapDocument(entry['id'], entry['rev'], - encrypted_json=entry['content']) - return_doc_cb(doc, entry['gen'], entry['trans_id']) - if parts[-1] != ']': - try: - partdic = json.loads(parts[-1]) - except ValueError: - pass - else: - if isinstance(partdic, dict): - self._error(partdic) - raise BrokenSyncStream - if not data or comma: # no entries or bad extra comma - raise BrokenSyncStream - return res - - def sync_exchange(self, docs_by_generations, source_replica_uid, - last_known_generation, last_known_trans_id, - return_doc_cb, ensure_callback=None): - self._ensure_connection() - if self._trace_hook: # for tests - self._trace_hook('sync_exchange') - url = '%s/sync-from/%s' % (self._url.path, source_replica_uid) - self._conn.putrequest('POST', url) - self._conn.putheader('content-type', 'application/x-u1db-sync-stream') - for header_name, header_value in self._sign_request('POST', url, {}): - self._conn.putheader(header_name, header_value) - entries = ['['] - size = 1 - - def prepare(**dic): - entry = comma + '\r\n' + json.dumps(dic) - entries.append(entry) - return len(entry) - - comma = '' - size += prepare( - last_known_generation=last_known_generation, - last_known_trans_id=last_known_trans_id, - ensure=ensure_callback is not None) - comma = ',' - for doc, gen, trans_id in docs_by_generations: - size += prepare(id=doc.doc_id, rev=doc.rev, - content=doc.get_encrypted_json(), - gen=gen, trans_id=trans_id) - entries.append('\r\n]') - size += len(entries[-1]) - self._conn.putheader('content-length', str(size)) - self._conn.endheaders() - for entry in entries: - self._conn.send(entry) - entries = None - data, _ = self._response() - res = self._parse_sync_stream(data, return_doc_cb, ensure_callback) - data = None - return res['new_generation'], res['new_transaction_id'] - - -class OpenStackSyncTarget(CommonSyncTarget): - - def get_sync_info(self, source_replica_uid): - raise NotImplementedError(self.get_sync_info) - - def record_sync_info(self, source_replica_uid, source_replica_generation, - source_replica_transaction_id): - raise NotImplementedError(self.record_sync_info) +from leap import * +from openstack import * -- cgit v1.2.3 From 2980e61298dc3a17715ce5693470c3d7f3a86497 Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 3 Dec 2012 12:12:01 -0200 Subject: Add python-gnupg simple wrapper --- src/leap/soledad/__init__.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) (limited to 'src/leap/soledad/__init__.py') diff --git a/src/leap/soledad/__init__.py b/src/leap/soledad/__init__.py index 6ba64a61..7991f898 100644 --- a/src/leap/soledad/__init__.py +++ b/src/leap/soledad/__init__.py @@ -4,3 +4,40 @@ from leap import * from openstack import * + +import gnupg + +class GPGWrapper(): + """ + This is a temporary class for handling GPG requests, and should be + replaced by a more general class used throughout the project. + """ + + GNUPG_HOME = "~/.config/leap/gnupg" + GNUPG_BINARY = "/usr/bin/gpg" # this has to be changed based on OS + + def __init__(self, gpghome=GNUPG_HOME, gpgbinary=GNUPG_BINARY): + self.gpg = gnupg.GPG(gnupghome=gpghome, gpgbinary=gpgbinary) + + def find_key(self, email): + """ + Find user's key based on their email. + """ + for key in self.gpg.list_keys(): + for uid in key['uids']: + if re.search(email, uid): + return key + raise LookupError("GnuPG public key for %s not found!" % email) + + def encrypt(self, data, recipient, sign=None, always_trust=False, + passphrase=None, symmetric=False): + return self.gpg.encrypt(data, recipient, sign=sign, + always_trust=always_trust, + passphrase=passphrase, symmetric=symmetric) + + def decrypt(self, data, always_trust=False, passphrase=None): + return self.gpg.decrypt(data, always_trust=always_trust, + passphrase=passphrase) + + def import_keys(self, data): + return self.gpg.import_keys(data) -- cgit v1.2.3 From 584696e4dbfc13b793208dc4c5c6cdc224db5a12 Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 6 Dec 2012 11:07:53 -0200 Subject: Remove u1db and swiftclient dirs and refactor. --- src/leap/soledad/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/leap/soledad/__init__.py') diff --git a/src/leap/soledad/__init__.py b/src/leap/soledad/__init__.py index 7991f898..b7082e53 100644 --- a/src/leap/soledad/__init__.py +++ b/src/leap/soledad/__init__.py @@ -2,8 +2,8 @@ """A U1DB implementation that uses OpenStack Swift as its persistence layer.""" -from leap import * -from openstack import * +from backends.leap import * +from backends.openstack import * import gnupg -- cgit v1.2.3 From 7cc7aee73fbf82b604988585e051da32b99dc70e Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 6 Dec 2012 11:15:42 -0200 Subject: Move log classes so all backends can use them. --- src/leap/soledad/__init__.py | 131 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) (limited to 'src/leap/soledad/__init__.py') diff --git a/src/leap/soledad/__init__.py b/src/leap/soledad/__init__.py index b7082e53..7f742a89 100644 --- a/src/leap/soledad/__init__.py +++ b/src/leap/soledad/__init__.py @@ -41,3 +41,134 @@ class GPGWrapper(): def import_keys(self, data): return self.gpg.import_keys(data) + + +#---------------------------------------------------------------------------- +# u1db Transaction and Sync logs as JSON structures. +#---------------------------------------------------------------------------- + +class SimpleLog(object): + def __init__(self): + self._log = [] + + def _set_log(self, log): + self._log = log + + def _get_log(self): + return self._log + + log = property( + _get_log, _set_log, doc="Log contents.") + + def append(self, msg): + self._log.append(msg) + + def reduce(self, func, initializer=None): + return reduce(func, self.log, initializer) + + def map(self, func): + return map(func, self.log) + + def filter(self, func): + return filter(func, self.log) + + +class TransactionLog(SimpleLog): + """ + An ordered list of (generation, doc_id, transaction_id) tuples. + """ + + def _set_log(self, log): + self._log = log + + def _get_log(self): + return sorted(self._log, reverse=True) + + log = property( + _get_log, _set_log, doc="Log contents.") + + def get_generation(self): + """ + Return the current generation. + """ + gens = self.map(lambda x: x[0]) + if not gens: + return 0 + return max(gens) + + def get_generation_info(self): + """ + Return the current generation and transaction id. + """ + if not self._log: + return(0, '') + info = self.map(lambda x: (x[0], x[2])) + return reduce(lambda x, y: x if (x[0] > y[0]) else y, info) + + def get_trans_id_for_gen(self, gen): + """ + Get the transaction id corresponding to a particular generation. + """ + log = self.reduce(lambda x, y: y if y[0] == gen else x) + if log is None: + return None + return log[2] + + def whats_changed(self, old_generation): + """ + Return a list of documents that have changed since old_generation. + """ + results = self.filter(lambda x: x[0] > old_generation) + seen = set() + changes = [] + newest_trans_id = '' + for generation, doc_id, trans_id in results: + if doc_id not in seen: + changes.append((doc_id, generation, trans_id)) + seen.add(doc_id) + if changes: + cur_gen = changes[0][1] # max generation + newest_trans_id = changes[0][2] + changes.reverse() + else: + results = self.log + if not results: + cur_gen = 0 + newest_trans_id = '' + else: + cur_gen, _, newest_trans_id = results[0] + + return cur_gen, newest_trans_id, changes + + + +class SyncLog(SimpleLog): + """ + A list of (replica_id, generation, transaction_id) tuples. + """ + + def find_by_replica_uid(self, replica_uid): + if not self.log: + return () + return self.reduce(lambda x, y: y if y[0] == replica_uid else x) + + def get_replica_gen_and_trans_id(self, other_replica_uid): + """ + Return the last known generation and transaction id for the other db + replica. + """ + info = self.find_by_replica_uid(other_replica_uid) + if not info: + return (0, '') + return (info[1], info[2]) + + def set_replica_gen_and_trans_id(self, other_replica_uid, + other_generation, other_transaction_id): + """ + Set the last-known generation and transaction id for the other + database replica. + """ + self.log = self.filter(lambda x: x[0] != other_replica_uid) + self.append((other_replica_uid, other_generation, + other_transaction_id)) + -- cgit v1.2.3 From f89f2e0fe490899ecc4baf3395f3441111da328f Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 10 Dec 2012 11:00:10 -0200 Subject: Refactor to add ObjectStore class. --- src/leap/soledad/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/soledad/__init__.py') diff --git a/src/leap/soledad/__init__.py b/src/leap/soledad/__init__.py index 7f742a89..78f1f768 100644 --- a/src/leap/soledad/__init__.py +++ b/src/leap/soledad/__init__.py @@ -44,7 +44,7 @@ class GPGWrapper(): #---------------------------------------------------------------------------- -# u1db Transaction and Sync logs as JSON structures. +# u1db Transaction and Sync logs. #---------------------------------------------------------------------------- class SimpleLog(object): -- cgit v1.2.3 From b3090f710e3777bad2a9f996444e5099883c9f03 Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 10 Dec 2012 12:05:31 -0200 Subject: Add CouchDB u1db backend. --- src/leap/soledad/__init__.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'src/leap/soledad/__init__.py') diff --git a/src/leap/soledad/__init__.py b/src/leap/soledad/__init__.py index 78f1f768..d07567b5 100644 --- a/src/leap/soledad/__init__.py +++ b/src/leap/soledad/__init__.py @@ -1,9 +1,6 @@ # License? -"""A U1DB implementation that uses OpenStack Swift as its persistence layer.""" - -from backends.leap import * -from backends.openstack import * +"""A U1DB implementation for using Object Stores as its persistence layer.""" import gnupg -- cgit v1.2.3 From a14d5ae150c52c3419764443409b7d146c43cb09 Mon Sep 17 00:00:00 2001 From: drebs Date: Tue, 11 Dec 2012 16:34:40 -0200 Subject: Fix gnupg prefix path. --- src/leap/soledad/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/leap/soledad/__init__.py') diff --git a/src/leap/soledad/__init__.py b/src/leap/soledad/__init__.py index d07567b5..45034561 100644 --- a/src/leap/soledad/__init__.py +++ b/src/leap/soledad/__init__.py @@ -2,6 +2,7 @@ """A U1DB implementation for using Object Stores as its persistence layer.""" +import os import gnupg class GPGWrapper(): @@ -10,7 +11,7 @@ class GPGWrapper(): replaced by a more general class used throughout the project. """ - GNUPG_HOME = "~/.config/leap/gnupg" + GNUPG_HOME = os.environ['HOME'] + "/.config/leap/gnupg" GNUPG_BINARY = "/usr/bin/gpg" # this has to be changed based on OS def __init__(self, gpghome=GNUPG_HOME, gpgbinary=GNUPG_BINARY): -- cgit v1.2.3 From 7a67c36efd95d86dea04ab0741c68f5307a95c09 Mon Sep 17 00:00:00 2001 From: drebs Date: Tue, 18 Dec 2012 18:51:01 -0200 Subject: Refactor and symmetric encryption --- src/leap/soledad/__init__.py | 245 ++++++++++++++----------------------------- 1 file changed, 78 insertions(+), 167 deletions(-) (limited to 'src/leap/soledad/__init__.py') diff --git a/src/leap/soledad/__init__.py b/src/leap/soledad/__init__.py index 45034561..835111a5 100644 --- a/src/leap/soledad/__init__.py +++ b/src/leap/soledad/__init__.py @@ -3,170 +3,81 @@ """A U1DB implementation for using Object Stores as its persistence layer.""" import os -import gnupg - -class GPGWrapper(): - """ - This is a temporary class for handling GPG requests, and should be - replaced by a more general class used throughout the project. - """ - - GNUPG_HOME = os.environ['HOME'] + "/.config/leap/gnupg" - GNUPG_BINARY = "/usr/bin/gpg" # this has to be changed based on OS - - def __init__(self, gpghome=GNUPG_HOME, gpgbinary=GNUPG_BINARY): - self.gpg = gnupg.GPG(gnupghome=gpghome, gpgbinary=gpgbinary) - - def find_key(self, email): - """ - Find user's key based on their email. - """ - for key in self.gpg.list_keys(): - for uid in key['uids']: - if re.search(email, uid): - return key - raise LookupError("GnuPG public key for %s not found!" % email) - - def encrypt(self, data, recipient, sign=None, always_trust=False, - passphrase=None, symmetric=False): - return self.gpg.encrypt(data, recipient, sign=sign, - always_trust=always_trust, - passphrase=passphrase, symmetric=symmetric) - - def decrypt(self, data, always_trust=False, passphrase=None): - return self.gpg.decrypt(data, always_trust=always_trust, - passphrase=passphrase) - - def import_keys(self, data): - return self.gpg.import_keys(data) - - -#---------------------------------------------------------------------------- -# u1db Transaction and Sync logs. -#---------------------------------------------------------------------------- - -class SimpleLog(object): - def __init__(self): - self._log = [] - - def _set_log(self, log): - self._log = log - - def _get_log(self): - return self._log - - log = property( - _get_log, _set_log, doc="Log contents.") - - def append(self, msg): - self._log.append(msg) - - def reduce(self, func, initializer=None): - return reduce(func, self.log, initializer) - - def map(self, func): - return map(func, self.log) - - def filter(self, func): - return filter(func, self.log) - - -class TransactionLog(SimpleLog): - """ - An ordered list of (generation, doc_id, transaction_id) tuples. - """ - - def _set_log(self, log): - self._log = log - - def _get_log(self): - return sorted(self._log, reverse=True) - - log = property( - _get_log, _set_log, doc="Log contents.") - - def get_generation(self): - """ - Return the current generation. - """ - gens = self.map(lambda x: x[0]) - if not gens: - return 0 - return max(gens) - - def get_generation_info(self): - """ - Return the current generation and transaction id. - """ - if not self._log: - return(0, '') - info = self.map(lambda x: (x[0], x[2])) - return reduce(lambda x, y: x if (x[0] > y[0]) else y, info) - - def get_trans_id_for_gen(self, gen): - """ - Get the transaction id corresponding to a particular generation. - """ - log = self.reduce(lambda x, y: y if y[0] == gen else x) - if log is None: - return None - return log[2] - - def whats_changed(self, old_generation): - """ - Return a list of documents that have changed since old_generation. - """ - results = self.filter(lambda x: x[0] > old_generation) - seen = set() - changes = [] - newest_trans_id = '' - for generation, doc_id, trans_id in results: - if doc_id not in seen: - changes.append((doc_id, generation, trans_id)) - seen.add(doc_id) - if changes: - cur_gen = changes[0][1] # max generation - newest_trans_id = changes[0][2] - changes.reverse() - else: - results = self.log - if not results: - cur_gen = 0 - newest_trans_id = '' - else: - cur_gen, _, newest_trans_id = results[0] - - return cur_gen, newest_trans_id, changes - - - -class SyncLog(SimpleLog): - """ - A list of (replica_id, generation, transaction_id) tuples. - """ - - def find_by_replica_uid(self, replica_uid): - if not self.log: - return () - return self.reduce(lambda x, y: y if y[0] == replica_uid else x) - - def get_replica_gen_and_trans_id(self, other_replica_uid): - """ - Return the last known generation and transaction id for the other db - replica. - """ - info = self.find_by_replica_uid(other_replica_uid) - if not info: - return (0, '') - return (info[1], info[2]) - - def set_replica_gen_and_trans_id(self, other_replica_uid, - other_generation, other_transaction_id): - """ - Set the last-known generation and transaction id for the other - database replica. - """ - self.log = self.filter(lambda x: x[0] != other_replica_uid) - self.append((other_replica_uid, other_generation, - other_transaction_id)) - +import string +import random +import cStringIO +from soledad.util import GPGWrapper + +class Soledad(object): + + PREFIX = os.environ['HOME'] + '/.config/leap/soledad' + SECRET_PATH = PREFIX + '/secret.gpg' + GNUPG_HOME = PREFIX + '/gnupg' + SECRET_LENGTH = 50 + + def __init__(self, user_email, gpghome=None): + self._user_email = user_email + if not os.path.isdir(self.PREFIX): + os.makedirs(self.PREFIX) + if not gpghome: + gpghome = self.GNUPG_HOME + self._gpg = GPGWrapper(gpghome=gpghome) + # load OpenPGP keypair + if not self._has_openpgp_keypair(): + self._gen_openpgp_keypair() + self._load_openpgp_keypair() + # load secret + if not self._has_secret(): + self._gen_secret() + self._load_secret() + + def _has_secret(self): + if os.path.isfile(self.SECRET_PATH): + return True + return False + + def _load_secret(self): + try: + with open(self.SECRET_PATH) as f: + self._secret = self._gpg.decrypt(f.read()) + except IOError as e: + raise IOError('Failed to open secret file %s.' % self.SECRET_PATH) + + def _gen_secret(self): + self._secret = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(self.SECRET_LENGTH)) + cyphertext = self._gpg.encrypt(self._secret, self._fingerprint, self._fingerprint) + f = open(self.SECRET_PATH, 'w') + f.write(str(cyphertext)) + f.close() + + + def _has_openpgp_keypair(self): + if self._gpg.find_key(self._user_email): + return True + return False + + def _gen_openpgp_keypair(self): + params = self._gpg.gen_key_input( + key_type='RSA', + key_length=4096, + name_real=self._user_email, + name_email=self._user_email, + name_comment='Generated by LEAP Soledad.') + self._gpg.gen_key(params) + + def _load_openpgp_keypair(self): + self._fingerprint = self._gpg.find_key(self._user_email)['fingerprint'] + + def encrypt(self, data, sign=None, passphrase=None, symmetric=False): + return str(self._gpg.encrypt(data, self._fingerprint, sign=sign, + passphrase=passphrase, symmetric=symmetric)) + + def encrypt_symmetric(self, data, sign=None): + return self.encrypt(data, sign=sign, passphrase=self._secret, + symmetric=True) + + def decrypt(self, data, passphrase=None, symmetric=False): + return str(self._gpg.decrypt(data, passphrase=passphrase)) + + def decrypt_symmetric(self, data): + return self.decrypt(data, passphrase=self._secret) -- cgit v1.2.3 From 4cd81148ec25cd6f1a9498345c7405a4d37a4012 Mon Sep 17 00:00:00 2001 From: drebs Date: Tue, 18 Dec 2012 18:57:01 -0200 Subject: Correct typ0 --- src/leap/soledad/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/leap/soledad/__init__.py') diff --git a/src/leap/soledad/__init__.py b/src/leap/soledad/__init__.py index 835111a5..4325d773 100644 --- a/src/leap/soledad/__init__.py +++ b/src/leap/soledad/__init__.py @@ -45,9 +45,9 @@ class Soledad(object): def _gen_secret(self): self._secret = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(self.SECRET_LENGTH)) - cyphertext = self._gpg.encrypt(self._secret, self._fingerprint, self._fingerprint) + ciphertext = self._gpg.encrypt(self._secret, self._fingerprint, self._fingerprint) f = open(self.SECRET_PATH, 'w') - f.write(str(cyphertext)) + f.write(str(ciphertext)) f.close() -- cgit v1.2.3 From 7161784fc65698e2603cf53e797dbd13711689e0 Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 20 Dec 2012 11:35:19 -0200 Subject: Use doc_id with HMAC for symmetric encryption --- src/leap/soledad/__init__.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'src/leap/soledad/__init__.py') diff --git a/src/leap/soledad/__init__.py b/src/leap/soledad/__init__.py index 4325d773..9f5d6e22 100644 --- a/src/leap/soledad/__init__.py +++ b/src/leap/soledad/__init__.py @@ -6,6 +6,7 @@ import os import string import random import cStringIO +import hmac from soledad.util import GPGWrapper class Soledad(object): @@ -39,7 +40,7 @@ class Soledad(object): def _load_secret(self): try: with open(self.SECRET_PATH) as f: - self._secret = self._gpg.decrypt(f.read()) + self._secret = str(self._gpg.decrypt(f.read())) except IOError as e: raise IOError('Failed to open secret file %s.' % self.SECRET_PATH) @@ -72,12 +73,13 @@ class Soledad(object): return str(self._gpg.encrypt(data, self._fingerprint, sign=sign, passphrase=passphrase, symmetric=symmetric)) - def encrypt_symmetric(self, data, sign=None): - return self.encrypt(data, sign=sign, passphrase=self._secret, - symmetric=True) + def encrypt_symmetric(self, doc_id, data, sign=None): + h = hmac.new(self._secret, doc_id).hexdigest() + return self.encrypt(data, sign=sign, passphrase=h, symmetric=True) def decrypt(self, data, passphrase=None, symmetric=False): return str(self._gpg.decrypt(data, passphrase=passphrase)) - def decrypt_symmetric(self, data): - return self.decrypt(data, passphrase=self._secret) + def decrypt_symmetric(self, doc_id, data): + h = hmac.new(self._secret, doc_id).hexdigest() + return self.decrypt(data, passphrase=h) -- cgit v1.2.3 From 8ec2353d688a6064e5c2cd69745e246c12707b95 Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 20 Dec 2012 12:42:34 -0200 Subject: Fix OpenPGP key generation. --- src/leap/soledad/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src/leap/soledad/__init__.py') diff --git a/src/leap/soledad/__init__.py b/src/leap/soledad/__init__.py index 9f5d6e22..6a3707ea 100644 --- a/src/leap/soledad/__init__.py +++ b/src/leap/soledad/__init__.py @@ -53,9 +53,11 @@ class Soledad(object): def _has_openpgp_keypair(self): - if self._gpg.find_key(self._user_email): + try: + self._gpg.find_key(self._user_email) return True - return False + except LookupError: + return False def _gen_openpgp_keypair(self): params = self._gpg.gen_key_input( -- cgit v1.2.3 From ba97ee77ddeb101834a25cbb8d48aaa5edc26b93 Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 24 Dec 2012 11:31:58 -0200 Subject: GPGWrapper extends gnupg.GPG --- src/leap/soledad/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/leap/soledad/__init__.py') diff --git a/src/leap/soledad/__init__.py b/src/leap/soledad/__init__.py index 6a3707ea..abec6488 100644 --- a/src/leap/soledad/__init__.py +++ b/src/leap/soledad/__init__.py @@ -7,7 +7,7 @@ import string import random import cStringIO import hmac -from soledad.util import GPGWrapper +from util import GPGWrapper class Soledad(object): @@ -85,3 +85,6 @@ class Soledad(object): def decrypt_symmetric(self, doc_id, data): h = hmac.new(self._secret, doc_id).hexdigest() return self.decrypt(data, passphrase=h) + + #def publish_pubkey(self): + # return self._gpg.export_keys( -- cgit v1.2.3 From 8a36fceb830d46cb53fbdcc47a9fb63d3b47c9f0 Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 24 Dec 2012 12:12:05 -0200 Subject: Add method signatures for document storage, retrieval and sync --- src/leap/soledad/__init__.py | 76 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 4 deletions(-) (limited to 'src/leap/soledad/__init__.py') diff --git a/src/leap/soledad/__init__.py b/src/leap/soledad/__init__.py index abec6488..2caee108 100644 --- a/src/leap/soledad/__init__.py +++ b/src/leap/soledad/__init__.py @@ -23,21 +23,32 @@ class Soledad(object): if not gpghome: gpghome = self.GNUPG_HOME self._gpg = GPGWrapper(gpghome=gpghome) - # load OpenPGP keypair + # load/generate OpenPGP keypair if not self._has_openpgp_keypair(): self._gen_openpgp_keypair() self._load_openpgp_keypair() - # load secret + # load/generate secret if not self._has_secret(): self._gen_secret() self._load_secret() + + #------------------------------------------------------------------------- + # Management of secret for symmetric encryption + #------------------------------------------------------------------------- + def _has_secret(self): + """ + Verify if secret already exists in a local encrypted file. + """ if os.path.isfile(self.SECRET_PATH): return True return False def _load_secret(self): + """ + Load secret from local encrypted file. + """ try: with open(self.SECRET_PATH) as f: self._secret = str(self._gpg.decrypt(f.read())) @@ -45,14 +56,24 @@ class Soledad(object): raise IOError('Failed to open secret file %s.' % self.SECRET_PATH) def _gen_secret(self): + """ + Generate secret for symmetric encryption and store it in a local encrypted file. + """ self._secret = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(self.SECRET_LENGTH)) ciphertext = self._gpg.encrypt(self._secret, self._fingerprint, self._fingerprint) f = open(self.SECRET_PATH, 'w') f.write(str(ciphertext)) f.close() + #------------------------------------------------------------------------- + # Management of OpenPGP keypair + #------------------------------------------------------------------------- def _has_openpgp_keypair(self): + """ + Verify if a keypair exists for this user. + """ + # TODO: verify if private key exists. try: self._gpg.find_key(self._user_email) return True @@ -60,6 +81,9 @@ class Soledad(object): return False def _gen_openpgp_keypair(self): + """ + Generate a keypair for this user. + """ params = self._gpg.gen_key_input( key_type='RSA', key_length=4096, @@ -69,22 +93,66 @@ class Soledad(object): self._gpg.gen_key(params) def _load_openpgp_keypair(self): + """ + Load the fingerprint for this user's keypair. + """ self._fingerprint = self._gpg.find_key(self._user_email)['fingerprint'] + def publish_pubkey(self, keyserver): + """ + Publish OpenPGP public key to a keyserver. + """ + pass + + #------------------------------------------------------------------------- + # Data encryption and decription + #------------------------------------------------------------------------- + def encrypt(self, data, sign=None, passphrase=None, symmetric=False): + """ + Encrypt data. + """ return str(self._gpg.encrypt(data, self._fingerprint, sign=sign, passphrase=passphrase, symmetric=symmetric)) def encrypt_symmetric(self, doc_id, data, sign=None): + """ + Symmetrically encrypt data using this user's secret. + """ h = hmac.new(self._secret, doc_id).hexdigest() return self.encrypt(data, sign=sign, passphrase=h, symmetric=True) def decrypt(self, data, passphrase=None, symmetric=False): + """ + Decrypt data. + """ return str(self._gpg.decrypt(data, passphrase=passphrase)) def decrypt_symmetric(self, doc_id, data): + """ + Symmetrically decrypt data using this user's secret. + """ h = hmac.new(self._secret, doc_id).hexdigest() return self.decrypt(data, passphrase=h) - #def publish_pubkey(self): - # return self._gpg.export_keys( + #------------------------------------------------------------------------- + # Document storage, retrieval and sync + #------------------------------------------------------------------------- + + def put(self, doc_id, data): + """ + Store a document. + """ + pass + + def get(self, doc_id): + """ + Retrieve a document. + """ + pass + + def sync(self): + """ + Synchronize with LEAP server. + """ + pass -- cgit v1.2.3