From be146d74d764eb8bfc3270d0576fae7a761cfc6e Mon Sep 17 00:00:00 2001 From: Viktor Villainov Date: Sat, 15 Sep 2018 05:52:52 -0400 Subject: [PATCH] merge PrivateKey to Destination --- docs/api.rst | 4 +- docs/examples/http_server.py | 8 ++-- docs/examples/ping_pong_test.py | 2 +- docs/examples/sync/wget.py | 1 + i2plib/__init__.py | 2 +- i2plib/__version__.py | 2 +- i2plib/aiosam.py | 44 +++++++++--------- i2plib/sam.py | 81 ++++++++++++++++----------------- i2plib/tunnel.py | 24 +++++----- i2plib/utils.py | 4 +- test/func/test_sam_ping_pong.py | 2 +- test/test_address.py | 6 +-- 12 files changed, 90 insertions(+), 90 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index b52fa8c..32be149 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -27,7 +27,7 @@ Utilities --------- .. autofunction:: dest_lookup -.. autofunction:: new_private_key +.. autofunction:: new_destination .. autofunction:: get_sam_address Tunnel API @@ -52,13 +52,13 @@ Data structures .. autoattribute:: i2plib.sam.Destination.data .. autoattribute:: i2plib.sam.Destination.base64 +.. autoattribute:: i2plib.sam.Destination.private_key .. autoclass:: i2plib.PrivateKey :members: .. autoattribute:: i2plib.sam.PrivateKey.data .. autoattribute:: i2plib.sam.PrivateKey.base64 -.. autoattribute:: i2plib.sam.PrivateKey.destination Exceptions --------------- diff --git a/docs/examples/http_server.py b/docs/examples/http_server.py index 000a6ee..6127193 100644 --- a/docs/examples/http_server.py +++ b/docs/examples/http_server.py @@ -25,11 +25,11 @@ def main(args): raise OSError("No such directory {}".format(args.web_directory)) if args.key: - priv = i2plib.PrivateKey(path=args.key) + dest = i2plib.Destination(path=args.key, has_private_key=True) else: - priv = i2plib.utils.get_new_private_key(sam_address=sam_address) + dest = i2plib.utils.get_new_destination(sam_address=sam_address) - logging.info("Listening: {}.b32.i2p".format(priv.destination.base32)) + logging.info("Listening: {}.b32.i2p".format(dest.base32)) logging.info("Server: {}:{}".format(server_address[0], server_address[1])) # run HTTP server @@ -41,7 +41,7 @@ def main(args): loop = asyncio.get_event_loop() tunnel = i2plib.ServerTunnel(server_address, - loop=loop, private_key=priv, sam_address=sam_address) + loop=loop, destination=dest, sam_address=sam_address) asyncio.ensure_future(tunnel.run(), loop=loop) try: diff --git a/docs/examples/ping_pong_test.py b/docs/examples/ping_pong_test.py index 1683202..d0c27bf 100644 --- a/docs/examples/ping_pong_test.py +++ b/docs/examples/ping_pong_test.py @@ -10,7 +10,7 @@ DEST_B32 = "bxwnysaa2nwykldz4ekz6u243x5ctqlcot5acmzj2huylvwr7eyq.b32.i2p" async def ping_pong(sam_address, loop): _, server_session_writer = await i2plib.create_session("ppserver", - sam_address=sam_address, loop=loop, private_key=PK_B64) + sam_address=sam_address, loop=loop, destination=PK_B64) server_reader, server_writer = await i2plib.stream_accept("ppserver", sam_address=sam_address, loop=loop) diff --git a/docs/examples/sync/wget.py b/docs/examples/sync/wget.py index 8096a58..2b1d32d 100644 --- a/docs/examples/sync/wget.py +++ b/docs/examples/sync/wget.py @@ -1,6 +1,7 @@ import sys from urllib.parse import urlparse +import i2plib from i2plib.sam import generate_session_id, lookup, get_socket, StreamSession def http_get(url, sam_address): diff --git a/i2plib/__init__.py b/i2plib/__init__.py index bb4c36c..c6b574d 100644 --- a/i2plib/__init__.py +++ b/i2plib/__init__.py @@ -10,7 +10,7 @@ from .__version__ import ( from .sam import Destination, PrivateKey from .aiosam import ( - get_sam_socket, dest_lookup, new_private_key, + get_sam_socket, dest_lookup, new_destination, create_session, stream_connect, stream_accept ) diff --git a/i2plib/__version__.py b/i2plib/__version__.py index 6335e8a..b62a445 100644 --- a/i2plib/__version__.py +++ b/i2plib/__version__.py @@ -1,7 +1,7 @@ __title__ = 'i2plib' __description__ = 'A modern asynchronous library for building I2P applications.' __url__ = 'https://github.com/l-n-s/i2plib' -__version__ = '0.0.7' +__version__ = '0.0.8' __author__ = 'Viktor Villainov' __author_email__ = 'supervillain@riseup.net' __license__ = 'MIT' diff --git a/i2plib/aiosam.py b/i2plib/aiosam.py index 195ba7a..86aacd5 100644 --- a/i2plib/aiosam.py +++ b/i2plib/aiosam.py @@ -45,26 +45,26 @@ async def dest_lookup(domain, sam_address=i2plib.sam.DEFAULT_ADDRESS, else: raise i2plib.exceptions.SAM_EXCEPTIONS[reply["RESULT"]]() -async def new_private_key(sam_address=i2plib.sam.DEFAULT_ADDRESS, loop=None, +async def new_destination(sam_address=i2plib.sam.DEFAULT_ADDRESS, loop=None, sig_type=i2plib.sam.Destination.default_sig_type): - """A coroutine used to generate a new private key of a chosen signature - type. + """A coroutine used to generate a new destination with a private key of a + chosen signature type. :param sam_address: (optional) SAM API address :param loop: (optional) Event loop instance :param sig_type: (optional) Signature type - :return: An instance of i2plib.PrivateKey + :return: An instance of i2plib.Destination """ reader, writer = await get_sam_socket(sam_address, loop) writer.write(i2plib.sam.dest_generate(sig_type)) reply = parse_reply(await reader.read(BUFFER_SIZE)) writer.close() - return i2plib.sam.PrivateKey(reply["PRIV"]) + return i2plib.sam.Destination(reply["PRIV"], has_private_key=True) async def create_session(session_name, sam_address=i2plib.sam.DEFAULT_ADDRESS, loop=None, session_ready=None, style="STREAM", signature_type=i2plib.sam.Destination.default_sig_type, - private_key=None, options={}, session_created=None, + destination=None, options={}, session_created=None, args=()): """A coroutine used to create a new SAM session. @@ -76,44 +76,46 @@ async def create_session(session_name, sam_address=i2plib.sam.DEFAULT_ADDRESS, :param style: (optional) Session style, can be STREAM, DATAGRAM, RAW :param signature_type: (optional) If the destination is TRANSIENT, this signature type is used - :param private_key: (optional) Private key to use in this session. Can be - a base64 encoded string, i2plib.sam.PrivateKey instance + :param destination: (optional) Destination to use in this session. Can be + a base64 encoded string, i2plib.sam.Destination instance or None. TRANSIENT destination is used when it is None. :param options: (optional) A dict object with i2cp options :param session_created: (optional) A coroutine to be executed after the session is created. Executed with arguments - (loop, reader, writer, private_key, \*args) + (loop, reader, writer, destination, \*args) :param args: (optional) Arguments for a session_created coroutine :return: A (reader, writer) pair """ logging.debug("Creating session {}".format(session_name)) - if private_key: - if type(private_key) == i2plib.sam.PrivateKey: - private_key = private_key + if destination: + if type(destination) == i2plib.sam.Destination: + destination = destination else: - private_key = i2plib.sam.PrivateKey(private_key) - destination = private_key.base64 + destination = i2plib.sam.Destination( + destination, has_private_key=True) + + dest_string = destination.private_key.base64 else: - private_key = None - destination = i2plib.sam.TRANSIENT_DESTINATION + dest_string = i2plib.sam.TRANSIENT_DESTINATION options = " ".join(["{}={}".format(k, v) for k, v in options.items()]) reader, writer = await get_sam_socket(sam_address, loop) writer.write(i2plib.sam.session_create( - style, session_name, destination, options)) + style, session_name, dest_string, options)) reply = parse_reply(await reader.read(BUFFER_SIZE)) if reply.ok: - if not private_key: - private_key = i2plib.sam.PrivateKey(reply["DESTINATION"]) - logging.debug(private_key.destination.base32) + if not destination: + destination = i2plib.sam.Destination( + reply["DESTINATION"], has_private_key=True) + logging.debug(destination.base32) if session_ready: session_ready.set() logging.debug("Session created {}".format(session_name)) if session_created: asyncio.ensure_future(session_created(loop, reader, writer, - private_key, *args), loop=loop) + destination, *args), loop=loop) return (reader, writer) else: raise i2plib.exceptions.SAM_EXCEPTIONS[reply["RESULT"]]() diff --git a/i2plib/sam.py b/i2plib/sam.py index 1f59e9e..59a40eb 100644 --- a/i2plib/sam.py +++ b/i2plib/sam.py @@ -10,6 +10,14 @@ from .exceptions import SAM_EXCEPTIONS I2P_B64_CHARS = "-~" +def i2p_b64encode(x): + """Encode I2P destination""" + return b64encode(x, altchars=I2P_B64_CHARS.encode()).decode() + +def i2p_b64decode(x): + """Decode I2P destination""" + return b64decode(x, altchars=I2P_B64_CHARS, validate=True) + SAM_BUFSIZE = 4096 DEFAULT_ADDRESS = ("127.0.0.1", 7656) DEFAULT_MIN_VER = "3.1" @@ -111,6 +119,8 @@ class Destination(object): https://geti2p.net/spec/common-structures#destination :param data: (optional) Base64 encoded data or binary data + :param path: (optional) A path to a file with binary data + :param has_private_key: (optional) Does data have a private key? """ ECDSA_SHA256_P256 = 1 @@ -120,20 +130,32 @@ class Destination(object): default_sig_type = EdDSA_SHA512_Ed25519 - def __init__(self, data=None): - #: Binary private key data + _pubkey_size = 256 + _signkey_size = 128 + _min_cert_size = 3 + + def __init__(self, data=None, path=None, has_private_key=False): + #: Binary destination self.data = bytes() - #: Base64 encoded private key data + #: Base64 encoded destination self.base64 = "" + #: i2plib.PrivateKey instance or None + self.private_key = None - if data: - if type(data) == bytes: - self.data = data - self.base64 = b64encode(self.data, - altchars=I2P_B64_CHARS.encode()).decode() - elif type(data) == str: - self.data = b64decode(data, altchars=I2P_B64_CHARS, validate=True) - self.base64 = data + if path: + with open(path, "rb") as f: data = f.read() + + if data and has_private_key: + self.private_key = PrivateKey(data) + + cert_len = struct.unpack("!H", self.private_key.data[385:387])[0] + data = self.private_key.data[:387+cert_len] + + if not data: + raise Exception("Can't create a destination with no data") + + self.data = data if type(data) == bytes else i2p_b64decode(data) + self.base64 = data if type(data) == str else i2p_b64encode(data) def __repr__(self): return "".format(self.base32) @@ -149,39 +171,14 @@ class PrivateKey(object): https://geti2p.net/spec/common-structures#keysandcert - :param data: (optional) Base64 encoded data or binary data - :param path: (optional) Path to a file with binary private key data + :param data: Base64 encoded data or binary data """ - _pubkey_size = 256 - _signkey_size = 128 - _min_cert_size = 3 - - def __init__(self, data=None, path=None): - #: Binary private key data - self.data = bytes() - #: Base64 encoded private key data - self.base64 = "" - #: i2plib.Destination object of the private key - self.destination = None - - if path: - with open(path, "rb") as f: data = f.read() - if data: - if type(data) == bytes: - self.data = data - self.base64 = b64encode(self.data, - altchars=I2P_B64_CHARS.encode()).decode() - elif type(data) == str: - self.data = b64decode(data, altchars=I2P_B64_CHARS, validate=True) - self.base64 = data - - cert_len = struct.unpack("!H", self.data[385:387])[0] - self.destination = Destination(data=self.data[:387+cert_len]) - - def __repr__(self): - return "".format(self.destination.base32) - + def __init__(self, data): + #: Binary private key + self.data = data if type(data) == bytes else i2p_b64decode(data) + #: Base64 encoded private key + self.base64 = data if type(data) == str else i2p_b64encode(data) class StreamSession(object): diff --git a/i2plib/tunnel.py b/i2plib/tunnel.py index c49fbdc..5ebcff9 100644 --- a/i2plib/tunnel.py +++ b/i2plib/tunnel.py @@ -30,9 +30,9 @@ class I2PTunnel(object): :param local_address: A local address to use for a tunnel. E.g. ("127.0.0.1", 6668) - :param private_key: (optional) Private key to use in this session. Can be - a base64 encoded string, i2plib.sam.PrivateKey instance - or None. A new key is created when it is None. + :param destination: (optional) Destination to use in this session. Can be + a base64 encoded string, i2plib.sam.Destination instance + or None. A new destination is created when it is None. :param session_name: (optional) Session nick name. A new session nickname is generated if not specified. :param options: (optional) A dict object with i2cp options @@ -40,23 +40,23 @@ class I2PTunnel(object): :param sam_address: (optional) SAM API address """ - def __init__(self, local_address, private_key=None, session_name=None, + def __init__(self, local_address, destination=None, session_name=None, options={}, loop=None, sam_address=i2plib.sam.DEFAULT_ADDRESS): self.local_address = local_address - self.private_key = private_key + self.destination = destination self.session_name = session_name or i2plib.sam.generate_session_id() self.options = options self.loop = loop self.sam_address = sam_address async def _pre_run(self): - if not self.private_key: - self.private_key = await i2plib.new_private_key( + if not self.destination: + self.destination = await i2plib.new_destination( sam_address=self.sam_address, loop=self.loop) _, self.session_writer = await i2plib.aiosam.create_session( self.session_name, style=self.style, options=self.options, sam_address=self.sam_address, - loop=self.loop, private_key=self.private_key) + loop=self.loop, destination=self.destination) def stop(self): """Stop the tunnel""" @@ -169,17 +169,17 @@ if __name__ == '__main__': loop.set_debug(args.debug) if args.key: - private_key = i2plib.sam.PrivateKey(path=args.key) + destination = i2plib.sam.Destination(path=args.key, has_private_key=True) else: - private_key = None + destination = None local_address = i2plib.utils.address_from_string(args.address) if args.type == "client": tunnel = ClientTunnel(args.destination, local_address, loop=loop, - private_key=private_key, sam_address=SAM_ADDRESS) + destination=destination, sam_address=SAM_ADDRESS) elif args.type == "server": - tunnel = ServerTunnel(local_address, loop=loop, private_key=private_key, + tunnel = ServerTunnel(local_address, loop=loop, destination=destination, sam_address=SAM_ADDRESS) asyncio.ensure_future(tunnel.run(), loop=loop) diff --git a/i2plib/utils.py b/i2plib/utils.py index 04ade72..4f3c55b 100644 --- a/i2plib/utils.py +++ b/i2plib/utils.py @@ -24,12 +24,12 @@ def get_sam_address(): else: return i2plib.sam.DEFAULT_ADDRESS -def get_new_private_key(sam_address=i2plib.sam.DEFAULT_ADDRESS, +def get_new_destination(sam_address=i2plib.sam.DEFAULT_ADDRESS, sig_type=i2plib.sam.Destination.default_sig_type): """Generates new I2P destination of a chosen signature type""" sam_socket = i2plib.sam.get_socket(sam_address) sam_socket.send(i2plib.sam.dest_generate(sig_type)) a = i2plib.sam.get_response(sam_socket) sam_socket.close() - return i2plib.sam.PrivateKey(a['PRIV']) + return i2plib.sam.Destination(a['PRIV'], has_private_key=True) diff --git a/test/func/test_sam_ping_pong.py b/test/func/test_sam_ping_pong.py index 8fdb31c..69aace5 100644 --- a/test/func/test_sam_ping_pong.py +++ b/test/func/test_sam_ping_pong.py @@ -46,7 +46,7 @@ async def ping_pong(sam_address, loop): await asyncio.sleep(0.1) _, server_session_writer = await i2plib.create_session("ppserver", - sam_address=sam_address, loop=loop, private_key=PK_B64) + sam_address=sam_address, loop=loop, destination=PK_B64) server_reader, server_writer = await i2plib.stream_accept("ppserver", sam_address=sam_address, loop=loop) diff --git a/test/test_address.py b/test/test_address.py index ef45f5f..b260c1e 100644 --- a/test/test_address.py +++ b/test/test_address.py @@ -10,9 +10,9 @@ TEST_DEST_B64 = "Q8VZ41jSRFutjRYDbnDioTfMitFMAMINRqrgcwISQ3ObOyzjBX9Kz~OJz7ShyGq class TestDestination(unittest.TestCase): def test_private_key(self): - pk = i2plib.sam.PrivateKey(path=TEST_KEY) - self.assertEqual(pk.destination.base32, TEST_DEST_B32) - self.assertEqual(pk.destination.base64, TEST_DEST_B64) + pk = i2plib.sam.Destination(path=TEST_KEY, has_private_key=True) + self.assertEqual(pk.base32, TEST_DEST_B32) + self.assertEqual(pk.base64, TEST_DEST_B64) def test_destination(self): dest = i2plib.sam.Destination(TEST_DEST_B64)