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:: 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
---------------

View File

@ -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:

View File

@ -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)

View File

@ -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):

View File

@ -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
)

View File

@ -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'

View File

@ -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"]]()

View File

@ -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 "<Destination: {}>".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 "<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):

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)