Exemple #1
0
    def _encrypt_message(self, uuid_pubkey, address, message):
        """
        Given a UUID, a public key, address and a message, it encrypts
        the message to that public key.
        The address is needed in order to build the OpenPGPKey object.

        @param uuid_pubkey: tuple that holds the uuid and the public
        key as it is returned by the previous call in the chain
        @type uuid_pubkey: tuple (str, str)
        @param address: mail address for this message
        @type address: str
        @param message: message contents
        @type message: str

        @return: uuid, doc to sync with Soledad
        @rtype: tuple(str, SoledadDocument)
        """
        uuid, pubkey = uuid_pubkey
        log.msg("Encrypting message to %s's pubkey" % (uuid,))
        log.msg("Pubkey: %s" % (pubkey,))

        doc = SoledadDocument(doc_id=str(pyuuid.uuid4()))

        data = {'incoming': True, 'content': message}

        if pubkey is None or len(pubkey) == 0:
            doc.content = {
                self.INCOMING_KEY: True,
                ENC_SCHEME_KEY: EncryptionSchemes.NONE,
                ENC_JSON_KEY: json.dumps(data)
            }
            return uuid, doc

        openpgp_key = None
        with openpgp.TempGPGWrapper(gpgbinary='/usr/bin/gpg') as gpg:
            gpg.import_keys(pubkey)
            key = gpg.list_keys().pop()
            openpgp_key = openpgp._build_key_from_gpg(address, key, pubkey)

            doc.content = {
                self.INCOMING_KEY: True,
                ENC_SCHEME_KEY: EncryptionSchemes.PUBKEY,
                ENC_JSON_KEY: str(gpg.encrypt(
                    json.dumps(data),
                    openpgp_key.fingerprint,
                    symmetric=False))
            }

        return uuid, doc
Exemple #2
0
    def _put_secrets_in_shared_db(self):
        """
        Assert local keys are the same as shared db's ones.

        Try to fetch keys from shared recovery database. If they already exist
        in the remote db, assert that that data is the same as local data.
        Otherwise, upload keys to shared recovery database.
        """
        soledad_assert(
            self._has_secret(),
            'Tried to send keys to server but they don\'t exist in local '
            'storage.')
        # try to get secrets doc from server, otherwise create it
        doc = self._get_secrets_from_shared_db()
        if doc is None:
            doc = SoledadDocument(
                doc_id=self._shared_db_doc_id())
        # fill doc with encrypted secrets
        doc.content = self.export_recovery_document()
        # upload secrets to server
        signal(SOLEDAD_UPLOADING_KEYS, self._uuid)
        db = self._shared_db
        if not db:
            logger.warning('No shared db found')
            return
        db.put_doc(doc)
        signal(SOLEDAD_DONE_UPLOADING_KEYS, self._uuid)
Exemple #3
0
 def test_stage2_bootstrap_signals(self):
     """
     Test that if there are keys in server, soledad will download them and
     emit corresponding signals.
     """
     # get existing instance so we have access to keys
     sol = self._soledad_instance()
     # create a document with secrets
     doc = SoledadDocument(doc_id=sol.secrets._shared_db_doc_id())
     doc.content = sol.secrets._export_recovery_document()
     sol.close()
     # reset mock
     soledad.client.secrets.events.emit.reset_mock()
     # get a fresh instance so it emits all bootstrap signals
     shared_db = self.get_default_shared_mock(get_doc_return_value=doc)
     sol = self._soledad_instance(secrets_path='alternative_stage2.json',
                                  local_db_path='alternative_stage2.u1db',
                                  shared_db_class=shared_db)
     # reverse call order so we can verify in the order the signals were
     # expected
     soledad.client.secrets.events.emit.mock_calls.reverse()
     soledad.client.secrets.events.emit.call_args = \
         soledad.client.secrets.events.emit.call_args_list[0]
     soledad.client.secrets.events.emit.call_args_list.reverse()
     # assert download keys signals
     soledad.client.secrets.events.emit.assert_called_with(
         catalog.SOLEDAD_DOWNLOADING_KEYS,
         ADDRESS,
     )
     self._pop_mock_call(soledad.client.secrets.events.emit)
     soledad.client.secrets.events.emit.assert_called_with(
         catalog.SOLEDAD_DONE_DOWNLOADING_KEYS,
         ADDRESS,
     )
     sol.close()
