merge PrivateKey to Destination

pull/1/head
Viktor Villainov 2018-09-15 05:52:52 -04:00
parent 29c9c2b8db
commit be146d74d7
No known key found for this signature in database
GPG Key ID: 8EB38B46F33BAF2F
12 changed files with 90 additions and 90 deletions

View File

@ -27,7 +27,7 @@ Utilities
--------- ---------
.. autofunction:: dest_lookup .. autofunction:: dest_lookup
.. autofunction:: new_private_key .. autofunction:: new_destination
.. autofunction:: get_sam_address .. autofunction:: get_sam_address
Tunnel API Tunnel API
@ -52,13 +52,13 @@ Data structures
.. autoattribute:: i2plib.sam.Destination.data .. autoattribute:: i2plib.sam.Destination.data
.. autoattribute:: i2plib.sam.Destination.base64 .. autoattribute:: i2plib.sam.Destination.base64
.. autoattribute:: i2plib.sam.Destination.private_key
.. autoclass:: i2plib.PrivateKey .. autoclass:: i2plib.PrivateKey
:members: :members:
.. autoattribute:: i2plib.sam.PrivateKey.data .. autoattribute:: i2plib.sam.PrivateKey.data
.. autoattribute:: i2plib.sam.PrivateKey.base64 .. autoattribute:: i2plib.sam.PrivateKey.base64
.. autoattribute:: i2plib.sam.PrivateKey.destination
Exceptions Exceptions
--------------- ---------------

View File

@ -25,11 +25,11 @@ def main(args):
raise OSError("No such directory {}".format(args.web_directory)) raise OSError("No such directory {}".format(args.web_directory))
if args.key: if args.key:
priv = i2plib.PrivateKey(path=args.key) dest = i2plib.Destination(path=args.key, has_private_key=True)
else: 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])) logging.info("Server: {}:{}".format(server_address[0], server_address[1]))
# run HTTP server # run HTTP server
@ -41,7 +41,7 @@ def main(args):
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
tunnel = i2plib.ServerTunnel(server_address, 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) asyncio.ensure_future(tunnel.run(), loop=loop)
try: try:

View File

@ -10,7 +10,7 @@ DEST_B32 = "bxwnysaa2nwykldz4ekz6u243x5ctqlcot5acmzj2huylvwr7eyq.b32.i2p"
async def ping_pong(sam_address, loop): async def ping_pong(sam_address, loop):
_, server_session_writer = await i2plib.create_session("ppserver", _, 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", server_reader, server_writer = await i2plib.stream_accept("ppserver",
sam_address=sam_address, loop=loop) sam_address=sam_address, loop=loop)

View File

@ -1,6 +1,7 @@
import sys import sys
from urllib.parse import urlparse from urllib.parse import urlparse
import i2plib
from i2plib.sam import generate_session_id, lookup, get_socket, StreamSession from i2plib.sam import generate_session_id, lookup, get_socket, StreamSession
def http_get(url, sam_address): def http_get(url, sam_address):

View File

