Beispiel #1
0
class TestServer(TestIronhouseBase):
    def test_secure_server(self):
        async def send_async_sec():
            ip = '127.0.0.1'
            port = 4523
            client = self.ironhouse.ctx.socket(zmq.REQ)
            client = self.ironhouse.secure_socket(client,
                                                  self.curve_public_key)
            client.connect('tcp://{}:{}'.format(ip, port))
            client.send(self.vk.encode())

            msg = await client.recv()
            client.close()
            self.ironhouse.cleanup()
            self.loop.call_soon_threadsafe(self.loop.stop)

        self.ironhouse.setup_secure_server()
        self.assertIsInstance(self.ironhouse.ctx, zmq.Context,
                              'asynchronous context created incorrectly')
        self.assertIsInstance(self.ironhouse.sec_sock, zmq.sugar.socket.Socket,
                              'unable to secure a socket')

        self.loop.run_until_complete(asyncio.ensure_future(send_async_sec()))

    def test_authenticate(self):
        port = 5523

        async def send_async_sec():
            authorized = await self.ironhouse.authenticate(
                self.fake['curve_key'], '127.0.0.1', port)
            self.assertTrue(authorized)
            self.ironhouse.cleanup()
            self.fake_ironhouse.cleanup()
            self.loop.stop()

        self.fake = genkeys(
            '91f7021a9e8c65ca873747ae24de08e0a7acf58159a8aa6548910fe152dab3d8')
        self.fake_ironhouse = Ironhouse(self.fake['sk'],
                                        wipe_certs=True,
                                        auth_validate=auth_validate,
                                        auth_port=port,
                                        keyname='fake')
        self.fake_ironhouse.create_from_public_key(self.curve_public_key)
        self.fake_ironhouse.setup_secure_server()
        self.ironhouse.create_from_public_key(self.fake['curve_key'])
        self.ironhouse.setup_secure_server()

        self.loop.run_until_complete(asyncio.ensure_future(send_async_sec()))

    def test_authenticate_self(self):
        async def send_async_sec():
            authorized = await self.ironhouse.authenticate(
                self.curve_public_key, '127.0.0.1')
            self.assertTrue(authorized)
            self.ironhouse.cleanup()
            self.loop.stop()

        self.ironhouse.setup_secure_server()

        self.loop.run_until_complete(asyncio.ensure_future(send_async_sec()))

    def test_authenticate_fail(self):
        async def send_async_sec():
            authorized = await self.ironhouse.authenticate(
                b'A/c=Kn2)aHRI*>fK-{v*r^YCyXJ//3.CGQQC@A9J', '127.0.0.1')
            self.assertEqual(authorized, 'no_reply')
            self.ironhouse.cleanup()
            self.loop.stop()

        self.ironhouse.setup_secure_server()

        self.loop.run_until_complete(asyncio.ensure_future(send_async_sec()))

    def test_auth_validate(self):
        port = 5523
        self.validated = False

        async def send_async_sec():
            authorized = await self.ironhouse.authenticate(
                self.fake['curve_key'], '127.0.0.1', port)
            self.assertTrue(authorized)
            self.assertTrue(self.validated)
            self.ironhouse.cleanup()
            self.fake_ironhouse.cleanup()
            self.loop.stop()

        def auth_validate_fake(vk):
            self.validated = True
            return True

        def auth_validate(vk):
            return vk == 'b9284b28589523f055ae5b54c98b0b904a1df3b0be5d546d30208d0516e71aa0'

        self.fake = genkeys(
            '7ae3fcfd3a9047adbec6ad11e5a58036df9934dc0746431d80b49d25584d7e78')
        self.fake_ironhouse = Ironhouse(self.fake['sk'],
                                        wipe_certs=True,
                                        auth_validate=auth_validate,
                                        auth_port=port,
                                        keyname='fake')
        self.fake_ironhouse.setup_secure_server()
        self.fake_ironhouse.create_from_public_key(self.curve_public_key)
        self.fake_ironhouse.auth_validate = auth_validate_fake
        self.ironhouse.setup_secure_server()
        self.ironhouse.create_from_public_key(self.fake['curve_key'])
        self.ironhouse.auth_validate = auth_validate

        self.loop.run_until_complete(asyncio.ensure_future(send_async_sec()))

    def test_auth_validate_default(self):
        port = 5523

        async def send_async_sec():
            authorized = await self.dih.authenticate(self.a['curve_key'],
                                                     '127.0.0.1', port)
            self.assertTrue(authorized)
            self.aih.cleanup()
            self.dih.cleanup()
            self.loop.stop()

        self.a = genkeys(
            '5664ec7306cc22e56820ae988b983bdc8ebec8246cdd771cfee9671299e98e3c')
        self.aih = Ironhouse(self.a['sk'],
                             wipe_certs=True,
                             auth_port=port,
                             keyname='a')
        self.aih.setup_secure_server()
        self.aih.create_from_public_key(self.curve_public_key)

        self.dih = Ironhouse(self.sk, wipe_certs=True)
        self.dih.setup_secure_server()
        self.dih.create_from_public_key(self.a['curve_key'])

        self.loop.run_until_complete(asyncio.ensure_future(send_async_sec()))

    def test_auth_invalid_public_key(self):
        async def send_async_sec():
            authorized = await self.ironhouse.authenticate(
                b'ack', '127.0.0.1', 1234)
            self.assertEqual(authorized, 'invalid')
            self.ironhouse.cleanup()
            self.loop.stop()

        self.ironhouse.setup_secure_server()

        self.loop.run_until_complete(asyncio.ensure_future(send_async_sec()))

    def test_auth_validate_fail_validate(self):
        port = 5523
        self.validated = False

        async def send_async_sec():
            authorized = await self.ironhouse.authenticate(
                self.fake['curve_key'], '127.0.0.1', port)
            self.assertEqual(authorized, 'unauthorized')
            self.assertTrue(self.validated)
            self.ironhouse.cleanup()
            self.fake_ironhouse.cleanup()
            self.loop.stop()

        def auth_validate_fake(vk):
            self.validated = True
            return True

        def auth_validate(vk):
            return vk == b'catastrophe'

        self.fake = genkeys(
            '7ae3fcfd3a9047adbec6ad11e5a58036df9934dc0746431d80b49d25584d7e78')
        self.fake_ironhouse = Ironhouse(self.fake['sk'],
                                        wipe_certs=True,
                                        auth_validate=auth_validate,
                                        auth_port=port,
                                        keyname='fake')
        self.fake_ironhouse.setup_secure_server()
        self.fake_ironhouse.create_from_public_key(self.curve_public_key)
        self.fake_ironhouse.auth_validate = auth_validate_fake
        self.ironhouse.setup_secure_server()
        self.ironhouse.create_from_public_key(self.fake['curve_key'])
        self.ironhouse.auth_validate = auth_validate

        self.loop.run_until_complete(asyncio.ensure_future(send_async_sec()))

    def test_auth_validate_fail_timeout(self):
        port = 5523
        self.validated = False

        async def send_async_sec():
            authorized = await self.ironhouse.authenticate(
                self.fake['curve_key'], '127.0.0.1', port)
            self.assertEqual(authorized, 'no_reply')
            self.assertTrue(self.validated)
            self.ironhouse.cleanup()
            self.fake_ironhouse.cleanup()
            self.loop.stop()

        def auth_validate_fake(vk):
            self.validated = True
            return False

        def auth_validate(vk):
            return vk == b'catastrophe'

        self.fake = genkeys(
            '7ae3fcfd3a9047adbec6ad11e5a58036df9934dc0746431d80b49d25584d7e78')
        self.fake_ironhouse = Ironhouse(self.fake['sk'],
                                        wipe_certs=True,
                                        auth_validate=auth_validate,
                                        auth_port=port,
                                        keyname='fake')
        self.fake_ironhouse.setup_secure_server()
        self.fake_ironhouse.create_from_public_key(self.curve_public_key)
        self.fake_ironhouse.auth_validate = auth_validate_fake
        self.ironhouse.setup_secure_server()
        self.ironhouse.create_from_public_key(self.fake['curve_key'])
        self.ironhouse.auth_validate = auth_validate

        self.loop.run_until_complete(asyncio.ensure_future(send_async_sec()))

    def test_cleanup(self):
        async def delay():
            await asyncio.sleep(0.1)
            del self.ironhouse
            self.loop.stop()

        self.loop.run_until_complete(asyncio.ensure_future(delay()))

    def tearDown(self):
        self.loop.close()