Exemple #4
0
 def test_stage2_bootstrap_signals(self):
     """
     Test that if there are keys in server, soledad will download them and
     emit corresponding signals.
     """
     # get existing instance so we have access to keys
     sol = self._soledad_instance()
     # create a document with secrets
     doc = SoledadDocument(doc_id=sol.secrets._shared_db_doc_id())
     doc.content = sol.secrets._export_recovery_document()
     sol.close()
     # reset mock
     soledad.client.secrets.events.emit_async.reset_mock()
     # get a fresh instance so it emits all bootstrap signals
     shared_db = self.get_default_shared_mock(get_doc_return_value=doc)
     sol = self._soledad_instance(
         secrets_path="alternative_stage2.json", local_db_path="alternative_stage2.u1db", shared_db_class=shared_db
     )
     # reverse call order so we can verify in the order the signals were
     # expected
     soledad.client.secrets.events.emit_async.mock_calls.reverse()
     soledad.client.secrets.events.emit_async.call_args = soledad.client.secrets.events.emit_async.call_args_list[0]
     soledad.client.secrets.events.emit_async.call_args_list.reverse()
     # assert download keys signals
     soledad.client.secrets.events.emit_async.assert_called_with(
         catalog.SOLEDAD_DOWNLOADING_KEYS, {"userid": ADDRESS, "uuid": ADDRESS}
     )
     self._pop_mock_call(soledad.client.secrets.events.emit_async)
     soledad.client.secrets.events.emit_async.assert_called_with(
         catalog.SOLEDAD_DONE_DOWNLOADING_KEYS, {"userid": ADDRESS, "uuid": ADDRESS}
     )
     sol.close()
Exemple #5
0
    def _put_secrets_in_shared_db(self):
        """
        Assert local keys are the same as shared db's ones.

        Try to fetch keys from shared recovery database. If they already exist
        in the remote db, assert that that data is the same as local data.
        Otherwise, upload keys to shared recovery database.
        """
        soledad_assert(
            self._has_secret(),
            'Tried to send keys to server but they don\'t exist in local '
            'storage.')
        # try to get secrets doc from server, otherwise create it
        doc = self._get_secrets_from_shared_db()
        if doc is None:
            doc = SoledadDocument(doc_id=self._shared_db_doc_id())
        # fill doc with encrypted secrets
        doc.content = self.export_recovery_document()
        # upload secrets to server
        signal(SOLEDAD_UPLOADING_KEYS, self._uuid)
        db = self._shared_db
        if not db:
            logger.warning('No shared db found')
            return
        db.put_doc(doc)
        signal(SOLEDAD_DONE_UPLOADING_KEYS, self._uuid)
Exemple #6
0
 def save_remote(self, encrypted):
     doc = self._remote_doc
     if not doc:
         doc = SoledadDocument(doc_id=self._remote_doc_id())
     doc.content = encrypted
     db = self._shared_db
     if not db:
         logger.warn('no shared db found')
         return
     db.put_doc(doc)
Exemple #7
0
 def test_doc_update_succeeds(self):
     doc_id = 'some-random-doc'
     self.assertIsNone(self._db.get_doc(doc_id))
     # create a document in shared db
     doc = SoledadDocument(doc_id=doc_id)
     self._db.put_doc(doc)
     # update that document
     expected = {'new': 'content'}
     doc.content = expected
     self._db.put_doc(doc)
     # ensure expected content was saved
     doc = self._db.get_doc(doc_id)
     self.assertEqual(expected, doc.content)