@ -10,7 +10,7 @@ from .__version__ import (
from .sam import Destination, PrivateKey from .sam import Destination, PrivateKey
from .aiosam import ( from .aiosam import (
get_sam_socket, dest_lookup, new_private_key, get_sam_socket, dest_lookup, new_destination,
create_session, stream_connect, stream_accept create_session, stream_connect, stream_accept
) )

View File

@ -1,7 +1,7 @@
__title__ = 'i2plib' __title__ = 'i2plib'
__description__ = 'A modern asynchronous library for building I2P applications.' __description__ = 'A modern asynchronous library for building I2P applications.'
__url__ = 'https://github.com/l-n-s/i2plib' __url__ = 'https://github.com/l-n-s/i2plib'
__version__ = '0.0.7' __version__ = '0.0.8'
__author__ = 'Viktor Villainov' __author__ = 'Viktor Villainov'
__author_email__ = 'supervillain@riseup.net' __author_email__ = 'supervillain@riseup.net'
__license__ = 'MIT' __license__ = 'MIT'

View File

@ -45,26 +45,26 @@ async def dest_lookup(domain, sam_address=i2plib.sam.DEFAULT_ADDRESS,
else: else:
raise i2plib.exceptions.SAM_EXCEPTIONS[reply["RESULT"]]() 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): sig_type=i2plib.sam.Destination.default_sig_type):
"""A coroutine used to generate a new private key of a chosen signature """A coroutine used to generate a new destination with a private key of a
type. chosen signature type.
:param sam_address: (optional) SAM API address :param sam_address: (optional) SAM API address
:param loop: (optional) Event loop instance :param loop: (optional) Event loop instance
:param sig_type: (optional) Signature type :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) reader, writer = await get_sam_socket(sam_address, loop)
writer.write(i2plib.sam.dest_generate(sig_type)) writer.write(i2plib.sam.dest_generate(sig_type))
reply = parse_reply(await reader.read(BUFFER_SIZE)) reply = parse_reply(await reader.read(BUFFER_SIZE))
writer.close() 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, async def create_session(session_name, sam_address=i2plib.sam.DEFAULT_ADDRESS,
loop=None, session_ready=None, style="STREAM", loop=None, session_ready=None, style="STREAM",
signature_type=i2plib.sam.Destination.default_sig_type, signature_type=i2plib.sam.Destination.default_sig_type,
private_key=None, options={}, session_created=None, destination=None, options={}, session_created=None,
args=()): args=()):
"""A coroutine used to create a new SAM session. """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 style: (optional) Session style, can be STREAM, DATAGRAM, RAW
:param signature_type: (optional) If the destination is TRANSIENT, this :param signature_type: (optional) If the destination is TRANSIENT, this
signature type is used signature type is used
:param private_key: (optional) Private key to use in this session. Can be :param destination: (optional) Destination to use in this session. Can be
a base64 encoded string, i2plib.sam.PrivateKey instance a base64 encoded string, i2plib.sam.Destination instance
or None. TRANSIENT destination is used when it is None. or None. TRANSIENT destination is used when it is None.
:param options: (optional) A dict object with i2cp options :param options: (optional) A dict object with i2cp options
:param session_created: (optional) A coroutine to be executed after the :param session_created: (optional) A coroutine to be executed after the
session is created. Executed with arguments 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 :param args: (optional) Arguments for a session_created coroutine
:return: A (reader, writer) pair :return: A (reader, writer) pair
""" """
logging.debug("Creating session {}".format(session_name)) logging.debug("Creating session {}".format(session_name))
if private_key: if destination:
if type(private_key) == i2plib.sam.PrivateKey: if type(destination) == i2plib.sam.Destination:
private_key = private_key destination = destination
else: else:
private_key = i2plib.sam.PrivateKey(private_key) destination = i2plib.sam.Destination(
destination = private_key.base64 destination, has_private_key=True)
dest_string = destination.private_key.base64
else: else:
private_key = None dest_string = i2plib.sam.TRANSIENT_DESTINATION
destination = i2plib.sam.TRANSIENT_DESTINATION
options = " ".join(["{}={}".format(k, v) for k, v in options.items()]) options = " ".join(["{}={}".format(k, v) for k, v in options.items()])
reader, writer = await get_sam_socket(sam_address, loop) reader, writer = await get_sam_socket(sam_address, loop)
writer.write(i2plib.sam.session_create( 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)) reply = parse_reply(await reader.read(BUFFER_SIZE))
if reply.ok: if reply.ok:
if not private_key: if not destination:
private_key = i2plib.sam.PrivateKey(reply["DESTINATION"]) destination = i2plib.sam.Destination(
logging.debug(private_key.destination.base32) reply["DESTINATION"], has_private_key=True)
logging.debug(destination.base32)
if session_ready: if session_ready:
session_ready.set() session_ready.set()
logging.debug("Session created {}".format(session_name)) logging.debug("Session created {}".format(session_name))
if session_created: if session_created:
asyncio.ensure_future(session_created(loop, reader, writer, asyncio.ensure_future(session_created(loop, reader, writer,
private_key, *args), loop=loop) destination, *args), loop=loop)
return (reader, writer) return (reader, writer)
else: else:
raise i2plib.exceptions.SAM_EXCEPTIONS[reply["RESULT"]]() raise i2plib.exceptions.SAM_EXCEPTIONS[reply["RESULT"]]()

View File

@ -10,6 +10,14 @@ from .exceptions import SAM_EXCEPTIONS
I2P_B64_CHARS = "-~" 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 SAM_BUFSIZE = 4096
DEFAULT_ADDRESS = ("127.0.0.1", 7656) DEFAULT_ADDRESS = ("127.0.0.1", 7656)
DEFAULT_MIN_VER = "3.1" DEFAULT_MIN_VER = "3.1"
@ -111,6 +119,8 @@ class Destination(object):
https://geti2p.net/spec/common-structures#destination https://geti2p.net/spec/common-structures#destination
:param data: (optional) Base64 encoded data or binary data :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 ECDSA_SHA256_P256 = 1
@ -120,20 +130,32 @@ class Destination(object):
default_sig_type = EdDSA_SHA512_Ed25519 default_sig_type = EdDSA_SHA512_Ed25519
def __init__(self, data=None): _pubkey_size = 256
#: Binary private key data _signkey_size = 128
self.data = bytes() _min_cert_size = 3
#: Base64 encoded private key data
self.base64 = ""
if data: def __init__(self, data=None, path=None, has_private_key=False):
if type(data) == bytes: #: Binary destination
self.data = data self.data = bytes()
self.base64 = b64encode(self.data, #: Base64 encoded destination
altchars=I2P_B64_CHARS.encode()).decode() self.base64 = ""
elif type(data) == str: #: i2plib.PrivateKey instance or None
self.data = b64decode(data, altchars=I2P_B64_CHARS, validate=True) self.private_key = None
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): def __repr__(self):
return "<Destination: {}>".format(self.base32) return "<Destination: {}>".format(self.base32)
@ -149,39 +171,14 @@ class PrivateKey(object):
https://geti2p.net/spec/common-structures#keysandcert https://geti2p.net/spec/common-structures#keysandcert
:param data: (optional) Base64 encoded data or binary data :param data: Base64 encoded data or binary data
:param path: (optional) Path to a file with binary private key 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 "<PrivateKey: {}>".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): class StreamSession(object):