Beispiel #2
0
class TestIronhouse(TestCase):
    def setUp(self):
        self.sk = '06391888e37a48cef1ded85a375490df4f9b2c74f7723e88c954a055f3d2685a'
        self.vk = '82540bb5a9c84162214c5540d6e43be49bbfe19cf49685660cab608998a65144'
        self.private_key = 'f0ca3d349e56e419e72f11c1fd734ae929a483f9490907d2ded554d9f794f361'
        self.public_key = '73619fa1464ce16802b480a0fd7868ffcce0f7285050a927a07ef1ffdd34c162'
        self.curve_public_key = b'B77YmmOI=O0<)GJ@DJ2Q+&5jzp/absPNMCh?88@S'
        self.ironhouse = Ironhouse(self.sk,
                                   wipe_certs=True,
                                   auth_validate=auth_validate)
        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)

    def test_assert_paths(self):
        self.assertEqual(self.ironhouse.base_dir, 'certs/ironhouse',
                         'key folder is incorrect')
        self.assertEqual(self.ironhouse.keys_dir,
                         'certs/ironhouse/certificates',
                         'keys dir is incorrect')
        self.assertEqual(self.ironhouse.public_keys_dir,
                         'certs/ironhouse/public_keys',
                         'public dir is incorrect')
        self.assertEqual(self.ironhouse.secret_keys_dir,
                         'certs/ironhouse/private_keys',
                         'secret dir is incorrect')
        self.assertEqual(self.ironhouse.secret_file,
                         'certs/ironhouse/private_keys/ironhouse.key_secret',
                         'secret_file is incorrect')

    def test_generate_certificates_failed(self):
        self.ironhouse.wipe_certs = False
        shutil.rmtree(self.ironhouse.base_dir)
        self.ironhouse.generate_certificates(self.sk)
        self.assertFalse(listdir(self.ironhouse.public_keys_dir),
                         'public keys dir should not be created')
        self.assertFalse(listdir(self.ironhouse.secret_keys_dir),
                         'secret keys dir should not be created')
        self.assertTrue(
            listdir(self.ironhouse.keys_dir) == [],
            'certificate keys dir should not be created')

    def test_generate_certificates(self):
        self.ironhouse.generate_certificates(self.sk)
        self.assertTrue(listdir(self.ironhouse.public_keys_dir),
                        'public keys dir not created')
        self.assertTrue(listdir(self.ironhouse.secret_keys_dir),
                        'secret keys dir not created')
        self.assertTrue(
            listdir(self.ironhouse.keys_dir) == [],
            'certificate keys is not empty')
        self.assertTrue(exists(self.ironhouse.secret_file),
                        'secret keys not created')
        self.assertEqual(self.private_key,
                         decode(self.ironhouse.secret).hex(),
                         'secret key generation is incorrect')
        self.assertEqual(self.public_key,
                         decode(self.ironhouse.public_key).hex(),
                         'public key generation is incorrect')

    def test_vk2pk(self):
        self.assertEqual(
            decode(self.ironhouse.vk2pk(self.vk)).hex(), self.public_key,
            'conversion of vk to pk failed')

    def test_generate_from_private_key(self):
        self.ironhouse.create_from_private_key(self.private_key)
        self.assertTrue(listdir(self.ironhouse.public_keys_dir),
                        'public keys dir not created')
        self.assertTrue(listdir(self.ironhouse.secret_keys_dir),
                        'secret keys dir not created')
        self.assertTrue(listdir(self.ironhouse.keys_dir),
                        'certificate keys dir not created')
        self.assertTrue(exists(self.ironhouse.secret_file),
                        'secret keys not created')
        self.assertEqual(self.private_key,
                         decode(self.ironhouse.secret).hex(),
                         'secret key generation is incorrect')
        self.assertEqual(self.public_key,
                         decode(self.ironhouse.public_key).hex(),
                         'public key generation is incorrect')

    def test_generate_from_public_key(self):
        self.ironhouse.create_from_public_key(encode(self.public_key.encode()))
        self.assertTrue(listdir(self.ironhouse.public_keys_dir),
                        'public keys dir not created')
        self.assertTrue(
            exists('{}/ironhouse.key'.format(self.ironhouse.public_keys_dir)),
            'public key not generated')

    def test_secure_context_async(self):
        ctx, auth = self.ironhouse.secure_context(async=True)
        self.assertIsInstance(ctx, zmq.asyncio.Context,
                              'asynchronous context created incorrectly')
        self.assertIsInstance(auth, AsyncioAuthenticator,
                              'synchronous auth object created incorrectly')
        auth.stop()

    def test_secure_context_sync(self):
        ctx, auth = self.ironhouse.secure_context(async=False)
        self.assertIsInstance(ctx, zmq.Context,
                              'synchronous context created incorrectly')
        self.assertIsInstance(auth, ThreadAuthenticator,
                              'synchronous auth object created incorrectly')
        auth.stop()

    def test_secure_socket_sync(self):
        ctx, auth = self.ironhouse.secure_context(async=False)
        sock = ctx.socket(zmq.REP)
        sec_sock = self.ironhouse.secure_socket(sock, curve_serverkey=None)
        self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket,
                              'unable to secure REP socket')

        sock = ctx.socket(zmq.REQ)
        sec_sock = self.ironhouse.secure_socket(
            sock, curve_serverkey=self.curve_public_key)
        self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket,
                              'unable to secure REQ socket')

        sock = ctx.socket(zmq.PUSH)
        sec_sock = self.ironhouse.secure_socket(sock, curve_serverkey=None)
        self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket,
                              'unable to secure PUSH socket')

        sock = ctx.socket(zmq.PULL)
        sec_sock = self.ironhouse.secure_socket(
            sock, curve_serverkey=self.curve_public_key)
        self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket,
                              'unable to secure PULL socket')

        sock = ctx.socket(zmq.DEALER)
        sec_sock = self.ironhouse.secure_socket(
            sock, curve_serverkey=self.curve_public_key)
        self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket,
                              'unable to secure DEALER socket')

        sock = ctx.socket(zmq.ROUTER)
        sec_sock = self.ironhouse.secure_socket(sock, curve_serverkey=None)
        self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket,
                              'unable to secure ROUTER socket')

        sock = ctx.socket(zmq.PUB)
        sec_sock = self.ironhouse.secure_socket(sock, curve_serverkey=None)
        self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket,
                              'unable to secure PUB socket')

        sock = ctx.socket(zmq.SUB)
        sec_sock = self.ironhouse.secure_socket(
            sock, curve_serverkey=self.curve_public_key)
        self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket,
                              'unable to secure SUB socket')
        sec_sock.close()
        auth.stop()

    def test_secure_socket_async(self):
        ctx, auth = self.ironhouse.secure_context(async=True)
        sock = ctx.socket(zmq.REP)
        sec_sock = self.ironhouse.secure_socket(sock, curve_serverkey=None)
        self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket,
                              'unable to secure REP socket')

        sock = ctx.socket(zmq.REQ)
        sec_sock = self.ironhouse.secure_socket(
            sock, curve_serverkey=self.curve_public_key)
        self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket,
                              'unable to secure REQ socket')

        sock = ctx.socket(zmq.PUSH)
        sec_sock = self.ironhouse.secure_socket(sock, curve_serverkey=None)
        self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket,
                              'unable to secure PUSH socket')

        sock = ctx.socket(zmq.PULL)
        sec_sock = self.ironhouse.secure_socket(
            sock, curve_serverkey=self.curve_public_key)
        self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket,
                              'unable to secure PULL socket')

        sock = ctx.socket(zmq.DEALER)
        sec_sock = self.ironhouse.secure_socket(
            sock, curve_serverkey=self.curve_public_key)
        self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket,
                              'unable to secure DEALER socket')

        sock = ctx.socket(zmq.ROUTER)
        sec_sock = self.ironhouse.secure_socket(sock, curve_serverkey=None)
        self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket,
                              'unable to secure ROUTER socket')

        sock = ctx.socket(zmq.PUB)
        sec_sock = self.ironhouse.secure_socket(sock, curve_serverkey=None)
        self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket,
                              'unable to secure PUB socket')

        sock = ctx.socket(zmq.SUB)
        sec_sock = self.ironhouse.secure_socket(
            sock, curve_serverkey=self.curve_public_key)
        self.assertIsInstance(sec_sock, zmq.sugar.socket.Socket,
                              'unable to secure SUB socket')

        sec_sock.close()
        auth.stop()

    def test_reconfigure_curve(self):
        ctx, auth = self.ironhouse.secure_context(async=True)
        sock = ctx.socket(zmq.REP)
        sec_sock = self.ironhouse.secure_socket(sock)
        self.assertIn(self.curve_public_key, auth.certs['*'].keys(),
                      'cannot find cert in auth')
        sec_sock.close()
        auth.stop()

    def test_secure_server(self):
        async def send_async_sec():
            ip = '127.0.0.1'
            port = 4523
            client = self.ironhouse.ctx.socket(zmq.REQ)
            client = self.ironhouse.secure_socket(client,
                                                  self.curve_public_key)
            client.connect('tcp://{}:{}'.format(ip, port))
            client.send(self.vk.encode())

            msg = await client.recv()
            client.close()
            self.ironhouse.cleanup()
            self.loop.call_soon_threadsafe(self.loop.stop)

        self.ironhouse.setup_secure_server()
        self.assertIsInstance(self.ironhouse.ctx, zmq.Context,
                              'asynchronous context created incorrectly')
        self.assertIsInstance(self.ironhouse.sec_sock, zmq.sugar.socket.Socket,
                              'unable to secure a socket')

        self.loop.run_until_complete(asyncio.ensure_future(send_async_sec()))

    def test_authenticate(self):
        port = 5523

        async def send_async_sec():
            authorized = await self.ironhouse.authenticate(
                self.fake['curve_key'], '127.0.0.1', port)
            self.assertTrue(authorized)
            self.ironhouse.cleanup()
            self.fake_ironhouse.cleanup()
            self.loop.stop()

        self.fake = genkeys(
            '91f7021a9e8c65ca873747ae24de08e0a7acf58159a8aa6548910fe152dab3d8')
        self.fake_ironhouse = Ironhouse(self.fake['sk'],
                                        wipe_certs=True,
                                        auth_validate=auth_validate,
                                        auth_port=port,
                                        keyname='fake')
        self.fake_ironhouse.create_from_public_key(self.curve_public_key)
        self.fake_ironhouse.setup_secure_server()
        self.ironhouse.create_from_public_key(self.fake['curve_key'])
        self.ironhouse.setup_secure_server()

        self.loop.run_until_complete(asyncio.ensure_future(send_async_sec()))

    def test_authenticate_self(self):
        async def send_async_sec():
            authorized = await self.ironhouse.authenticate(
                self.curve_public_key, '127.0.0.1')
            self.assertTrue(authorized)
            self.ironhouse.cleanup()
            self.loop.stop()

        self.ironhouse.setup_secure_server()

        self.loop.run_until_complete(asyncio.ensure_future(send_async_sec()))

    def test_authenticate_fail(self):
        async def send_async_sec():
            authorized = await self.ironhouse.authenticate(
                b'A/c=Kn2)aHRI*>fK-{v*r^YCyXJ//3.CGQQC@A9J', '127.0.0.1')
            self.assertFalse(authorized)
            self.ironhouse.cleanup()
            self.loop.stop()

        self.ironhouse.setup_secure_server()

        self.loop.run_until_complete(asyncio.ensure_future(send_async_sec()))

    def test_auth_validate(self):
        port = 5523
        self.validated = False

        async def send_async_sec():
            authorized = await self.ironhouse.authenticate(
                self.fake['curve_key'], '127.0.0.1', port)
            self.assertTrue(authorized)
            self.assertTrue(self.validated)
            self.ironhouse.cleanup()
            self.fake_ironhouse.cleanup()
            self.loop.stop()

        def auth_validate_fake(vk):
            self.validated = True
            return True

        def auth_validate(vk):
            return vk == 'b9284b28589523f055ae5b54c98b0b904a1df3b0be5d546d30208d0516e71aa0'

        self.fake = genkeys(
            '7ae3fcfd3a9047adbec6ad11e5a58036df9934dc0746431d80b49d25584d7e78')
        self.fake_ironhouse = Ironhouse(self.fake['sk'],
                                        wipe_certs=True,
                                        auth_validate=auth_validate,
                                        auth_port=port,
                                        keyname='fake')
        self.fake_ironhouse.create_from_public_key(self.curve_public_key)
        self.fake_ironhouse.auth_validate = auth_validate_fake
        self.fake_ironhouse.setup_secure_server()
        self.ironhouse.create_from_public_key(self.fake['curve_key'])
        self.ironhouse.auth_validate = auth_validate
        self.ironhouse.setup_secure_server()

        self.loop.run_until_complete(asyncio.ensure_future(send_async_sec()))

    def test_auth_validate_default(self):
        port = 5523

        async def send_async_sec():
            authorized = await self.dih.authenticate(self.a['curve_key'],
                                                     '127.0.0.1', port)
            self.assertTrue(authorized)
            self.aih.cleanup()
            self.dih.cleanup()
            self.loop.stop()

        self.a = genkeys(
            '5664ec7306cc22e56820ae988b983bdc8ebec8246cdd771cfee9671299e98e3c')
        self.aih = Ironhouse(self.a['sk'],
                             wipe_certs=True,
                             auth_port=port,
                             keyname='a')
        self.aih.create_from_public_key(self.curve_public_key)
        self.aih.setup_secure_server()

        self.dih = Ironhouse(self.sk, wipe_certs=True)
        self.dih.create_from_public_key(self.a['curve_key'])
        self.dih.setup_secure_server()

        self.loop.run_until_complete(asyncio.ensure_future(send_async_sec()))

    def test_auth_invalid_public_key(self):
        async def send_async_sec():
            authorized = await self.ironhouse.authenticate(
                b'ack', '127.0.0.1', 1234)
            self.assertFalse(authorized)
            self.ironhouse.cleanup()
            self.loop.stop()

        self.ironhouse.setup_secure_server()

        self.loop.run_until_complete(asyncio.ensure_future(send_async_sec()))

    def test_auth_validate_fail_validate(self):
        port = 5523
        self.validated = False

        async def send_async_sec():
            authorized = await self.ironhouse.authenticate(
                self.fake['curve_key'], '127.0.0.1', port)
            self.assertFalse(authorized)
            self.assertTrue(self.validated)
            self.ironhouse.cleanup()
            self.fake_ironhouse.cleanup()
            self.loop.stop()

        def auth_validate_fake(vk):
            self.validated = True
            return True

        def auth_validate(vk):
            return vk == b'catastrophe'

        self.fake = genkeys(
            '7ae3fcfd3a9047adbec6ad11e5a58036df9934dc0746431d80b49d25584d7e78')
        self.fake_ironhouse = Ironhouse(self.fake['sk'],
                                        wipe_certs=True,
                                        auth_validate=auth_validate,
                                        auth_port=port,
                                        keyname='fake')
        self.fake_ironhouse.create_from_public_key(self.curve_public_key)
        self.fake_ironhouse.auth_validate = auth_validate_fake
        self.fake_ironhouse.setup_secure_server()
        self.ironhouse.create_from_public_key(self.fake['curve_key'])
        self.ironhouse.auth_validate = auth_validate
        self.ironhouse.setup_secure_server()

        self.loop.run_until_complete(asyncio.ensure_future(send_async_sec()))

    def test_auth_validate_fail_timeout(self):
        port = 5523
        self.validated = False

        async def send_async_sec():
            authorized = await self.ironhouse.authenticate(
                self.fake['curve_key'], '127.0.0.1', port)
            self.assertFalse(authorized)
            self.assertTrue(self.validated)
            self.ironhouse.cleanup()
            self.fake_ironhouse.cleanup()
            self.loop.stop()

        def auth_validate_fake(vk):
            self.validated = True
            return False

        def auth_validate(vk):
            return vk == b'catastrophe'

        self.fake = genkeys(
            '7ae3fcfd3a9047adbec6ad11e5a58036df9934dc0746431d80b49d25584d7e78')
        self.fake_ironhouse = Ironhouse(self.fake['sk'],
                                        wipe_certs=True,
                                        auth_validate=auth_validate,
                                        auth_port=port,
                                        keyname='fake')
        self.fake_ironhouse.create_from_public_key(self.curve_public_key)
        self.fake_ironhouse.auth_validate = auth_validate_fake
        self.fake_ironhouse.setup_secure_server()
        self.ironhouse.create_from_public_key(self.fake['curve_key'])
        self.ironhouse.auth_validate = auth_validate
        self.ironhouse.setup_secure_server()

        self.loop.run_until_complete(asyncio.ensure_future(send_async_sec()))

    def test_cleanup(self):
        async def delay():
            await asyncio.sleep(0.1)
            del self.ironhouse
            self.loop.stop()

        self.loop.run_until_complete(asyncio.ensure_future(delay()))

    def tearDown(self):
        self.loop.close()