Exemple #8
0
 def test_doc_update_succeeds(self):
     doc_id = 'some-random-doc'
     self.assertIsNone(self._db.get_doc(doc_id))
     # create a document in shared db
     doc = SoledadDocument(doc_id=doc_id)
     self._db.put_doc(doc)
     # update that document
     expected = {'new': 'content'}
     doc.content = expected
     self._db.put_doc(doc)
     # ensure expected content was saved
     doc = self._db.get_doc(doc_id)
     self.assertEqual(expected, doc.content)
Exemple #9
0
    def test_extra_comma(self):
        doc = SoledadDocument('i', rev='r')
        doc.content = {'a': 'b'}

        encrypted_docstr = _crypto.SoledadCrypto('safe').encrypt_doc(doc)

        with self.assertRaises(l2db.errors.BrokenSyncStream):
            self.parse("[\r\n{},\r\n]")

        with self.assertRaises(l2db.errors.BrokenSyncStream):
            self.parse(('[\r\n{},\r\n{"id": "i", "rev": "r", ' +
                        '"gen": 3, "trans_id": "T-sid"},\r\n' + '%s,\r\n]') %
                       encrypted_docstr)
Exemple #10
0
    def test_extra_comma(self):
        doc = SoledadDocument('i', rev='r')
        doc.content = {'a': 'b'}

        encrypted_docstr = _crypto.SoledadCrypto('safe').encrypt_doc(doc)

        with self.assertRaises(l2db.errors.BrokenSyncStream):
            self.parse("[\r\n{},\r\n]")

        with self.assertRaises(l2db.errors.BrokenSyncStream):
            self.parse(
                ('[\r\n{},\r\n{"id": "i", "rev": "r", ' +
                 '"gen": 3, "trans_id": "T-sid"},\r\n' +
                 '%s,\r\n]') % encrypted_docstr)
Exemple #11
0
 def test_decrypt_with_unknown_mac_method_raises(self):
     """
     Trying to decrypt a document with unknown MAC method should raise.
     """
     simpledoc = {'key': 'val'}
     doc = SoledadDocument(doc_id='id')
     doc.content = simpledoc
     # encrypt doc
     doc.set_json(self._soledad._crypto.encrypt_doc(doc))
     self.assertTrue(MAC_KEY in doc.content)
     self.assertTrue(MAC_METHOD_KEY in doc.content)
     # mess with MAC method
     doc.content[MAC_METHOD_KEY] = 'mymac'
     # try to decrypt doc
     self.assertRaises(UnknownMacMethodError,
                       self._soledad._crypto.decrypt_doc, doc)
Exemple #12
0
 def test_decrypt_with_wrong_mac_raises(self):
     """
     Trying to decrypt a document with wrong MAC should raise.
     """
     simpledoc = {'key': 'val'}
     doc = SoledadDocument(doc_id='id')
     doc.content = simpledoc
     # encrypt doc
     doc.set_json(self._soledad._crypto.encrypt_doc(doc))
     self.assertTrue(MAC_KEY in doc.content)
     self.assertTrue(MAC_METHOD_KEY in doc.content)
     # mess with MAC
     doc.content[MAC_KEY] = '1234567890ABCDEF'
     # try to decrypt doc
     self.assertRaises(WrongMacError, self._soledad._crypto.decrypt_doc,
                       doc)
    def testLogErrorIfDecryptFails(self):
        def assert_failure(_):
            mock_logger_error.assert_any_call('_decrypt_doc: '
                                              'Error decrypting document with '
                                              'ID 1')

        with patch.object(Logger, 'error') as mock_logger_error:
            doc = SoledadDocument()
            doc.doc_id = '1'
            doc.content = {'_enc_json': ''}

            self.fetcher._process_decrypted_doc = Mock()
            self.km.decrypt = Mock(return_value=defer.fail(Exception()))

            d = self.fetcher._decrypt_doc(doc)
            d.addCallback(assert_failure)
            return d