View File

@ -30,9 +30,9 @@ class I2PTunnel(object):
:param local_address: A local address to use for a tunnel. :param local_address: A local address to use for a tunnel.
E.g. ("127.0.0.1", 6668) E.g. ("127.0.0.1", 6668)
:param private_key: (optional) Private key to use in this session. Can be :param destination: (optional) Destination to use in this session. Can be
a base64 encoded string, i2plib.sam.PrivateKey instance a base64 encoded string, i2plib.sam.Destination instance
or None. A new key is created when it is None. or None. A new destination is created when it is None.
:param session_name: (optional) Session nick name. A new session nickname is :param session_name: (optional) Session nick name. A new session nickname is
generated if not specified. generated if not specified.
:param options: (optional) A dict object with i2cp options :param options: (optional) A dict object with i2cp options
@ -40,23 +40,23 @@ class I2PTunnel(object):
:param sam_address: (optional) SAM API address :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): options={}, loop=None, sam_address=i2plib.sam.DEFAULT_ADDRESS):
self.local_address = local_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.session_name = session_name or i2plib.sam.generate_session_id()
self.options = options self.options = options
self.loop = loop self.loop = loop
self.sam_address = sam_address self.sam_address = sam_address
async def _pre_run(self): async def _pre_run(self):
if not self.private_key: if not self.destination:
self.private_key = await i2plib.new_private_key( self.destination = await i2plib.new_destination(
sam_address=self.sam_address, loop=self.loop) sam_address=self.sam_address, loop=self.loop)
_, self.session_writer = await i2plib.aiosam.create_session( _, self.session_writer = await i2plib.aiosam.create_session(
self.session_name, style=self.style, options=self.options, self.session_name, style=self.style, options=self.options,
sam_address=self.sam_address, sam_address=self.sam_address,
loop=self.loop, private_key=self.private_key) loop=self.loop, destination=self.destination)
def stop(self): def stop(self):
"""Stop the tunnel""" """Stop the tunnel"""
@ -169,17 +169,17 @@ if __name__ == '__main__':
loop.set_debug(args.debug) loop.set_debug(args.debug)
if args.key: if args.key:
private_key = i2plib.sam.PrivateKey(path=args.key) destination = i2plib.sam.Destination(path=args.key, has_private_key=True)
else: else:
private_key = None destination = None
local_address = i2plib.utils.address_from_string(args.address) local_address = i2plib.utils.address_from_string(args.address)
if args.type == "client": if args.type == "client":
tunnel = ClientTunnel(args.destination, local_address, loop=loop, 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": 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) sam_address=SAM_ADDRESS)
asyncio.ensure_future(tunnel.run(), loop=loop) asyncio.ensure_future(tunnel.run(), loop=loop)

View File

@ -24,12 +24,12 @@ def get_sam_address():
else: else:
return i2plib.sam.DEFAULT_ADDRESS 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): sig_type=i2plib.sam.Destination.default_sig_type):
"""Generates new I2P destination of a chosen signature type""" """Generates new I2P destination of a chosen signature type"""
sam_socket = i2plib.sam.get_socket(sam_address) sam_socket = i2plib.sam.get_socket(sam_address)
sam_socket.send(i2plib.sam.dest_generate(sig_type)) sam_socket.send(i2plib.sam.dest_generate(sig_type))
a = i2plib.sam.get_response(sam_socket) a = i2plib.sam.get_response(sam_socket)
sam_socket.close() sam_socket.close()
return i2plib.sam.PrivateKey(a['PRIV']) return i2plib.sam.Destination(a['PRIV'], has_private_key=True)

View File

@ -46,7 +46,7 @@ async def ping_pong(sam_address, loop):
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
_, server_session_writer = await i2plib.create_session("ppserver", _, 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", server_reader, server_writer = await i2plib.stream_accept("ppserver",
sam_address=sam_address, loop=loop) sam_address=sam_address, loop=loop)

View File

@ -10,9 +10,9 @@ TEST_DEST_B64 = "Q8VZ41jSRFutjRYDbnDioTfMitFMAMINRqrgcwISQ3ObOyzjBX9Kz~OJz7ShyGq
class TestDestination(unittest.TestCase): class TestDestination(unittest.TestCase):
def test_private_key(self): def test_private_key(self):
pk = i2plib.sam.PrivateKey(path=TEST_KEY) pk = i2plib.sam.Destination(path=TEST_KEY, has_private_key=True)
self.assertEqual(pk.destination.base32, TEST_DEST_B32) self.assertEqual(pk.base32, TEST_DEST_B32)
self.assertEqual(pk.destination.base64, TEST_DEST_B64) self.assertEqual(pk.base64, TEST_DEST_B64)
def test_destination(self): def test_destination(self):
dest = i2plib.sam.Destination(TEST_DEST_B64) dest = i2plib.sam.Destination(TEST_DEST_B64)