Beispiel #3
0
class Network(object):
    """
    High level view of a node instance.  This is the object that should be
    created to start listening as an active node on the network.
    """

    protocol_class = KademliaProtocol

    def __init__(self,
                 ksize=20,
                 alpha=3,
                 node_id=None,
                 discovery_mode='neighborhood',
                 loop=None,
                 max_peers=64,
                 network_port=None,
                 public_ip=None,
                 *args,
                 **kwargs):
        """
        Create a server instance.  This will start listening on the given port.

        Args:
            ksize (int): The k parameter from the paper
            alpha (int): The alpha parameter from the paper
            node_id: The id for this node on the network.
        """
        self.loop = loop if loop else asyncio.get_event_loop()
        asyncio.set_event_loop(self.loop)
        self.vkcache = Bidict()
        self.authorized_node = {}
        self.ksize = ksize
        self.alpha = alpha
        self.transport = None
        self.protocol = None
        self.refresh_loop = None
        self.save_state_loop = None
        self.max_peers = max_peers
        self.network_port = network_port
        self.heartbeat_port = self.network_port + HEARTBEAT_PORT_OFFSET
        self.daemon = kwargs.get('daemon')
        self.ironhouse = Ironhouse(auth_port=self.network_port +
                                   AUTH_PORT_OFFSET,
                                   *args,
                                   **kwargs)
        self.node = Node(node_id=digest(self.ironhouse.vk),
                         public_key=self.ironhouse.public_key,
                         ip=public_ip or os.getenv('HOST_IP', '127.0.0.1'),
                         port=self.network_port)
        self.setup_stethoscope()
        self.ironhouse.setup_secure_server()
        self.listen()
        self.saveStateRegularly('state.tmp')

    async def authenticate(self, node):
        if len([
                n for n in self.bootstrappableNeighbors()
                if n == (node.ip, node.port, node.public_key)
        ]) == 1:
            log.debug('Node {}:{} is already a neighbor'.format(
                node.ip, node.port))
            return True
        authorized = await self.ironhouse.authenticate(
            node.public_key, node.ip, node.port + AUTH_PORT_OFFSET)
        log.debug('{}:{} is {}'.format(node.ip, node.port, authorized))
        if authorized == 'authorized':
            self.protocol.router.addContact(node)
            self.connect_to_neighbor(node)
            return True
        else:
            return False

    def setup_stethoscope(self):
        socket.setdefaulttimeout(0.1)
        self.stethoscope_sock = socket.socket(socket.AF_INET,
                                              socket.SOCK_STREAM)
        self.stethoscope_sock.setsockopt(socket.SOL_SOCKET,
                                         socket.SO_REUSEADDR, 1)
        self.stethoscope_sock.setsockopt(socket.IPPROTO_TCP,
                                         socket.TCP_NODELAY, 1)
        self.stethoscope_sock.setblocking(0)
        self.stethoscope_sock.bind(('0.0.0.0', self.heartbeat_port))
        self.stethoscope_sock.listen(self.max_peers)
        self.stethoscope_future = asyncio.ensure_future(self.stethoscope())

    async def stethoscope(self):
        self.connections = {}
        self.poll = poll()
        log.debug('Listening to heartbeats on {}...'.format(
            self.heartbeat_port))
        try:
            while True:
                events = self.poll.poll(1)
                for fileno, event in events:
                    if event & (POLLIN):
                        conn, node = self.connections[fileno]
                        addr = (node.ip, node.port + HEARTBEAT_PORT_OFFSET)
                        try:
                            log.debug('reconnecting {} - {}'.format(
                                self.network_port, addr))
                            conn.connect(addr)
                        except Exception as e:
                            log.debug(e.args)
                            if e.args[1] == 'Connection reset by peer':
                                log.info(
                                    "Client ({}, {}) disconnected from {}".
                                    format(*addr, self.node))
                                del self.connections[fileno]
                                if self.vkcache.get(node.ip):
                                    del self.vkcache[node.ip]
                                self.protocol.router.removeContact(node)
                                self.poll.unregister(fileno)
                                conn.close()
                                self.connection_drop()
                await asyncio.sleep(0.1)
        except asyncio.CancelledError:
            log.info('Network shutting down gracefully.')

    def connection_drop(self):
        if self.daemon:
            callback = ReactorCommand.create_callback(
                callback=StateInput.CONN_DROPPED,
                vk=self.ironhouse.vk,
                ip=self.node.ip)
            log.debug(
                "Sending callback failure to mainthread {}".format(callback))
            self.daemon.socket.send(callback.serialize())

    def connect_to_neighbor(self, node):
        if self.node.id == node.id: return

        self.ironhouse.create_from_public_key(node.public_key)
        self.ironhouse.reconfigure_curve()

        addr = (node.ip, node.port + HEARTBEAT_PORT_OFFSET)
        conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.connections[conn.fileno()] = (conn, node)
        try:
            conn.connect(addr)
            self.poll.register(conn.fileno(), POLLIN)
            log.info("[CLIENT SIDE] Client ({}, {}) connected".format(*addr))
            return conn
        except Exception as e:
            del self.connections[conn.fileno()]
            conn.close()

    def lookup_ip_in_cache(self, vk):
        ip = self.vkcache.get(vk)
        if ip:
            log.debug('Found ip {} in cache'.format(ip))
        return ip

    async def lookup_ip(self, node_key):
        cache_node = self.lookup_ip_in_cache(node_key)
        if cache_node: return cache_node, True
        node_id = digest(node_key)
        if node_id == self.node.id: return self.node

        nearest = self.protocol.router.findNeighbors(self.node)
        spider = NodeSpiderCrawl(self.protocol, self.node, nearest, self.ksize,
                                 self.alpha)

        log.debug("Starting lookup for node_key {}".format(node_key))
        res_node = await spider.find(node_id=node_id)

        if type(res_node) == list: res_node = None
        log.debug('{} resolves to {}'.format(node_key, res_node))
        if res_node != None:
            self.vkcache[node_key] = res_node.ip
            pk = self.ironhouse.vk2pk(node_key)
        return res_node, False

    def stop(self):
        if self.transport is not None:
            self.transport.close()

        if not self.refresh_future.done():
            self.refresh_future.set_result('done')

        if self.refresh_loop:
            self.refresh_loop.cancel()

        if self.save_state_loop:
            self.save_state_loop.cancel()

        for fileno in self.connections:
            conn, node = self.connections[fileno]
            try:
                self.poll.unregister(fileno)
            except:
                log.debug('Already unregistered')
            conn.close()
            log.debug('Closed a previously opened connection')

        self.ironhouse.cleanup()

        try:
            self.poll.unregister(self.stethoscope_sock.fileno())
        except:
            log.debug('Stehoscope is already unregistered')
        self.stethoscope_sock.close()
        try:
            self.poll.close()
        except:
            pass  #log.debug('Not epoll object, no need to close.')
        self.stethoscope_future.cancel()

    def _create_protocol(self):
        return self.protocol_class(self.node, self.ksize, self)

    def listen(self, port=None, interface='0.0.0.0'):
        """
        Start listening on the given port.

        Provide interface="::" to accept ipv6 address
        """
        port = self.network_port
        listen = self.loop.create_datagram_endpoint(self._create_protocol,
                                                    local_addr=(interface,
                                                                port))
        log.info("Listening to kade network on %s:%i", interface, port)
        self.transport, self.protocol = self.loop.run_until_complete(listen)
        # finally, schedule refreshing table
        self.refresh_table()

    def refresh_table(self):
        self.refresh_loop = self.loop.call_later(3600, self.refresh_table)
        self.refresh_future = asyncio.ensure_future(self._refresh_table())
        return self.refresh_future

    async def _refresh_table(self):
        """
        Refresh buckets that haven't had any lookups in the last hour
        (per section 2.3 of the paper).
        """
        log.debug("Refreshing routing table")
        ds = []
        for node_id in self.protocol.getRefreshIDs():
            node = Node(node_id=node_id)
            nearest = self.protocol.router.findNeighbors(node, self.alpha)
            spider = NodeSpiderCrawl(self.protocol, node, nearest, self.ksize,
                                     self.alpha)
            ds.append(spider.find())

        # do our crawling
        await asyncio.gather(*ds)

    def bootstrappableNeighbors(self):
        """
        Get a :class:`list` of (ip, port) :class:`tuple` pairs suitable for
        use as an argument to the bootstrap method.

        The server should have been bootstrapped
        already - this is just a utility for getting some neighbors and then
        storing them if this server is going down for a while.  When it comes
        back up, the list of nodes can be used to bootstrap.
        """
        neighbors = self.protocol.router.findNeighbors(self.node)
        return [tuple(n)[-3:] for n in neighbors]

    async def bootstrap(self, addrs):
        """
        Bootstrap the server by connecting to other known nodes in the network.

        Args:
            addrs: A `list` of (ip, port) `tuple` pairs.  Note that only IP
                   addresses are acceptable - hostnames will cause an error.
        """
        log.debug("Attempting to bootstrap node with %i initial contacts",
                  len(addrs))
        cos = list(map(self.bootstrap_node, addrs))
        gathered = await asyncio.gather(*cos)
        nodes = [node for node in gathered if node is not None]

        if len(nodes) == 0:
            log.warning(
                'Unable to find/authenticate with any nodes in the network')
            return []

        spider = NodeSpiderCrawl(self.protocol, self.node, nodes, self.ksize,
                                 self.alpha)
        res = await spider.find()
        return res

    async def bootstrap_node(self, addr):
        result = await self.protocol.ping(addr, self.node.public_key,
                                          self.node.id)
        if result[0]:
            node_id, public_key = result[1]
            node = Node(node_id,
                        ip=addr[0],
                        port=addr[1],
                        public_key=public_key)
            authorized = await self.authenticate(node)
            if authorized == True:
                return node
        return None

    def saveState(self, fname):
        """
        Save the state of this node (the alpha/ksize/id/immediate neighbors)
        to a cache file with the given fname.
        """
        log.info("Saving state to %s", fname)
        data = {
            'ksize': self.ksize,
            'alpha': self.alpha,
            'id': self.node.id,
            'neighbors': self.bootstrappableNeighbors()
        }
        if len(data['neighbors']) == 0:
            log.info("No known neighbors, so not writing to cache.")
            return False
        with open(fname, 'wb+') as f:
            pickle.dump(data, f)
        return True

    @classmethod
    def loadState(self, fname):
        """
        Load the state of this node (the alpha/ksize/id/immediate neighbors)
        from a cache file with the given fname.
        """
        log.info("Loading state from %s", fname)
        with open(fname, 'rb') as f:
            data = pickle.load(f)
        return data

    def saveStateRegularly(self, fname, frequency=600):
        """
        Save the state of node with a given regularity to the given
        filename.

        Args:
            fname: File name to save retularly to
            frequency: Frequency in seconds that the state should be saved.
                        By default, 10 minutes.
        """
        self.saveState(fname)
        self.save_state_loop = self.loop.call_later(frequency,
                                                    self.saveStateRegularly,
                                                    fname, frequency)