Exemple #14
0
    def test_extra_comma(self):
        """
        Test adapted to use encrypted content.
        """
        doc = SoledadDocument('i', rev='r')
        doc.content = {}
        enc_json = target.encrypt_doc(self._soledad._crypto, doc)
        tgt = target.SoledadSyncTarget("http://foo/foo",
                                       crypto=self._soledad._crypto)

        self.assertRaises(u1db.errors.BrokenSyncStream, tgt._parse_sync_stream,
                          "[\r\n{},\r\n]", None)
        self.assertRaises(
            u1db.errors.BrokenSyncStream, tgt._parse_sync_stream,
            '[\r\n{},\r\n{"id": "i", "rev": "r", '
            '"content": %s, "gen": 3, "trans_id": "T-sid"}'
            ',\r\n]' % json.dumps(enc_json), lambda doc, gen, trans_id: None)
Exemple #15
0
    def test_stage2_bootstrap_signals(self):
        """
        Test that if there are keys in server, soledad will download them and
        emit corresponding signals.
        """
        # get existing instance so we have access to keys
        sol = self._soledad_instance()
        # create a document with secrets
        doc = SoledadDocument(doc_id=sol._shared_db_doc_id())
        doc.content = sol.export_recovery_document()

        class Stage2MockSharedDB(object):

            get_doc = Mock(return_value=doc)
            put_doc = Mock()
            lock = Mock(return_value=('atoken', 300))
            unlock = Mock()

            def __call__(self):
                return self

        sol.close()
        # reset mock
        soledad.client.signal.reset_mock()
        # get a fresh instance so it emits all bootstrap signals
        sol = self._soledad_instance(
            secrets_path='alternative_stage2.json',
            local_db_path='alternative_stage2.u1db',
            shared_db_class=Stage2MockSharedDB)
        # reverse call order so we can verify in the order the signals were
        # expected
        soledad.client.signal.mock_calls.reverse()
        soledad.client.signal.call_args = \
            soledad.client.signal.call_args_list[0]
        soledad.client.signal.call_args_list.reverse()
        # assert download keys signals
        soledad.client.signal.assert_called_with(
            proto.SOLEDAD_DOWNLOADING_KEYS,
            ADDRESS,
        )
        self._pop_mock_call(soledad.client.signal)
        soledad.client.signal.assert_called_with(
            proto.SOLEDAD_DONE_DOWNLOADING_KEYS,
            ADDRESS,
        )
        sol.close()
Exemple #16
0
 def test_decrypt_with_unknown_mac_method_raises(self):
     """
     Trying to decrypt a document with unknown MAC method should raise.
     """
     simpledoc = {'key': 'val'}
     doc = SoledadDocument(doc_id='id')
     doc.content = simpledoc
     # encrypt doc
     doc.set_json(crypto.encrypt_doc(self._soledad._crypto, doc))
     self.assertTrue(MAC_KEY in doc.content)
     self.assertTrue(MAC_METHOD_KEY in doc.content)
     # mess with MAC method
     doc.content[MAC_METHOD_KEY] = 'mymac'
     # try to decrypt doc
     self.assertRaises(
         UnknownMacMethodError,
         crypto.decrypt_doc, self._soledad._crypto, doc)
Exemple #17
0
 def test_decrypt_with_wrong_mac_raises(self):
     """
     Trying to decrypt a document with wrong MAC should raise.
     """
     simpledoc = {'key': 'val'}
     doc = SoledadDocument(doc_id='id')
     doc.content = simpledoc
     # encrypt doc
     doc.set_json(crypto.encrypt_doc(self._soledad._crypto, doc))
     self.assertTrue(MAC_KEY in doc.content)
     self.assertTrue(MAC_METHOD_KEY in doc.content)
     # mess with MAC
     doc.content[MAC_KEY] = '1234567890ABCDEF'
     # try to decrypt doc
     self.assertRaises(
         WrongMacError,
         crypto.decrypt_doc, self._soledad._crypto, doc)
Exemple #18
0
    def test_stage2_bootstrap_signals(self):
        """
        Test that if there are keys in server, soledad will download them and
        emit corresponding signals.
        """
        # get existing instance so we have access to keys
        sol = self._soledad_instance()
        # create a document with secrets
        doc = SoledadDocument(doc_id=sol._shared_db_doc_id())
        doc.content = sol.export_recovery_document()

        class Stage2MockSharedDB(object):

            get_doc = Mock(return_value=doc)
            put_doc = Mock()
            lock = Mock(return_value=('atoken', 300))
            unlock = Mock()

            def __call__(self):
                return self

        sol.close()
        # reset mock
        soledad.client.signal.reset_mock()
        # get a fresh instance so it emits all bootstrap signals
        sol = self._soledad_instance(secrets_path='alternative_stage2.json',
                                     local_db_path='alternative_stage2.u1db',
                                     shared_db_class=Stage2MockSharedDB)
        # reverse call order so we can verify in the order the signals were
        # expected
        soledad.client.signal.mock_calls.reverse()
        soledad.client.signal.call_args = \
            soledad.client.signal.call_args_list[0]
        soledad.client.signal.call_args_list.reverse()
        # assert download keys signals
        soledad.client.signal.assert_called_with(
            proto.SOLEDAD_DOWNLOADING_KEYS,
            ADDRESS,
        )
        self._pop_mock_call(soledad.client.signal)
        soledad.client.signal.assert_called_with(
            proto.SOLEDAD_DONE_DOWNLOADING_KEYS,
            ADDRESS,
        )
        sol.close()
Exemple #19
0
    def test_extra_comma(self):
        """
        Test adapted to use encrypted content.
        """
        doc = SoledadDocument('i', rev='r')
        doc.content = {}
        enc_json = target.encrypt_doc(self._soledad._crypto, doc)
        tgt = target.SoledadSyncTarget(
            "http://foo/foo", crypto=self._soledad._crypto)

        self.assertRaises(u1db.errors.BrokenSyncStream,
                          tgt._parse_sync_stream, "[\r\n{},\r\n]", None)
        self.assertRaises(u1db.errors.BrokenSyncStream,
                          tgt._parse_sync_stream,
                          '[\r\n{},\r\n{"id": "i", "rev": "r", '
                          '"content": %s, "gen": 3, "trans_id": "T-sid"}'
                          ',\r\n]' % json.dumps(enc_json),
                          lambda doc, gen, trans_id: None)
Exemple #20
0
    def test_encrypt_decrypt_json(self):
        """
        Test encrypting and decrypting documents.
        """
        simpledoc = {'key': 'val'}
        doc1 = SoledadDocument(doc_id='id')
        doc1.content = simpledoc

        # encrypt doc
        doc1.set_json(self._soledad._crypto.encrypt_doc(doc1))
        # assert content is different and includes keys
        self.assertNotEqual(simpledoc, doc1.content,
                            'incorrect document encryption')
        self.assertTrue(ENC_JSON_KEY in doc1.content)
        self.assertTrue(ENC_SCHEME_KEY in doc1.content)
        # decrypt doc
        doc1.set_json(self._soledad._crypto.decrypt_doc(doc1))
        self.assertEqual(simpledoc, doc1.content,
                         'incorrect document encryption')
Exemple #21
0
 def test_encrypt_decrypt_json(self):
     """
     Test encrypting and decrypting documents.
     """
     simpledoc = {'key': 'val'}
     doc1 = SoledadDocument(doc_id='id')
     doc1.content = simpledoc
     # encrypt doc
     doc1.set_json(target.encrypt_doc(self._soledad._crypto, doc1))
     # assert content is different and includes keys
     self.assertNotEqual(
         simpledoc, doc1.content,
         'incorrect document encryption')
     self.assertTrue(target.ENC_JSON_KEY in doc1.content)
     self.assertTrue(target.ENC_SCHEME_KEY in doc1.content)
     # decrypt doc
     doc1.set_json(target.decrypt_doc(self._soledad._crypto, doc1))
     self.assertEqual(
         simpledoc, doc1.content, 'incorrect document encryption')
Exemple #22
0
    def test_extra_comma(self):
        """
        Test adapted to use encrypted content.
        """
        doc = SoledadDocument('i', rev='r')
        doc.content = {}
        _crypto = self._soledad._crypto
        key = _crypto.doc_passphrase(doc.doc_id)
        secret = _crypto.secret

        enc_json = crypto.encrypt_docstr(doc.get_json(), doc.doc_id, doc.rev,
                                         key, secret)

        with self.assertRaises(u1db.errors.BrokenSyncStream):
            self.target._parse_received_doc_response("[\r\n{},\r\n]")

        with self.assertRaises(u1db.errors.BrokenSyncStream):
            self.target._parse_received_doc_response(
                ('[\r\n{},\r\n{"id": "i", "rev": "r", ' +
                 '"content": %s, "gen": 3, "trans_id": "T-sid"}' + ',\r\n]') %
                json.dumps(enc_json))
    def testFlagMessageOnBadJsonWhileDecrypting(self):
        doc = SoledadDocument()
        doc.doc_id = '1'
        doc.content = {'_enc_json': ''}

        err = ValueError('No JSON object could be decoded')

        def assert_failure():
            mock_logger_error.assert_any_call('Error while decrypting 1')
            mock_logger_error.assert_any_call(
                'No JSON object could be decoded')
            self.assertEquals(doc.content['errdecr'], True)

        with patch.object(Logger, 'error') as mock_logger_error:
            with patch.object(utils, 'json_loads') as mock_json_loader:
                self.fetcher._update_incoming_message = Mock()
                mock_json_loader.side_effect = err

                self.fetcher._process_decrypted_doc(doc, '')

                assert_failure()
Exemple #24
0
    def test_extra_comma(self):
        """
        Test adapted to use encrypted content.
        """
        doc = SoledadDocument('i', rev='r')
        doc.content = {}
        _crypto = self._soledad._crypto
        key = _crypto.doc_passphrase(doc.doc_id)
        secret = _crypto.secret

        enc_json = crypto.encrypt_docstr(
            doc.get_json(), doc.doc_id, doc.rev,
            key, secret)

        with self.assertRaises(u1db.errors.BrokenSyncStream):
            self.target._parse_received_doc_response("[\r\n{},\r\n]")

        with self.assertRaises(u1db.errors.BrokenSyncStream):
            self.target._parse_received_doc_response(
                ('[\r\n{},\r\n{"id": "i", "rev": "r", ' +
                 '"content": %s, "gen": 3, "trans_id": "T-sid"}' +
                 ',\r\n]') % json.dumps(enc_json))
Exemple #25
0
    def test_stage2_bootstrap_signals(self):
        """
        Test that if there are keys in server, soledad will download them and
        emit corresponding signals.
        """
        # get existing instance so we have access to keys
        sol = self._soledad_instance()
        # create a document with secrets
        doc = SoledadDocument(doc_id=sol.secrets.storage._remote_doc_id())
        doc.content = sol.secrets.crypto.encrypt(sol.secrets._secrets)
        sol.close()
        # reset mock
        soledad.client._secrets.util.events.emit_async.reset_mock()
        # get a fresh instance so it emits all bootstrap signals
        shared_db = self.get_default_shared_mock(get_doc_return_value=doc)
        sol = self._soledad_instance(
            secrets_path='alternative_stage2.json',
            local_db_path='alternative_stage2.u1db',
            shared_db_class=shared_db)
        # reverse call order so we can verify in the order the signals were
        # expected
        mocked = soledad.client._secrets.util.events.emit_async
        mocked.mock_calls.reverse()
        mocked.call_args = mocked.call_args_list[0]
        mocked.call_args_list.reverse()

        def _assert(*args, **kwargs):
            mocked = soledad.client._secrets.util.events.emit_async
            mocked.assert_called_with(*args)
            pop = kwargs.get('pop')
            if pop or pop is None:
                self._pop_mock_call(mocked)

        # assert download keys signals
        user_data = {'userid': ADDRESS, 'uuid': ADDRESS}
        _assert(catalog.SOLEDAD_DOWNLOADING_KEYS, user_data)
        _assert(catalog.SOLEDAD_DONE_DOWNLOADING_KEYS, user_data, pop=False)

        sol.close()